From c5477e4f9ea8a0d4d9ad77db2171999664a8b0c4 Mon Sep 17 00:00:00 2001 From: Omar Hatem Date: Fri, 14 Apr 2023 06:39:08 +0200 Subject: [PATCH] Dashboard desktop view (#737) * Add build scripts for macOS. Add macos for cw_monero plugin. Add macos proj to the application. * - Update Flutter secure storage to work with macos - Enable uni links only on Mobile - Update devcelocale to work with macos * Add network access to mac * Change Dashboard view on desktop size screens * Add on Tap to desktop_action_button.dart Remove unused functions * Fix arch match for monero lib for darwin x86_64 -> x86-64 * Add Bundle ID in entitlements files through app config script * Update deployment target to 10.13 * Revert back to Cake fork for secure storage * Revert back to Cake fork for secure storage * Revert mac os version * Revert mac os version * Add platform channel specific code for mac os * Add desktop sidebar * [skip ci] Add desktop sidebar * [skip ci] Add desktop sidebar * - Remove legacy migration from macos - Remove wake lock native code and just use the ready made package * Remove wake lock native code and just use the ready made package * Remove unstoppable domain from macos since it's not supported * Temporarily fetch unstoppable domains only on mobile * refactor desktop settings sidebar * Ignore increasing brightness for non-mobile platforms * Add Wallet selection dropdown to dashboard desktop view * Generate MacOS icons * localize settings * fix dashboard sidebar and responsive utils * Change Mac os app name and bundle id * Fix exchange page as fullScreenDialog * Remove constants * - Refactor onRamper to have a single point of modification - Enlarge initial app size - update Flutter and Packages * Add pubspec.lock and Podfile.lock to gitignore * Remove Podfile.lock from cache * Fix bug on sidebar reset * Fix issues from code review * [skip ci] reformat desktop dashboard * [skip ci] reformat desktop dashboard * Revert removing .lock files * Revert changes in .gitignore * [skip ci] remove .project changes * [skip ci] remove .project changes * Separate Dashboard desktop view from mobile view * constraint images and pincoded box * Remove drawer from mac os * - Listen to keyboard events in PIN screen - Fix PIN buttons style * Fix desktop nav bar UI * Add Marketplace to dashboard view * Update trailing icon to open transaction page * Update widget contraints * Add empty trailing to center page title on desktop * Refresh desktop dashboard actions on wallet change * Change ionia welcome page animation * Fix Constrained width screens UI * Refactor sidebar state management * remove empty line * Add max width constrain to Welcome page * Change Exchange page UI depending on platform * - Change design/paddings for Send page on desktop view - Make AddTemplateButton instead of having it duplicated in send/exchange * Fix Desktop dashboard actions background color * Constrain primary Buttons width * Make side menu items toggle back to dashboard * Add padding to support page * Add width constraints to desktop dashboard * Fix UI issues, paddings and alignments * Rename misleading variable Change initial mac window size * Fix wallet create in settings * remove unnecessary code * remove unnecessary code * Remove duplicated constrains * - Use close icon on main screens - Minor UI fixes * fix pageview controller reset index * Add create and restore wallet options to dropdown menu * Fix desktop background color and address book view issues * Fix input field * Add onFieldSubmitted to allow "enter" button interaction * Fix issue from code review * Fix Popup width constraint and add focus orders * Fix variable name * Fix issues from code review * refactor dropdown items * Fix alignment in create and restore wallet screens * Fix dropdown change state bug Hide scanner for desktop * remove space * override navbar with desktopnavbar * Remove autofocus * remove unused code * Fix ionia input field alignment * Replace removed code * Add app lock feature on mac * Add assertion to avoid null * Add Nano currency image * Enable adding contact from send screen * Fix UI issues Add missing translation * pop only PIN screen after successful auth * Add back wallet settings page to desktop settings actions * Fix Navigation animation for settings screens * Fixate MobX version to fix restore issue * CW-324 Refresh current settings page if wallet changed (#811) * Fix refresh current settings page if wallet changed * Fix refresh current settings page if wallet changed * Refresh Wallet Seeds/Keys List upon wallet change --------- Co-authored-by: OmarHatem * Remove navigation workaround for duplicate key, and fix the issue by handling creation/disposing of global key (#840) * Cw 323 add wallet list to settings on mac (#843) * Remove navigation workaround for duplicate key, and fix the issue by handling creation/disposing of global key * - Register Wallet List as singleton in Desktop to be modify the same instance from settings and dropdown - General Fixes and Enhancements * Fix Changing/Restoring wallet from settings * Fix Create wallet not showing seeds screens if launched from settings * Add max width constraint for Alerts * - Add Desktop API keys - Fix Change back up password issue - Fix Popup width * Sync Mac with latest main updates * Swap Transactions icon with lock icon * Save backup file locally on desktop * Sync with latest main updates * Fix Navigation issues with anonpay * Update macos build version * Remove deprecated custom wake lock code for Android * Remove Legacy CryptoSwift package from MacOS * - Refactor Payfura page code - Add OnRamper new configs to onramper_buy_provider.dart - Fix Conflicts with main * Updated device locale package * Update android tools * Revert changes and update only gradle version * Downgrade android tools version * Update gradle version * Update package/gradle/plugin version * - Fixate device locale version - Downgrade gradle version * Update kotlin version * Update gradle version * Trial for a custom fork from devicelocale * Fixate shared preferences package version * Revert gradle version * Revert kotlin version * Downgrade gradle version * Downgrade gradle version * Repair cache and clean before build * Fixate flutter version * update google services version * revert google services version * Force shared pref android version * Override shared prefs android package version * Override shared prefs android package [skip ci] --------- Co-authored-by: M Co-authored-by: Godwin Asuquo Co-authored-by: Godwin Asuquo <41484542+godilite@users.noreply.github.com> --- .github/workflows/pr_test_build.yml | 2 + .gitignore | 5 + .metadata | 24 +- .../cakewallet/cake_wallet/MainActivity.java | 8 - .../com/cakewallet/haven/MainActivity.java | 8 - .../java/com/monero/app/MainActivity.java | 8 - .../desktop_transactions_outline_icon.png | Bin 0 -> 663 bytes .../desktop_transactions_solid_icon.png | Bin 0 -> 390 bytes assets/images/settings_outline.png | Bin 0 -> 871 bytes assets/images/support_icon.png | Bin 0 -> 819 bytes assets/images/wallet_outline.png | Bin 0 -> 628 bytes assets/images/wallet_solid.png | Bin 0 -> 581 bytes cw_monero/.gitignore | 5 +- cw_monero/.metadata | 26 +- cw_monero/analysis_options.yaml | 4 + cw_monero/example/.gitignore | 44 + cw_monero/example/README.md | 16 + cw_monero/example/analysis_options.yaml | 29 + cw_monero/example/lib/main.dart | 63 ++ cw_monero/example/macos/.gitignore | 7 + .../macos/Flutter/Flutter-Debug.xcconfig | 2 + .../macos/Flutter/Flutter-Release.xcconfig | 2 + .../Flutter/GeneratedPluginRegistrant.swift | 14 + cw_monero/example/macos/Podfile | 40 + cw_monero/example/macos/Podfile.lock | 22 + .../macos/Runner.xcodeproj/project.pbxproj | 632 ++++++++++++++ .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/xcschemes/Runner.xcscheme | 87 ++ .../contents.xcworkspacedata | 10 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../example/macos/Runner/AppDelegate.swift | 9 + .../AppIcon.appiconset/Contents.json | 68 ++ .../AppIcon.appiconset/app_icon_1024.png | Bin 0 -> 102994 bytes .../AppIcon.appiconset/app_icon_128.png | Bin 0 -> 5680 bytes .../AppIcon.appiconset/app_icon_16.png | Bin 0 -> 520 bytes .../AppIcon.appiconset/app_icon_256.png | Bin 0 -> 14142 bytes .../AppIcon.appiconset/app_icon_32.png | Bin 0 -> 1066 bytes .../AppIcon.appiconset/app_icon_512.png | Bin 0 -> 36406 bytes .../AppIcon.appiconset/app_icon_64.png | Bin 0 -> 2218 bytes .../macos/Runner/Base.lproj/MainMenu.xib | 343 ++++++++ .../macos/Runner/Configs/AppInfo.xcconfig | 14 + .../macos/Runner/Configs/Debug.xcconfig | 2 + .../macos/Runner/Configs/Release.xcconfig | 2 + .../macos/Runner/Configs/Warnings.xcconfig | 13 + .../macos/Runner/DebugProfile.entitlements | 12 + cw_monero/example/macos/Runner/Info.plist | 32 + .../macos/Runner/MainFlutterWindow.swift | 15 + .../example/macos/Runner/Release.entitlements | 8 + cw_monero/example/pubspec.lock | 403 +++++++++ cw_monero/example/pubspec.yaml | 84 ++ cw_monero/example/test/widget_test.dart | 27 + cw_monero/lib/cw_monero.dart | 8 + cw_monero/lib/cw_monero_method_channel.dart | 17 + .../lib/cw_monero_platform_interface.dart | 29 + cw_monero/macos/Classes/CwMoneroPlugin.swift | 19 + cw_monero/macos/Classes/CwWalletListener.h | 23 + cw_monero/macos/Classes/monero_api.cpp | 798 ++++++++++++++++++ cw_monero/macos/Classes/monero_api.h | 38 + cw_monero/macos/cw_monero_base.podspec | 56 ++ cw_monero/pubspec.yaml | 11 +- .../test/cw_monero_method_channel_test.dart | 24 + cw_monero/test/cw_monero_test.dart | 29 + ios/CakeWallet/wakeLock.swift | 20 - ios/Runner.xcodeproj/project.pbxproj | 2 - ios/Runner/AppDelegate.swift | 5 - lib/buy/moonpay/moonpay_buy_provider.dart | 6 +- lib/buy/onramper/onramper_buy_provider.dart | 69 ++ lib/buy/payfura/payfura_buy_provider.dart | 24 + lib/di.dart | 134 ++- lib/entities/desktop_dropdown_item.dart | 9 + lib/entities/main_actions.dart | 142 ++++ lib/entities/unstoppable_domain_address.dart | 20 +- lib/entities/wake_lock.dart | 21 - .../changenow_exchange_provider.dart | 3 +- .../simpleswap_exchange_provider.dart | 3 +- .../on_authentication_state_change.dart | 4 +- .../on_wallet_sync_status_change.dart | 9 +- lib/router.dart | 50 +- lib/routes.dart | 4 + lib/src/screens/backup/backup_page.dart | 33 +- .../backup/edit_backup_password_page.dart | 3 +- lib/src/screens/base_page.dart | 25 +- lib/src/screens/buy/onramper_page.dart | 44 +- lib/src/screens/buy/payfura_page.dart | 29 +- .../screens/contact/contact_list_page.dart | 4 - lib/src/screens/dashboard/dashboard_page.dart | 312 +++---- .../dashboard/desktop_dashboard_page.dart | 111 +++ .../desktop_action_button.dart | 69 ++ .../desktop_dashboard_actions.dart | 80 ++ .../desktop_dashboard_navbar.dart | 48 ++ .../desktop_sidebar/side_menu.dart | 32 + .../desktop_sidebar/side_menu_item.dart | 50 ++ .../desktop_sidebar_wrapper.dart | 175 ++++ .../desktop_wallet_selection_dropdown.dart | 199 +++++ .../desktop_widgets/dropdown_item_widget.dart | 36 + lib/src/screens/dashboard/wallet_menu.dart | 74 -- .../screens/dashboard/wallet_menu_item.dart | 12 - .../dashboard/widgets/address_page.dart | 37 +- .../dashboard/widgets/balance_page.dart | 9 +- .../dashboard/widgets/menu_widget.dart | 151 +--- .../present_receive_option_picker.dart | 6 +- .../dashboard/widgets/transactions_page.dart | 1 + lib/src/screens/exchange/exchange_page.dart | 417 ++++----- .../exchange/exchange_template_page.dart | 190 ++--- .../exchange/widgets/currency_picker.dart | 17 +- .../desktop_exchange_cards_section.dart | 31 + .../exchange/widgets/exchange_card.dart | 207 +++-- .../mobile_exchange_cards_section.dart | 58 ++ .../ionia/auth/ionia_create_account_page.dart | 15 +- .../screens/ionia/auth/ionia_login_page.dart | 15 +- .../ionia/auth/ionia_verify_otp_page.dart | 5 +- .../ionia/cards/ionia_buy_gift_card.dart | 65 +- .../screens/new_wallet/new_wallet_page.dart | 225 +++-- .../new_wallet/new_wallet_type_page.dart | 95 +-- .../nodes/node_create_or_edit_page.dart | 1 - lib/src/screens/pin_code/pin_code_widget.dart | 357 ++++---- .../screens/receive/anonpay_invoice_page.dart | 6 +- .../screens/receive/widgets/qr_widget.dart | 74 +- .../restore/restore_from_backup_page.dart | 80 +- .../screens/restore/restore_options_page.dart | 53 +- .../screens/restore/wallet_restore_page.dart | 242 +++--- lib/src/screens/root/root.dart | 5 +- lib/src/screens/seed/pre_seed_page.dart | 77 +- lib/src/screens/seed/wallet_seed_page.dart | 196 ++--- lib/src/screens/send/send_page.dart | 321 ++++--- lib/src/screens/send/widgets/send_card.dart | 12 +- .../desktop_settings_page.dart | 105 +++ .../settings/display_settings_page.dart | 17 +- lib/src/screens/support/support_page.dart | 51 +- .../screens/wallet_list/wallet_list_page.dart | 124 ++- lib/src/screens/welcome/welcome_page.dart | 256 +++--- lib/src/widgets/add_template_button.dart | 52 ++ lib/src/widgets/address_text_field.dart | 33 +- lib/src/widgets/alert_background.dart | 11 +- lib/src/widgets/alert_close_button.dart | 5 +- lib/src/widgets/base_text_form_field.dart | 11 +- lib/src/widgets/check_box_picker.dart | 100 +-- lib/src/widgets/market_place_item.dart | 3 + lib/src/widgets/nav_bar.dart | 37 +- lib/src/widgets/picker.dart | 183 ++-- lib/src/widgets/primary_button.dart | 229 ++--- lib/src/widgets/setting_action_button.dart | 81 ++ lib/src/widgets/setting_actions.dart | 109 +++ lib/store/settings_store.dart | 6 +- lib/utils/device_info.dart | 11 + lib/utils/exception_handler.dart | 1 + lib/utils/responsive_layout_util.dart | 34 + .../dashboard/dashboard_view_model.dart | 8 - .../dashboard/desktop_sidebar_view_model.dart | 34 + .../node_list/node_list_view_model.dart | 40 +- lib/view_model/wallet_keys_view_model.dart | 102 ++- .../wallet_list/wallet_list_item.dart | 14 +- .../wallet_list/wallet_list_view_model.dart | 1 + macos/.gitignore | 7 + macos/CakeWallet/secRandom.swift | 12 + macos/Flutter/Flutter-Debug.xcconfig | 2 + macos/Flutter/Flutter-Release.xcconfig | 2 + macos/Flutter/GeneratedPluginRegistrant.swift | 36 + macos/Podfile | 40 + macos/Podfile.lock | 118 +++ macos/Runner.xcodeproj/project.pbxproj | 663 +++++++++++++++ .../xcshareddata/IDEWorkspaceChecks.plist | 8 + macos/Runner.xcodeproj/project_base.pbxproj | 636 ++++++++++++++ .../xcshareddata/xcschemes/Runner.xcscheme | 87 ++ .../contents.xcworkspacedata | 10 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/WorkspaceSettings.xcsettings | 8 + macos/Runner/AppDelegate.swift | 33 + .../AppIcon.appiconset/Contents.json | 68 ++ .../AppIcon.appiconset/app_icon_1024.png | Bin 0 -> 112502 bytes .../AppIcon.appiconset/app_icon_128.png | Bin 0 -> 7683 bytes .../AppIcon.appiconset/app_icon_16.png | Bin 0 -> 678 bytes .../AppIcon.appiconset/app_icon_256.png | Bin 0 -> 17249 bytes .../AppIcon.appiconset/app_icon_32.png | Bin 0 -> 1522 bytes .../AppIcon.appiconset/app_icon_512.png | Bin 0 -> 42348 bytes .../AppIcon.appiconset/app_icon_64.png | Bin 0 -> 3520 bytes macos/Runner/Base.lproj/MainMenu.xib | 344 ++++++++ macos/Runner/Configs/AppInfo.xcconfig | 14 + macos/Runner/Configs/Debug.xcconfig | 2 + macos/Runner/Configs/Release.xcconfig | 2 + macos/Runner/Configs/Warnings.xcconfig | 13 + macos/Runner/DebugProfileBase.entitlements | 18 + macos/Runner/InfoBase.plist | 34 + macos/Runner/MainFlutterWindow.swift | 15 + macos/Runner/ReleaseBase.entitlements | 14 + pubspec_base.yaml | 13 +- res/values/strings_ar.arb | 1 + res/values/strings_de.arb | 1 + res/values/strings_en.arb | 1 + res/values/strings_es.arb | 1 + res/values/strings_fr.arb | 1 + res/values/strings_hi.arb | 1 + res/values/strings_hr.arb | 1 + res/values/strings_it.arb | 1 + res/values/strings_ja.arb | 1 + res/values/strings_ko.arb | 1 + res/values/strings_my.arb | 1 + res/values/strings_nl.arb | 1 + res/values/strings_pl.arb | 1 + res/values/strings_pt.arb | 1 + res/values/strings_ru.arb | 1 + res/values/strings_th.arb | 1 + res/values/strings_tr.arb | 1 + res/values/strings_uk.arb | 1 + res/values/strings_zh.arb | 1 + scripts/macos/app_config.sh | 34 + scripts/macos/app_env.sh | 39 + scripts/macos/build_all.sh | 3 + scripts/macos/build_boost_arm64.sh | 4 + scripts/macos/build_boost_common.sh | 210 +++++ scripts/macos/build_boost_universal.sh | 4 + scripts/macos/build_boost_x86_64.sh | 4 + scripts/macos/build_expat.sh | 17 + scripts/macos/build_haven.sh | 50 ++ scripts/macos/build_monero.sh | 54 ++ scripts/macos/build_monero_all.sh | 20 + scripts/macos/build_openssl_arm64.sh | 4 + scripts/macos/build_openssl_common.sh | 116 +++ scripts/macos/build_openssl_universal.sh | 4 + scripts/macos/build_openssl_x86_64.sh | 4 + scripts/macos/build_sodium.sh | 16 + scripts/macos/build_unbound.sh | 23 + scripts/macos/build_zmq.sh | 17 + scripts/macos/cakewallet.sh | 4 + scripts/macos/config.sh | 13 + scripts/macos/gen_arm64.sh | 5 + scripts/macos/gen_common.sh | 31 + scripts/macos/gen_universal.sh | 5 + scripts/macos/gen_x86_64.sh | 5 + scripts/macos/setup.sh | 40 + tool/utils/secret_key.dart | 2 + 231 files changed, 9946 insertions(+), 2653 deletions(-) create mode 100644 assets/images/desktop_transactions_outline_icon.png create mode 100644 assets/images/desktop_transactions_solid_icon.png create mode 100644 assets/images/settings_outline.png create mode 100644 assets/images/support_icon.png create mode 100644 assets/images/wallet_outline.png create mode 100644 assets/images/wallet_solid.png create mode 100644 cw_monero/analysis_options.yaml create mode 100644 cw_monero/example/.gitignore create mode 100644 cw_monero/example/README.md create mode 100644 cw_monero/example/analysis_options.yaml create mode 100644 cw_monero/example/lib/main.dart create mode 100644 cw_monero/example/macos/.gitignore create mode 100644 cw_monero/example/macos/Flutter/Flutter-Debug.xcconfig create mode 100644 cw_monero/example/macos/Flutter/Flutter-Release.xcconfig create mode 100644 cw_monero/example/macos/Flutter/GeneratedPluginRegistrant.swift create mode 100644 cw_monero/example/macos/Podfile create mode 100644 cw_monero/example/macos/Podfile.lock create mode 100644 cw_monero/example/macos/Runner.xcodeproj/project.pbxproj create mode 100644 cw_monero/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 cw_monero/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme create mode 100644 cw_monero/example/macos/Runner.xcworkspace/contents.xcworkspacedata create mode 100644 cw_monero/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 cw_monero/example/macos/Runner/AppDelegate.swift create mode 100644 cw_monero/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 cw_monero/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png create mode 100644 cw_monero/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png create mode 100644 cw_monero/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png create mode 100644 cw_monero/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png create mode 100644 cw_monero/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png create mode 100644 cw_monero/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png create mode 100644 cw_monero/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png create mode 100644 cw_monero/example/macos/Runner/Base.lproj/MainMenu.xib create mode 100644 cw_monero/example/macos/Runner/Configs/AppInfo.xcconfig create mode 100644 cw_monero/example/macos/Runner/Configs/Debug.xcconfig create mode 100644 cw_monero/example/macos/Runner/Configs/Release.xcconfig create mode 100644 cw_monero/example/macos/Runner/Configs/Warnings.xcconfig create mode 100644 cw_monero/example/macos/Runner/DebugProfile.entitlements create mode 100644 cw_monero/example/macos/Runner/Info.plist create mode 100644 cw_monero/example/macos/Runner/MainFlutterWindow.swift create mode 100644 cw_monero/example/macos/Runner/Release.entitlements create mode 100644 cw_monero/example/pubspec.lock create mode 100644 cw_monero/example/pubspec.yaml create mode 100644 cw_monero/example/test/widget_test.dart create mode 100644 cw_monero/lib/cw_monero.dart create mode 100644 cw_monero/lib/cw_monero_method_channel.dart create mode 100644 cw_monero/lib/cw_monero_platform_interface.dart create mode 100644 cw_monero/macos/Classes/CwMoneroPlugin.swift create mode 100644 cw_monero/macos/Classes/CwWalletListener.h create mode 100644 cw_monero/macos/Classes/monero_api.cpp create mode 100644 cw_monero/macos/Classes/monero_api.h create mode 100644 cw_monero/macos/cw_monero_base.podspec create mode 100644 cw_monero/test/cw_monero_method_channel_test.dart create mode 100644 cw_monero/test/cw_monero_test.dart delete mode 100644 ios/CakeWallet/wakeLock.swift create mode 100644 lib/buy/onramper/onramper_buy_provider.dart create mode 100644 lib/buy/payfura/payfura_buy_provider.dart create mode 100644 lib/entities/desktop_dropdown_item.dart create mode 100644 lib/entities/main_actions.dart delete mode 100644 lib/entities/wake_lock.dart create mode 100644 lib/src/screens/dashboard/desktop_dashboard_page.dart create mode 100644 lib/src/screens/dashboard/desktop_widgets/desktop_action_button.dart create mode 100644 lib/src/screens/dashboard/desktop_widgets/desktop_dashboard_actions.dart create mode 100644 lib/src/screens/dashboard/desktop_widgets/desktop_dashboard_navbar.dart create mode 100644 lib/src/screens/dashboard/desktop_widgets/desktop_sidebar/side_menu.dart create mode 100644 lib/src/screens/dashboard/desktop_widgets/desktop_sidebar/side_menu_item.dart create mode 100644 lib/src/screens/dashboard/desktop_widgets/desktop_sidebar_wrapper.dart create mode 100644 lib/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart create mode 100644 lib/src/screens/dashboard/desktop_widgets/dropdown_item_widget.dart delete mode 100644 lib/src/screens/dashboard/wallet_menu.dart delete mode 100644 lib/src/screens/dashboard/wallet_menu_item.dart create mode 100644 lib/src/screens/exchange/widgets/desktop_exchange_cards_section.dart create mode 100644 lib/src/screens/exchange/widgets/mobile_exchange_cards_section.dart create mode 100644 lib/src/screens/settings/desktop_settings/desktop_settings_page.dart create mode 100644 lib/src/widgets/add_template_button.dart create mode 100644 lib/src/widgets/setting_action_button.dart create mode 100644 lib/src/widgets/setting_actions.dart create mode 100644 lib/utils/device_info.dart create mode 100644 lib/utils/responsive_layout_util.dart create mode 100644 lib/view_model/dashboard/desktop_sidebar_view_model.dart create mode 100644 macos/.gitignore create mode 100644 macos/CakeWallet/secRandom.swift create mode 100644 macos/Flutter/Flutter-Debug.xcconfig create mode 100644 macos/Flutter/Flutter-Release.xcconfig create mode 100644 macos/Flutter/GeneratedPluginRegistrant.swift create mode 100644 macos/Podfile create mode 100644 macos/Podfile.lock create mode 100644 macos/Runner.xcodeproj/project.pbxproj create mode 100644 macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 macos/Runner.xcodeproj/project_base.pbxproj create mode 100644 macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme create mode 100644 macos/Runner.xcworkspace/contents.xcworkspacedata create mode 100644 macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 macos/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 macos/Runner/AppDelegate.swift create mode 100644 macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png create mode 100644 macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png create mode 100644 macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png create mode 100644 macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png create mode 100644 macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png create mode 100644 macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png create mode 100644 macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png create mode 100644 macos/Runner/Base.lproj/MainMenu.xib create mode 100644 macos/Runner/Configs/AppInfo.xcconfig create mode 100644 macos/Runner/Configs/Debug.xcconfig create mode 100644 macos/Runner/Configs/Release.xcconfig create mode 100644 macos/Runner/Configs/Warnings.xcconfig create mode 100644 macos/Runner/DebugProfileBase.entitlements create mode 100644 macos/Runner/InfoBase.plist create mode 100644 macos/Runner/MainFlutterWindow.swift create mode 100644 macos/Runner/ReleaseBase.entitlements create mode 100755 scripts/macos/app_config.sh create mode 100755 scripts/macos/app_env.sh create mode 100755 scripts/macos/build_all.sh create mode 100755 scripts/macos/build_boost_arm64.sh create mode 100755 scripts/macos/build_boost_common.sh create mode 100755 scripts/macos/build_boost_universal.sh create mode 100755 scripts/macos/build_boost_x86_64.sh create mode 100755 scripts/macos/build_expat.sh create mode 100755 scripts/macos/build_haven.sh create mode 100755 scripts/macos/build_monero.sh create mode 100755 scripts/macos/build_monero_all.sh create mode 100755 scripts/macos/build_openssl_arm64.sh create mode 100755 scripts/macos/build_openssl_common.sh create mode 100755 scripts/macos/build_openssl_universal.sh create mode 100755 scripts/macos/build_openssl_x86_64.sh create mode 100755 scripts/macos/build_sodium.sh create mode 100755 scripts/macos/build_unbound.sh create mode 100755 scripts/macos/build_zmq.sh create mode 100755 scripts/macos/cakewallet.sh create mode 100755 scripts/macos/config.sh create mode 100755 scripts/macos/gen_arm64.sh create mode 100755 scripts/macos/gen_common.sh create mode 100755 scripts/macos/gen_universal.sh create mode 100755 scripts/macos/gen_x86_64.sh create mode 100755 scripts/macos/setup.sh diff --git a/.github/workflows/pr_test_build.yml b/.github/workflows/pr_test_build.yml index fc8f9b8ae..076fc2ea1 100644 --- a/.github/workflows/pr_test_build.yml +++ b/.github/workflows/pr_test_build.yml @@ -99,6 +99,7 @@ jobs: echo "const backupSalt = '${{ secrets.BACKUP_SALT }}';" >> lib/.secrets.g.dart echo "const backupKeychainSalt = '${{ secrets.BACKUP_KEY_CHAIN_SALT }}';" >> lib/.secrets.g.dart echo "const changeNowApiKey = '${{ secrets.CHANGE_NOW_API_KEY }}';" >> lib/.secrets.g.dart + echo "const changeNowApiKeyDesktop = '${{ secrets.CHANGE_NOW_API_KEY_DESKTOP }}';" >> lib/.secrets.g.dart echo "const wyreSecretKey = '${{ secrets.WYRE_SECRET_KEY }}';" >> lib/.secrets.g.dart echo "const wyreApiKey = '${{ secrets.WYRE_API_KEY }}';" >> lib/.secrets.g.dart echo "const wyreAccountId = '${{ secrets.WYRE_ACCOUNT_ID }}';" >> lib/.secrets.g.dart @@ -107,6 +108,7 @@ jobs: echo "const sideShiftAffiliateId = '${{ secrets.SIDE_SHIFT_AFFILIATE_ID }}';" >> lib/.secrets.g.dart echo "const sideShiftApiKey = '${{ secrets.SIDE_SHIFT_API_KEY }}';" >> lib/.secrets.g.dart echo "const simpleSwapApiKey = '${{ secrets.SIMPLE_SWAP_API_KEY }}';" >> lib/.secrets.g.dart + echo "const simpleSwapApiKeyDesktop = '${{ secrets.SIMPLE_SWAP_API_KEY_DESKTOP }}';" >> lib/.secrets.g.dart echo "const onramperApiKey = '${{ secrets.ONRAMPER_API_KEY }}';" >> lib/.secrets.g.dart echo "const anypayToken = '${{ secrets.ANY_PAY_TOKEN }}';" >> lib/.secrets.g.dart echo "const ioniaClientId = '${{ secrets.IONIA_CLIENT_ID }}';" >> lib/.secrets.g.dart diff --git a/.gitignore b/.gitignore index 7e3f38beb..9fb7fd204 100644 --- a/.gitignore +++ b/.gitignore @@ -135,3 +135,8 @@ assets/images/app_logo.png /pubspec.lock /android/app.properties /android/app/src/main/AndroidManifest.xml + + +macos/Runner/Info.plist +macos/Runner/DebugProfile.entitlements +macos/Runner/Release.entitlements \ No newline at end of file diff --git a/.metadata b/.metadata index e0236519d..cdddb9350 100644 --- a/.metadata +++ b/.metadata @@ -1,10 +1,30 @@ # This file tracks properties of this Flutter project. # Used by Flutter tool to assess capabilities and perform upgrades etc. # -# This file should be version controlled and should not be manually edited. +# This file should be version controlled. version: - revision: 20e59316b8b8474554b38493b8ca888794b0234a + revision: e3c29ec00c9c825c891d75054c63fcc46454dca1 channel: stable project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1 + base_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1 + - platform: macos + create_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1 + base_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/android/app/src/main/java/com/cakewallet/cake_wallet/MainActivity.java b/android/app/src/main/java/com/cakewallet/cake_wallet/MainActivity.java index 19373aab2..1c1e42df8 100644 --- a/android/app/src/main/java/com/cakewallet/cake_wallet/MainActivity.java +++ b/android/app/src/main/java/com/cakewallet/cake_wallet/MainActivity.java @@ -41,14 +41,6 @@ public class MainActivity extends FlutterFragmentActivity { try { switch (call.method) { - case "enableWakeScreen": - getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - handler.post(() -> result.success(true)); - break; - case "disableWakeScreen": - getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - handler.post(() -> result.success(true)); - break; case "sec_random": int count = call.argument("count"); SecureRandom random = new SecureRandom(); diff --git a/android/app/src/main/java/com/cakewallet/haven/MainActivity.java b/android/app/src/main/java/com/cakewallet/haven/MainActivity.java index 065af9318..8c13d1f8d 100644 --- a/android/app/src/main/java/com/cakewallet/haven/MainActivity.java +++ b/android/app/src/main/java/com/cakewallet/haven/MainActivity.java @@ -40,14 +40,6 @@ public class MainActivity extends FlutterFragmentActivity { try { switch (call.method) { - case "enableWakeScreen": - getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - handler.post(() -> result.success(true)); - break; - case "disableWakeScreen": - getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - handler.post(() -> result.success(true)); - break; case "sec_random": int count = call.argument("count"); SecureRandom random = new SecureRandom(); diff --git a/android/app/src/main/java/com/monero/app/MainActivity.java b/android/app/src/main/java/com/monero/app/MainActivity.java index 385932b38..f9e4f0882 100644 --- a/android/app/src/main/java/com/monero/app/MainActivity.java +++ b/android/app/src/main/java/com/monero/app/MainActivity.java @@ -40,14 +40,6 @@ public class MainActivity extends FlutterFragmentActivity { try { switch (call.method) { - case "enableWakeScreen": - getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - handler.post(() -> result.success(true)); - break; - case "disableWakeScreen": - getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - handler.post(() -> result.success(true)); - break; case "sec_random": int count = call.argument("count"); SecureRandom random = new SecureRandom(); diff --git a/assets/images/desktop_transactions_outline_icon.png b/assets/images/desktop_transactions_outline_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..38fc3886664e5e6180383934c43a8676d2770eeb GIT binary patch literal 663 zcmV;I0%-k-P)kBO)7O z^5f5MbMWqkAD$9e-}zQ^96N#$NI4S02BRD2fEAb7cVsP~8F=bc`=5gGje@_YF4RC!VIL}2aySP!V4+?;Bti4p-3 zscBd;w=RS9fE*d4c{~ECyTcKc01@^uM7cozfyzO# z_A3S1@%E};rxMWlu-~%suRw{|T7A&V<_im?cZa*E_omjnzT4dW`b2O4>$d+Yvoo|H zDwU~+xIS8%R%^Q6=nh|^+8w54%xz5D!itco$nZ&X_#N%xnF1EjA8S*9g8nEvF%yd$ z7=S4Z%p`x%;|DtuQUuxT@|HyWyO;Ck&rT2f(>pPWfa zJNR6%d%YU*_`Nfw-n+c!idh6&U;?{+>>M_VD|=(E4;lkR$OW_4&J-4MfMV0aoa@^I kY!IzdV$DO-9T;ij1priDa{-xuivR!s07*qoM6N<$f_A~1=l}o! literal 0 HcmV?d00001 diff --git a/assets/images/settings_outline.png b/assets/images/settings_outline.png new file mode 100644 index 0000000000000000000000000000000000000000..3230f00c45cdb4a258fe38fc67edfb5fe61c1fd9 GIT binary patch literal 871 zcmV-t1DO1YP)NcZr-hDYJSr$5+#51u;(>6fSs(0k^Ylk04Ce&Rg$&A1T~T^h;#T$se5~cP>-Cl*PyXbo{R(3J>L#BoHGLVh_svqZqY5-%aERrA6ECL z`D+lkFFSqyRy==oFmpZ!h*BSyYcVbl>$yl+dN0`~Bx(|>h_4p=j01?Uq4~fCt2Z|e zVkCs{iU`k=)HSM8EhIWqO4>6L;a{4Bk+#HF3@Dia^2tfUGkZiGxJ?K~um6k`vQ*Tc z;2F_yf=H#P$dRmU9nBNd2B>#q1a3KC^lRcHc^0q_kK5}~;Bh~@=`2>bpiEPVkj^4h zyk?4`dJGCTM2l-S|4UtsNunwVPiuVv8S-JlIGSVE9V8VitZ`t1ABehh0LFV!7*x zqFs{~*IF3wkc;{7%B{xhKQr=DvT<&wieL?nIM&pU>Z{DIWF)54UMV xptZ|L(m=3b4f*%gWMKAyW002ovPDHLkV1jsFlE44} literal 0 HcmV?d00001 diff --git a/assets/images/support_icon.png b/assets/images/support_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..0d6ec99fd1ef8fe39f50ce854500e89cc2c4fb26 GIT binary patch literal 819 zcmV-31I+x1P)wSQf5ttS2y@z~u(` zWQ1he?oG0!l1)OiEV^nlliA+>rvJVM_|Jlq6rR61%Uc!}SoJ<@yU8KpC4l*QeTDVc z&x-M10qni~R6xrv13{sC6ca+6>TlB0DFE| zMgy9k%E3-i!xVA@g2Ca|iZ=wHIhk!seH&#w$%)Es0+K_9PL8%$>q9!np99X#CWm$T zXr~y-nd-f4*~;f7&N43GsO)pz7C7lH8>#V)3xj$RQn7(n5-vyqp1ulQOsd5UB`E<{ zxWUd|TI$!LrJ4_9&*4}fx(MfsjHycHNqMILIh!8KYAF?E0dU>djWHE5&}_~+eBcn7 zpINH5eHF;cUKW!7T|btHu%W$`IP0TsJflt?u*LwfZK)tjS1z&tK8%&(#)Pg1gyHFwCW>Mz;iz1s09-*0 z?uhl0Uvq*noqM>o8cZ@S70H~HXR4_tVDi%@ie#N{&6LJ{y#FrzAR2c63o|%&_^s>HWB3m41S-6ael3CWQ_0G~Nl3EuA ztTO6s7sGIr-?yd@MDJQ5oL66XK18$JqfTEJ9J931usS-XZN%44BH+xwbLImH z{hR;W?%a6tw%pkM+f!C{3;Wq!(OM>+z5FQq($+a zEewe0w|vQ4q}idQJAA({@98Anm@CuLMj<=_wY8R<2baJMN(5@`bUX9~tK37+zUB`A O0000TM^696XwPC$_OmD~W+AU*|K5J9;T28Dok zm`&ouaqI*+lzENU2*_8FVHOye6f*nNkccSgX}jFreY?LB zkp#*4vmaRhr&uF6H-UN$*ahMpZK~<9T>hCnN)v4LV&`TMsgIc1RAE}ON z_tZ~J8kCHL5o(551mu8CA<9xaseAS<+&0LYNT!#c18yjj9W2_B4a=@O>G6DHGP7VA zjORe{Rxa6Ak|z5d1mx$NsA7?gH2l8%@%>r$Zf^G;k04#pjGOEq;Cwmb9$`y^ANbJ( zk`0ane>W`x{U7fm+MkNQJe-yMfzZEODx+_yvi!nD0l5-fars_zuA_7qq9=X00000NkvXXu0mjfLBjiN literal 0 HcmV?d00001 diff --git a/cw_monero/.gitignore b/cw_monero/.gitignore index c8bb78494..ebb19df82 100644 --- a/cw_monero/.gitignore +++ b/cw_monero/.gitignore @@ -8,4 +8,7 @@ build/ ios/External/ android/.externalNativeBuild/ -android/.cxx/ \ No newline at end of file +android/.cxx/ + +macos/cw_monero.podspec +macos/External/ \ No newline at end of file diff --git a/cw_monero/.metadata b/cw_monero/.metadata index 36ba765ff..46a2f7f6f 100644 --- a/cw_monero/.metadata +++ b/cw_monero/.metadata @@ -1,10 +1,30 @@ # This file tracks properties of this Flutter project. # Used by Flutter tool to assess capabilities and perform upgrades etc. # -# This file should be version controlled and should not be manually edited. +# This file should be version controlled. version: - revision: 798e4272a2e43d7daab75f225a13442e384ee0cd - channel: dev + revision: e3c29ec00c9c825c891d75054c63fcc46454dca1 + channel: stable project_type: plugin + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1 + base_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1 + - platform: macos + create_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1 + base_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/cw_monero/analysis_options.yaml b/cw_monero/analysis_options.yaml new file mode 100644 index 000000000..a5744c1cf --- /dev/null +++ b/cw_monero/analysis_options.yaml @@ -0,0 +1,4 @@ +include: package:flutter_lints/flutter.yaml + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/cw_monero/example/.gitignore b/cw_monero/example/.gitignore new file mode 100644 index 000000000..24476c5d1 --- /dev/null +++ b/cw_monero/example/.gitignore @@ -0,0 +1,44 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/cw_monero/example/README.md b/cw_monero/example/README.md new file mode 100644 index 000000000..18cf6d109 --- /dev/null +++ b/cw_monero/example/README.md @@ -0,0 +1,16 @@ +# cw_monero_example + +Demonstrates how to use the cw_monero plugin. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) + +For help getting started with Flutter development, view the +[online documentation](https://docs.flutter.dev/), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/cw_monero/example/analysis_options.yaml b/cw_monero/example/analysis_options.yaml new file mode 100644 index 000000000..61b6c4de1 --- /dev/null +++ b/cw_monero/example/analysis_options.yaml @@ -0,0 +1,29 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at + # https://dart-lang.github.io/linter/lints/index.html. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/cw_monero/example/lib/main.dart b/cw_monero/example/lib/main.dart new file mode 100644 index 000000000..e4374f097 --- /dev/null +++ b/cw_monero/example/lib/main.dart @@ -0,0 +1,63 @@ +import 'package:flutter/material.dart'; +import 'dart:async'; + +import 'package:flutter/services.dart'; +import 'package:cw_monero/cw_monero.dart'; + +void main() { + runApp(const MyApp()); +} + +class MyApp extends StatefulWidget { + const MyApp({super.key}); + + @override + State createState() => _MyAppState(); +} + +class _MyAppState extends State { + String _platformVersion = 'Unknown'; + final _cwMoneroPlugin = CwMonero(); + + @override + void initState() { + super.initState(); + initPlatformState(); + } + + // Platform messages are asynchronous, so we initialize in an async method. + Future initPlatformState() async { + String platformVersion; + // Platform messages may fail, so we use a try/catch PlatformException. + // We also handle the message potentially returning null. + try { + platformVersion = + await _cwMoneroPlugin.getPlatformVersion() ?? 'Unknown platform version'; + } on PlatformException { + platformVersion = 'Failed to get platform version.'; + } + + // If the widget was removed from the tree while the asynchronous platform + // message was in flight, we want to discard the reply rather than calling + // setState to update our non-existent appearance. + if (!mounted) return; + + setState(() { + _platformVersion = platformVersion; + }); + } + + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + appBar: AppBar( + title: const Text('Plugin example app'), + ), + body: Center( + child: Text('Running on: $_platformVersion\n'), + ), + ), + ); + } +} diff --git a/cw_monero/example/macos/.gitignore b/cw_monero/example/macos/.gitignore new file mode 100644 index 000000000..746adbb6b --- /dev/null +++ b/cw_monero/example/macos/.gitignore @@ -0,0 +1,7 @@ +# Flutter-related +**/Flutter/ephemeral/ +**/Pods/ + +# Xcode-related +**/dgph +**/xcuserdata/ diff --git a/cw_monero/example/macos/Flutter/Flutter-Debug.xcconfig b/cw_monero/example/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 000000000..4b81f9b2d --- /dev/null +++ b/cw_monero/example/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/cw_monero/example/macos/Flutter/Flutter-Release.xcconfig b/cw_monero/example/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 000000000..5caa9d157 --- /dev/null +++ b/cw_monero/example/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/cw_monero/example/macos/Flutter/GeneratedPluginRegistrant.swift b/cw_monero/example/macos/Flutter/GeneratedPluginRegistrant.swift new file mode 100644 index 000000000..e25d64097 --- /dev/null +++ b/cw_monero/example/macos/Flutter/GeneratedPluginRegistrant.swift @@ -0,0 +1,14 @@ +// +// Generated file. Do not edit. +// + +import FlutterMacOS +import Foundation + +import cw_monero +import path_provider_foundation + +func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + CwMoneroPlugin.register(with: registry.registrar(forPlugin: "CwMoneroPlugin")) + PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) +} diff --git a/cw_monero/example/macos/Podfile b/cw_monero/example/macos/Podfile new file mode 100644 index 000000000..dade8dfad --- /dev/null +++ b/cw_monero/example/macos/Podfile @@ -0,0 +1,40 @@ +platform :osx, '10.11' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_macos_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_macos_build_settings(target) + end +end diff --git a/cw_monero/example/macos/Podfile.lock b/cw_monero/example/macos/Podfile.lock new file mode 100644 index 000000000..692176b30 --- /dev/null +++ b/cw_monero/example/macos/Podfile.lock @@ -0,0 +1,22 @@ +PODS: + - FlutterMacOS (1.0.0) + - path_provider_macos (0.0.1): + - FlutterMacOS + +DEPENDENCIES: + - FlutterMacOS (from `Flutter/ephemeral`) + - path_provider_macos (from `Flutter/ephemeral/.symlinks/plugins/path_provider_macos/macos`) + +EXTERNAL SOURCES: + FlutterMacOS: + :path: Flutter/ephemeral + path_provider_macos: + :path: Flutter/ephemeral/.symlinks/plugins/path_provider_macos/macos + +SPEC CHECKSUMS: + FlutterMacOS: ae6af50a8ea7d6103d888583d46bd8328a7e9811 + path_provider_macos: 3c0c3b4b0d4a76d2bf989a913c2de869c5641a19 + +PODFILE CHECKSUM: 6eac6b3292e5142cfc23bdeb71848a40ec51c14c + +COCOAPODS: 1.11.2 diff --git a/cw_monero/example/macos/Runner.xcodeproj/project.pbxproj b/cw_monero/example/macos/Runner.xcodeproj/project.pbxproj new file mode 100644 index 000000000..472859e8c --- /dev/null +++ b/cw_monero/example/macos/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,632 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 51; + objects = { + +/* Begin PBXAggregateTarget section */ + 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; + buildPhases = ( + 33CC111E2044C6BF0003C045 /* ShellScript */, + ); + dependencies = ( + ); + name = "Flutter Assemble"; + productName = FLX; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; + 428E7496E2068D0AB138F295 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C29B2253BA962B7A415DBA77 /* Pods_Runner.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC111A2044C6BA0003C045; + remoteInfo = FLX; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 33CC110E2044A8840003C045 /* Bundle Framework */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Bundle Framework"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; + 33CC10ED2044A3C60003C045 /* cw_monero_example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = cw_monero_example.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; + 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; + 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; + 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; + 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; + A9CDA1605413332AB9056C23 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + C29B2253BA962B7A415DBA77 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + E434913D71DC2682EF8E9059 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + EEF09839C86335F78056F812 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 428E7496E2068D0AB138F295 /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 33BA886A226E78AF003329D5 /* Configs */ = { + isa = PBXGroup; + children = ( + 33E5194F232828860026EE4D /* AppInfo.xcconfig */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, + ); + path = Configs; + sourceTree = ""; + }; + 33CC10E42044A3C60003C045 = { + isa = PBXGroup; + children = ( + 33FAB671232836740065AC1E /* Runner */, + 33CEB47122A05771004F2AC0 /* Flutter */, + 33CC10EE2044A3C60003C045 /* Products */, + D73912EC22F37F3D000D13A0 /* Frameworks */, + 77870A4C94A9AB6EEC2EE261 /* Pods */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* cw_monero_example.app */, + ); + name = Products; + sourceTree = ""; + }; + 33CC11242044D66E0003C045 /* Resources */ = { + isa = PBXGroup; + children = ( + 33CC10F22044A3C60003C045 /* Assets.xcassets */, + 33CC10F42044A3C60003C045 /* MainMenu.xib */, + 33CC10F72044A3C60003C045 /* Info.plist */, + ); + name = Resources; + path = ..; + sourceTree = ""; + }; + 33CEB47122A05771004F2AC0 /* Flutter */ = { + isa = PBXGroup; + children = ( + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, + ); + path = Flutter; + sourceTree = ""; + }; + 33FAB671232836740065AC1E /* Runner */ = { + isa = PBXGroup; + children = ( + 33CC10F02044A3C60003C045 /* AppDelegate.swift */, + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, + 33E51913231747F40026EE4D /* DebugProfile.entitlements */, + 33E51914231749380026EE4D /* Release.entitlements */, + 33CC11242044D66E0003C045 /* Resources */, + 33BA886A226E78AF003329D5 /* Configs */, + ); + path = Runner; + sourceTree = ""; + }; + 77870A4C94A9AB6EEC2EE261 /* Pods */ = { + isa = PBXGroup; + children = ( + EEF09839C86335F78056F812 /* Pods-Runner.debug.xcconfig */, + A9CDA1605413332AB9056C23 /* Pods-Runner.release.xcconfig */, + E434913D71DC2682EF8E9059 /* Pods-Runner.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; + D73912EC22F37F3D000D13A0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + C29B2253BA962B7A415DBA77 /* Pods_Runner.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 33CC10EC2044A3C60003C045 /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 0A239C1738C005E3F6E4DFC6 /* [CP] Check Pods Manifest.lock */, + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + 0CEAA82AE8A029C31B39F234 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* cw_monero_example.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 33CC10E52044A3C60003C045 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 1300; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 33CC10EC2044A3C60003C045 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.Sandbox = { + enabled = 1; + }; + }; + }; + 33CC111A2044C6BA0003C045 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 33CC10E42044A3C60003C045; + productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 33CC10EC2044A3C60003C045 /* Runner */, + 33CC111A2044C6BA0003C045 /* Flutter Assemble */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 33CC10EB2044A3C60003C045 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 0A239C1738C005E3F6E4DFC6 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 0CEAA82AE8A029C31B39F234 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 3399D490228B24CF009A79C7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; + }; + 33CC111E2044C6BF0003C045 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + Flutter/ephemeral/FlutterInputs.xcfilelist, + ); + inputPaths = ( + Flutter/ephemeral/tripwire, + ); + outputFileListPaths = ( + Flutter/ephemeral/FlutterOutputs.xcfilelist, + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 33CC10E92044A3C60003C045 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; + targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 33CC10F52044A3C60003C045 /* Base */, + ); + name = MainMenu.xib; + path = Runner; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 338D0CE9231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Profile; + }; + 338D0CEA231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Profile; + }; + 338D0CEB231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + 33CC10F92044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 33CC10FA2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 33CC10FC2044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 33CC10FD2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 33CC111C2044C6BA0003C045 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 33CC111D2044C6BA0003C045 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10F92044A3C60003C045 /* Debug */, + 33CC10FA2044A3C60003C045 /* Release */, + 338D0CE9231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10FC2044A3C60003C045 /* Debug */, + 33CC10FD2044A3C60003C045 /* Release */, + 338D0CEA231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC111C2044C6BA0003C045 /* Debug */, + 33CC111D2044C6BA0003C045 /* Release */, + 338D0CEB231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/cw_monero/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/cw_monero/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/cw_monero/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/cw_monero/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/cw_monero/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 000000000..4e44b7ced --- /dev/null +++ b/cw_monero/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cw_monero/example/macos/Runner.xcworkspace/contents.xcworkspacedata b/cw_monero/example/macos/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..21a3cc14c --- /dev/null +++ b/cw_monero/example/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/cw_monero/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/cw_monero/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/cw_monero/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/cw_monero/example/macos/Runner/AppDelegate.swift b/cw_monero/example/macos/Runner/AppDelegate.swift new file mode 100644 index 000000000..d53ef6437 --- /dev/null +++ b/cw_monero/example/macos/Runner/AppDelegate.swift @@ -0,0 +1,9 @@ +import Cocoa +import FlutterMacOS + +@NSApplicationMain +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } +} diff --git a/cw_monero/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/cw_monero/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000..a2ec33f19 --- /dev/null +++ b/cw_monero/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_16.png", + "scale" : "1x" + }, + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "2x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "1x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_64.png", + "scale" : "2x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_128.png", + "scale" : "1x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "2x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "1x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "1x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_1024.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/cw_monero/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/cw_monero/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png new file mode 100644 index 0000000000000000000000000000000000000000..82b6f9d9a33e198f5747104729e1fcef999772a5 GIT binary patch literal 102994 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ⅅ8MglG&ER{i5lN315cO?EdHNLrg? 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 z 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 Pe7HEdAEg|=c=LY&YVNkY literal 0 HcmV?d00001 diff --git a/cw_monero/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/cw_monero/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png new file mode 100644 index 0000000000000000000000000000000000000000..13b35eba55c6dabc3aac36f33d859266c18fa0d0 GIT binary patch literal 5680 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 literal 0 HcmV?d00001 diff --git a/cw_monero/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/cw_monero/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png new file mode 100644 index 0000000000000000000000000000000000000000..0a3f5fa40fb3d1e0710331a48de5d256da3f275d GIT binary patch literal 520 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 literal 0 HcmV?d00001 diff --git a/cw_monero/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/cw_monero/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png new file mode 100644 index 0000000000000000000000000000000000000000..2f1632cfddf3d9dade342351e627a0a75609fb46 GIT binary patch literal 2218 zcmV;b2vzrqP)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91K%fHv1ONa40RR91KmY&$07g+lumAuE6iGxuRCodHTWf3-RTMruyW6Fu zQYeUM04eX6D5c0FCjKKPrco1(K`<0SL=crI{PC3-^hZU0kQie$gh-5!7z6SH6Q0J% zqot*`H1q{R5fHFYS}dje@;kG=v$L0(yY0?wY2%*c?A&{2?!D*x?m71{of2gv!$5|C z3>qG_BW}7K_yUcT3A5C6QD<+{aq?x;MAUyAiJn#Jv8_zZtQ{P zTRzbL3U9!qVuZzS$xKU10KiW~Bgdcv1-!uAhQxf3a7q+dU6lj?yoO4Lq4TUN4}h{N z*fIM=SS8|C2$(T>w$`t@3Tka!(r!7W`x z-isCVgQD^mG-MJ;XtJuK3V{Vy72GQ83KRWsHU?e*wrhKk=ApIYeDqLi;JI1e zuvv}5^Dc=k7F7?nm3nIw$NVmU-+R>> zyqOR$-2SDpJ}Pt;^RkJytDVXNTsu|mI1`~G7yw`EJR?VkGfNdqK9^^8P`JdtTV&tX4CNcV4 z&N06nZa??Fw1AgQOUSE2AmPE@WO(Fvo`%m`cDgiv(fAeRA%3AGXUbsGw{7Q`cY;1BI#ac3iN$$Hw z0LT0;xc%=q)me?Y*$xI@GRAw?+}>=9D+KTk??-HJ4=A>`V&vKFS75@MKdSF1JTq{S zc1!^8?YA|t+uKigaq!sT;Z!&0F2=k7F0PIU;F$leJLaw2UI6FL^w}OG&!;+b%ya1c z1n+6-inU<0VM-Y_s5iTElq)ThyF?StVcebpGI znw#+zLx2@ah{$_2jn+@}(zJZ{+}_N9BM;z)0yr|gF-4=Iyu@hI*Lk=-A8f#bAzc9f z`Kd6K--x@t04swJVC3JK1cHY-Hq+=|PN-VO;?^_C#;coU6TDP7Bt`;{JTG;!+jj(` zw5cLQ-(Cz-Tlb`A^w7|R56Ce;Wmr0)$KWOUZ6ai0PhzPeHwdl0H(etP zUV`va_i0s-4#DkNM8lUlqI7>YQLf)(lz9Q3Uw`)nc(z3{m5ZE77Ul$V%m)E}3&8L0 z-XaU|eB~Is08eORPk;=<>!1w)Kf}FOVS2l&9~A+@R#koFJ$Czd%Y(ENTV&A~U(IPI z;UY+gf+&6ioZ=roly<0Yst8ck>(M=S?B-ys3mLdM&)ex!hbt+ol|T6CTS+Sc0jv(& z7ijdvFwBq;0a{%3GGwkDKTeG`b+lyj0jjS1OMkYnepCdoosNY`*zmBIo*981BU%%U z@~$z0V`OVtIbEx5pa|Tct|Lg#ZQf5OYMUMRD>Wdxm5SAqV2}3!ceE-M2 z@O~lQ0OiKQp}o9I;?uxCgYVV?FH|?Riri*U$Zi_`V2eiA>l zdSm6;SEm6#T+SpcE8Ro_f2AwxzI z44hfe^WE3!h@W3RDyA_H440cpmYkv*)6m1XazTqw%=E5Xv7^@^^T7Q2wxr+Z2kVYr + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cw_monero/example/macos/Runner/Configs/AppInfo.xcconfig b/cw_monero/example/macos/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 000000000..a80a25602 --- /dev/null +++ b/cw_monero/example/macos/Runner/Configs/AppInfo.xcconfig @@ -0,0 +1,14 @@ +// Application-level settings for the Runner target. +// +// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the +// future. If not, the values below would default to using the project name when this becomes a +// 'flutter create' template. + +// The application's name. By default this is also the title of the Flutter window. +PRODUCT_NAME = cw_monero_example + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = com.cakewallet.cwMoneroExample + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright © 2022 com.cakewallet. All rights reserved. diff --git a/cw_monero/example/macos/Runner/Configs/Debug.xcconfig b/cw_monero/example/macos/Runner/Configs/Debug.xcconfig new file mode 100644 index 000000000..36b0fd946 --- /dev/null +++ b/cw_monero/example/macos/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/cw_monero/example/macos/Runner/Configs/Release.xcconfig b/cw_monero/example/macos/Runner/Configs/Release.xcconfig new file mode 100644 index 000000000..dff4f4956 --- /dev/null +++ b/cw_monero/example/macos/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/cw_monero/example/macos/Runner/Configs/Warnings.xcconfig b/cw_monero/example/macos/Runner/Configs/Warnings.xcconfig new file mode 100644 index 000000000..42bcbf478 --- /dev/null +++ b/cw_monero/example/macos/Runner/Configs/Warnings.xcconfig @@ -0,0 +1,13 @@ +WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings +GCC_WARN_UNDECLARED_SELECTOR = YES +CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_PRAGMA_PACK = YES +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_COMMA = YES +GCC_WARN_STRICT_SELECTOR_MATCH = YES +CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +GCC_WARN_SHADOW = YES +CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/cw_monero/example/macos/Runner/DebugProfile.entitlements b/cw_monero/example/macos/Runner/DebugProfile.entitlements new file mode 100644 index 000000000..dddb8a30c --- /dev/null +++ b/cw_monero/example/macos/Runner/DebugProfile.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.server + + + diff --git a/cw_monero/example/macos/Runner/Info.plist b/cw_monero/example/macos/Runner/Info.plist new file mode 100644 index 000000000..4789daa6a --- /dev/null +++ b/cw_monero/example/macos/Runner/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/cw_monero/example/macos/Runner/MainFlutterWindow.swift b/cw_monero/example/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 000000000..2722837ec --- /dev/null +++ b/cw_monero/example/macos/Runner/MainFlutterWindow.swift @@ -0,0 +1,15 @@ +import Cocoa +import FlutterMacOS + +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController.init() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } +} diff --git a/cw_monero/example/macos/Runner/Release.entitlements b/cw_monero/example/macos/Runner/Release.entitlements new file mode 100644 index 000000000..852fa1a47 --- /dev/null +++ b/cw_monero/example/macos/Runner/Release.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.security.app-sandbox + + + diff --git a/cw_monero/example/pubspec.lock b/cw_monero/example/pubspec.lock new file mode 100644 index 000000000..772ff47bd --- /dev/null +++ b/cw_monero/example/pubspec.lock @@ -0,0 +1,403 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + args: + dependency: transitive + description: + name: args + sha256: "139d809800a412ebb26a3892da228b2d0ba36f0ef5d9a82166e5e52ec8d61611" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + asn1lib: + dependency: transitive + description: + name: asn1lib + sha256: ab96a1cb3beeccf8145c52e449233fe68364c9641623acd3adad66f8184f1039 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + async: + dependency: transitive + description: + name: async + sha256: bfe67ef28df125b7dddcea62755991f807aa39a2492a23e1550161692950bbe0 + url: "https://pub.dev" + source: hosted + version: "2.10.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + characters: + dependency: transitive + description: + name: characters + sha256: e6a326c8af69605aec75ed6c187d06b349707a27fbff8222ca9cc2cff167975c + url: "https://pub.dev" + source: hosted + version: "1.2.1" + clock: + dependency: transitive + description: + name: clock + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.dev" + source: hosted + version: "1.1.1" + collection: + dependency: transitive + description: + name: collection + sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0 + url: "https://pub.dev" + source: hosted + version: "1.17.0" + convert: + dependency: transitive + description: + name: convert + sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" + url: "https://pub.dev" + source: hosted + version: "3.1.1" + crypto: + dependency: transitive + description: + name: crypto + sha256: aa274aa7774f8964e4f4f38cc994db7b6158dd36e9187aaceaddc994b35c6c67 + url: "https://pub.dev" + source: hosted + version: "3.0.2" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + sha256: e35129dc44c9118cee2a5603506d823bab99c68393879edb440e0090d07586be + url: "https://pub.dev" + source: hosted + version: "1.0.5" + cw_core: + dependency: transitive + description: + path: "../../cw_core" + relative: true + source: path + version: "0.0.1" + cw_monero: + dependency: "direct main" + description: + path: ".." + relative: true + source: path + version: "0.0.1" + encrypt: + dependency: transitive + description: + name: encrypt + sha256: "4fd4e4fdc21b9d7d4141823e1e6515cd94e7b8d84749504c232999fba25d9bbb" + url: "https://pub.dev" + source: hosted + version: "5.0.1" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + url: "https://pub.dev" + source: hosted + version: "1.3.1" + ffi: + dependency: transitive + description: + name: ffi + sha256: "13a6ccf6a459a125b3fcdb6ec73bd5ff90822e071207c663bfd1f70062d51d18" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + file: + dependency: transitive + description: + name: file + sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d" + url: "https://pub.dev" + source: hosted + version: "6.1.4" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c + url: "https://pub.dev" + source: hosted + version: "2.0.1" + flutter_mobx: + dependency: transitive + description: + name: flutter_mobx + sha256: "0da4add0016387a7bf309a0d0c41d36c6b3ae25ed7a176409267f166509e723e" + url: "https://pub.dev" + source: hosted + version: "2.0.6+5" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + http: + dependency: transitive + description: + name: http + sha256: "6aa2946395183537c8b880962d935877325d6a09a2867c3970c05c0fed6ac482" + url: "https://pub.dev" + source: hosted + version: "0.13.5" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + url: "https://pub.dev" + source: hosted + version: "4.0.2" + intl: + dependency: transitive + description: + name: intl + sha256: "910f85bce16fb5c6f614e117efa303e85a1731bb0081edf3604a2ae6e9a3cc91" + url: "https://pub.dev" + source: hosted + version: "0.17.0" + js: + dependency: transitive + description: + name: js + sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7" + url: "https://pub.dev" + source: hosted + version: "0.6.5" + lints: + dependency: transitive + description: + name: lints + sha256: "5e4a9cd06d447758280a8ac2405101e0e2094d2a1dbdd3756aec3fe7775ba593" + url: "https://pub.dev" + source: hosted + version: "2.0.1" + matcher: + dependency: transitive + description: + name: matcher + sha256: "16db949ceee371e9b99d22f88fa3a73c4e59fd0afed0bd25fc336eb76c198b72" + url: "https://pub.dev" + source: hosted + version: "0.12.13" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + url: "https://pub.dev" + source: hosted + version: "0.2.0" + meta: + dependency: transitive + description: + name: meta + sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42" + url: "https://pub.dev" + source: hosted + version: "1.8.0" + mobx: + dependency: transitive + description: + name: mobx + sha256: f1862bd92c6a903fab67338f27e2f731117c3cb9ea37cee1a487f9e4e0de314a + url: "https://pub.dev" + source: hosted + version: "2.1.3+1" + path: + dependency: transitive + description: + name: path + sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b + url: "https://pub.dev" + source: hosted + version: "1.8.2" + path_provider: + dependency: transitive + description: + name: path_provider + sha256: dcea5feb97d8abf90cab9e9030b497fb7c3cbf26b7a1fe9e3ef7dcb0a1ddec95 + url: "https://pub.dev" + source: hosted + version: "2.0.12" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: a776c088d671b27f6e3aa8881d64b87b3e80201c64e8869b811325de7a76c15e + url: "https://pub.dev" + source: hosted + version: "2.0.22" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "62a68e7e1c6c459f9289859e2fae58290c981ce21d1697faf54910fe1faa4c74" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: ab0987bf95bc591da42dffb38c77398fc43309f0b9b894dcc5d6f40c4b26c379 + url: "https://pub.dev" + source: hosted + version: "2.1.7" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: f0abc8ebd7253741f05488b4813d936b4d07c6bae3e86148a09e342ee4b08e76 + url: "https://pub.dev" + source: hosted + version: "2.0.5" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: a34ecd7fb548f8e57321fd8e50d865d266941b54e6c3b7758cf8f37c24116905 + url: "https://pub.dev" + source: hosted + version: "2.0.7" + platform: + dependency: transitive + description: + name: platform + sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76" + url: "https://pub.dev" + source: hosted + version: "3.1.0" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: dbf0f707c78beedc9200146ad3cb0ab4d5da13c246336987be6940f026500d3a + url: "https://pub.dev" + source: hosted + version: "2.1.3" + pointycastle: + dependency: transitive + description: + name: pointycastle + sha256: db7306cf0249f838d1a24af52b5a5887c5bf7f31d8bb4e827d071dc0939ad346 + url: "https://pub.dev" + source: hosted + version: "3.6.2" + process: + dependency: transitive + description: + name: process + sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09" + url: "https://pub.dev" + source: hosted + version: "4.2.4" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_span: + dependency: transitive + description: + name: source_span + sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 + url: "https://pub.dev" + source: hosted + version: "1.9.1" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + url: "https://pub.dev" + source: hosted + version: "1.11.0" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + test_api: + dependency: transitive + description: + name: test_api + sha256: ad540f65f92caa91bf21dfc8ffb8c589d6e4dc0c2267818b4cc2792857706206 + url: "https://pub.dev" + source: hosted + version: "0.4.16" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: "26f87ade979c47a150c9eaab93ccd2bebe70a27dc0b4b29517f2904f04eb11a5" + url: "https://pub.dev" + source: hosted + version: "1.3.1" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + win32: + dependency: transitive + description: + name: win32 + sha256: c0e3a4f7be7dae51d8f152230b86627e3397c1ba8c3fa58e63d44a9f3edc9cef + url: "https://pub.dev" + source: hosted + version: "2.6.1" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: bd512f03919aac5f1313eb8249f223bacf4927031bf60b02601f81f687689e86 + url: "https://pub.dev" + source: hosted + version: "0.2.0+3" +sdks: + dart: ">=2.18.1 <3.0.0" + flutter: ">=3.0.0" diff --git a/cw_monero/example/pubspec.yaml b/cw_monero/example/pubspec.yaml new file mode 100644 index 000000000..2dee5337f --- /dev/null +++ b/cw_monero/example/pubspec.yaml @@ -0,0 +1,84 @@ +name: cw_monero_example +description: Demonstrates how to use the cw_monero plugin. + +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +environment: + sdk: '>=2.18.1 <3.0.0' + +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. +dependencies: + flutter: + sdk: flutter + + cw_monero: + # When depending on this package from a real application you should use: + # cw_monero: ^x.y.z + # See https://dart.dev/tools/pub/dependencies#version-constraints + # The example app is bundled with the plugin so we use a path dependency on + # the parent directory to use the current plugin's version. + path: ../ + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.2 + +dev_dependencies: + flutter_test: + sdk: flutter + + # The "flutter_lints" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + flutter_lints: ^2.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/assets-and-images/#from-packages + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/custom-fonts/#from-packages diff --git a/cw_monero/example/test/widget_test.dart b/cw_monero/example/test/widget_test.dart new file mode 100644 index 000000000..b37e6313d --- /dev/null +++ b/cw_monero/example/test/widget_test.dart @@ -0,0 +1,27 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility in the flutter_test package. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:cw_monero_example/main.dart'; + +void main() { + testWidgets('Verify Platform version', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(const MyApp()); + + // Verify that platform version is retrieved. + expect( + find.byWidgetPredicate( + (Widget widget) => widget is Text && + widget.data!.startsWith('Running on:'), + ), + findsOneWidget, + ); + }); +} diff --git a/cw_monero/lib/cw_monero.dart b/cw_monero/lib/cw_monero.dart new file mode 100644 index 000000000..7945a020e --- /dev/null +++ b/cw_monero/lib/cw_monero.dart @@ -0,0 +1,8 @@ + +import 'cw_monero_platform_interface.dart'; + +class CwMonero { + Future getPlatformVersion() { + return CwMoneroPlatform.instance.getPlatformVersion(); + } +} diff --git a/cw_monero/lib/cw_monero_method_channel.dart b/cw_monero/lib/cw_monero_method_channel.dart new file mode 100644 index 000000000..1cbca9f2c --- /dev/null +++ b/cw_monero/lib/cw_monero_method_channel.dart @@ -0,0 +1,17 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; + +import 'cw_monero_platform_interface.dart'; + +/// An implementation of [CwMoneroPlatform] that uses method channels. +class MethodChannelCwMonero extends CwMoneroPlatform { + /// The method channel used to interact with the native platform. + @visibleForTesting + final methodChannel = const MethodChannel('cw_monero'); + + @override + Future getPlatformVersion() async { + final version = await methodChannel.invokeMethod('getPlatformVersion'); + return version; + } +} diff --git a/cw_monero/lib/cw_monero_platform_interface.dart b/cw_monero/lib/cw_monero_platform_interface.dart new file mode 100644 index 000000000..6c9b20a25 --- /dev/null +++ b/cw_monero/lib/cw_monero_platform_interface.dart @@ -0,0 +1,29 @@ +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; + +import 'cw_monero_method_channel.dart'; + +abstract class CwMoneroPlatform extends PlatformInterface { + /// Constructs a CwMoneroPlatform. + CwMoneroPlatform() : super(token: _token); + + static final Object _token = Object(); + + static CwMoneroPlatform _instance = MethodChannelCwMonero(); + + /// The default instance of [CwMoneroPlatform] to use. + /// + /// Defaults to [MethodChannelCwMonero]. + static CwMoneroPlatform get instance => _instance; + + /// Platform-specific implementations should set this with their own + /// platform-specific class that extends [CwMoneroPlatform] when + /// they register themselves. + static set instance(CwMoneroPlatform instance) { + PlatformInterface.verifyToken(instance, _token); + _instance = instance; + } + + Future getPlatformVersion() { + throw UnimplementedError('platformVersion() has not been implemented.'); + } +} diff --git a/cw_monero/macos/Classes/CwMoneroPlugin.swift b/cw_monero/macos/Classes/CwMoneroPlugin.swift new file mode 100644 index 000000000..d4ff81e1c --- /dev/null +++ b/cw_monero/macos/Classes/CwMoneroPlugin.swift @@ -0,0 +1,19 @@ +import Cocoa +import FlutterMacOS + +public class CwMoneroPlugin: NSObject, FlutterPlugin { + public static func register(with registrar: FlutterPluginRegistrar) { + let channel = FlutterMethodChannel(name: "cw_monero", binaryMessenger: registrar.messenger) + let instance = CwMoneroPlugin() + registrar.addMethodCallDelegate(instance, channel: channel) + } + + public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + switch call.method { + case "getPlatformVersion": + result("macOS " + ProcessInfo.processInfo.operatingSystemVersionString) + default: + result(FlutterMethodNotImplemented) + } + } +} diff --git a/cw_monero/macos/Classes/CwWalletListener.h b/cw_monero/macos/Classes/CwWalletListener.h new file mode 100644 index 000000000..cbfcb0c4e --- /dev/null +++ b/cw_monero/macos/Classes/CwWalletListener.h @@ -0,0 +1,23 @@ +#include + +struct CWMoneroWalletListener; + +typedef int8_t (*on_new_block_callback)(uint64_t height); +typedef int8_t (*on_need_to_refresh_callback)(); + +typedef struct CWMoneroWalletListener +{ + // on_money_spent_callback *on_money_spent; + // on_money_received_callback *on_money_received; + // on_unconfirmed_money_received_callback *on_unconfirmed_money_received; + // on_new_block_callback *on_new_block; + // on_updated_callback *on_updated; + // on_refreshed_callback *on_refreshed; + + on_new_block_callback on_new_block; +} CWMoneroWalletListener; + +struct TestListener { + // int8_t x; + on_new_block_callback on_new_block; +}; \ No newline at end of file diff --git a/cw_monero/macos/Classes/monero_api.cpp b/cw_monero/macos/Classes/monero_api.cpp new file mode 100644 index 000000000..56548e79e --- /dev/null +++ b/cw_monero/macos/Classes/monero_api.cpp @@ -0,0 +1,798 @@ +#include +#include "cstdlib" +#include +#include +#include +#include +#include +#include "thread" +#include "CwWalletListener.h" +#if __APPLE__ +// Fix for randomx on ios +void __clear_cache(void* start, void* end) { } +#include "../External/macos/include/wallet2_api.h" +#else +#include "../External/android/include/wallet2_api.h" +#endif + +using namespace std::chrono_literals; +#ifdef __cplusplus +extern "C" +{ +#endif + const uint64_t MONERO_BLOCK_SIZE = 1000; + + struct Utf8Box + { + char *value; + + Utf8Box(char *_value) + { + value = _value; + } + }; + + struct SubaddressRow + { + uint64_t id; + char *address; + char *label; + + SubaddressRow(std::size_t _id, char *_address, char *_label) + { + id = static_cast(_id); + address = _address; + label = _label; + } + }; + + struct AccountRow + { + uint64_t id; + char *label; + + AccountRow(std::size_t _id, char *_label) + { + id = static_cast(_id); + label = _label; + } + }; + + struct MoneroWalletListener : Monero::WalletListener + { + uint64_t m_height; + bool m_need_to_refresh; + bool m_new_transaction; + + MoneroWalletListener() + { + m_height = 0; + m_need_to_refresh = false; + m_new_transaction = false; + } + + void moneySpent(const std::string &txId, uint64_t amount) + { + m_new_transaction = true; + } + + void moneyReceived(const std::string &txId, uint64_t amount) + { + m_new_transaction = true; + } + + void unconfirmedMoneyReceived(const std::string &txId, uint64_t amount) + { + m_new_transaction = true; + } + + void newBlock(uint64_t height) + { + m_height = height; + } + + void updated() + { + m_new_transaction = true; + } + + void refreshed() + { + m_need_to_refresh = true; + } + + void resetNeedToRefresh() + { + m_need_to_refresh = false; + } + + bool isNeedToRefresh() + { + return m_need_to_refresh; + } + + bool isNewTransactionExist() + { + return m_new_transaction; + } + + void resetIsNewTransactionExist() + { + m_new_transaction = false; + } + + uint64_t height() + { + return m_height; + } + }; + + struct TransactionInfoRow + { + uint64_t amount; + uint64_t fee; + uint64_t blockHeight; + uint64_t confirmations; + uint32_t subaddrAccount; + int8_t direction; + int8_t isPending; + uint32_t subaddrIndex; + + char *hash; + char *paymentId; + + int64_t datetime; + + TransactionInfoRow(Monero::TransactionInfo *transaction) + { + amount = transaction->amount(); + fee = transaction->fee(); + blockHeight = transaction->blockHeight(); + subaddrAccount = transaction->subaddrAccount(); + std::set::iterator it = transaction->subaddrIndex().begin(); + subaddrIndex = *it; + confirmations = transaction->confirmations(); + datetime = static_cast(transaction->timestamp()); + direction = transaction->direction(); + isPending = static_cast(transaction->isPending()); + std::string *hash_str = new std::string(transaction->hash()); + hash = strdup(hash_str->c_str()); + paymentId = strdup(transaction->paymentId().c_str()); + } + }; + + struct PendingTransactionRaw + { + uint64_t amount; + uint64_t fee; + char *hash; + char *hex; + char *txKey; + Monero::PendingTransaction *transaction; + + PendingTransactionRaw(Monero::PendingTransaction *_transaction) + { + transaction = _transaction; + amount = _transaction->amount(); + fee = _transaction->fee(); + hash = strdup(_transaction->txid()[0].c_str()); + hex = strdup(_transaction->hex()[0].c_str()); + txKey = strdup(_transaction->txKey()[0].c_str()); + } + }; + + Monero::Wallet *m_wallet; + Monero::TransactionHistory *m_transaction_history; + MoneroWalletListener *m_listener; + Monero::Subaddress *m_subaddress; + Monero::SubaddressAccount *m_account; + uint64_t m_last_known_wallet_height; + uint64_t m_cached_syncing_blockchain_height = 0; + std::mutex store_lock; + bool is_storing = false; + + void change_current_wallet(Monero::Wallet *wallet) + { + m_wallet = wallet; + m_listener = nullptr; + + + if (wallet != nullptr) + { + m_transaction_history = wallet->history(); + } + else + { + m_transaction_history = nullptr; + } + + if (wallet != nullptr) + { + m_account = wallet->subaddressAccount(); + } + else + { + m_account = nullptr; + } + + if (wallet != nullptr) + { + m_subaddress = wallet->subaddress(); + } + else + { + m_subaddress = nullptr; + } + } + + Monero::Wallet *get_current_wallet() + { + return m_wallet; + } + + bool create_wallet(char *path, char *password, char *language, int32_t networkType, char *error) + { + Monero::NetworkType _networkType = static_cast(networkType); + Monero::WalletManager *walletManager = Monero::WalletManagerFactory::getWalletManager(); + Monero::Wallet *wallet = walletManager->createWallet(path, password, language, _networkType); + + int status; + std::string errorString; + + wallet->statusWithErrorString(status, errorString); + + if (wallet->status() != Monero::Wallet::Status_Ok) + { + error = strdup(wallet->errorString().c_str()); + return false; + } + + change_current_wallet(wallet); + + return true; + } + + bool restore_wallet_from_seed(char *path, char *password, char *seed, int32_t networkType, uint64_t restoreHeight, char *error) + { + Monero::NetworkType _networkType = static_cast(networkType); + Monero::Wallet *wallet = Monero::WalletManagerFactory::getWalletManager()->recoveryWallet( + std::string(path), + std::string(password), + std::string(seed), + _networkType, + (uint64_t)restoreHeight); + + int status; + std::string errorString; + + wallet->statusWithErrorString(status, errorString); + + if (status != Monero::Wallet::Status_Ok || !errorString.empty()) + { + error = strdup(errorString.c_str()); + return false; + } + + change_current_wallet(wallet); + return true; + } + + bool restore_wallet_from_keys(char *path, char *password, char *language, char *address, char *viewKey, char *spendKey, int32_t networkType, uint64_t restoreHeight, char *error) + { + Monero::NetworkType _networkType = static_cast(networkType); + Monero::Wallet *wallet = Monero::WalletManagerFactory::getWalletManager()->createWalletFromKeys( + std::string(path), + std::string(password), + std::string(language), + _networkType, + (uint64_t)restoreHeight, + std::string(address), + std::string(viewKey), + std::string(spendKey)); + + int status; + std::string errorString; + + wallet->statusWithErrorString(status, errorString); + + if (status != Monero::Wallet::Status_Ok || !errorString.empty()) + { + error = strdup(errorString.c_str()); + return false; + } + + change_current_wallet(wallet); + return true; + } + + bool load_wallet(char *path, char *password, int32_t nettype) + { + nice(19); + Monero::NetworkType networkType = static_cast(nettype); + Monero::WalletManager *walletManager = Monero::WalletManagerFactory::getWalletManager(); + Monero::Wallet *wallet = walletManager->openWallet(std::string(path), std::string(password), networkType); + int status; + std::string errorString; + + wallet->statusWithErrorString(status, errorString); + change_current_wallet(wallet); + + return !(status != Monero::Wallet::Status_Ok || !errorString.empty()); + } + + char *error_string() { + return strdup(get_current_wallet()->errorString().c_str()); + } + + + bool is_wallet_exist(char *path) + { + return Monero::WalletManagerFactory::getWalletManager()->walletExists(std::string(path)); + } + + void close_current_wallet() + { + Monero::WalletManagerFactory::getWalletManager()->closeWallet(get_current_wallet()); + change_current_wallet(nullptr); + } + + char *get_filename() + { + return strdup(get_current_wallet()->filename().c_str()); + } + + char *secret_view_key() + { + return strdup(get_current_wallet()->secretViewKey().c_str()); + } + + char *public_view_key() + { + return strdup(get_current_wallet()->publicViewKey().c_str()); + } + + char *secret_spend_key() + { + return strdup(get_current_wallet()->secretSpendKey().c_str()); + } + + char *public_spend_key() + { + return strdup(get_current_wallet()->publicSpendKey().c_str()); + } + + char *get_address(uint32_t account_index, uint32_t address_index) + { + return strdup(get_current_wallet()->address(account_index, address_index).c_str()); + } + + + const char *seed() + { + return strdup(get_current_wallet()->seed().c_str()); + } + + uint64_t get_full_balance(uint32_t account_index) + { + return get_current_wallet()->balance(account_index); + } + + uint64_t get_unlocked_balance(uint32_t account_index) + { + return get_current_wallet()->unlockedBalance(account_index); + } + + uint64_t get_current_height() + { + return get_current_wallet()->blockChainHeight(); + } + + uint64_t get_node_height() + { + return get_current_wallet()->daemonBlockChainHeight(); + } + + bool connect_to_node(char *error) + { + nice(19); + bool is_connected = get_current_wallet()->connectToDaemon(); + + if (!is_connected) + { + error = strdup(get_current_wallet()->errorString().c_str()); + } + + return is_connected; + } + + bool setup_node(char *address, char *login, char *password, bool use_ssl, bool is_light_wallet, char *error) + { + nice(19); + Monero::Wallet *wallet = get_current_wallet(); + + std::string _login = ""; + std::string _password = ""; + + if (login != nullptr) + { + _login = std::string(login); + } + + if (password != nullptr) + { + _password = std::string(password); + } + + bool inited = wallet->init(std::string(address), 0, _login, _password, use_ssl, is_light_wallet); + + if (!inited) + { + error = strdup(wallet->errorString().c_str()); + } else if (!wallet->connectToDaemon()) { + error = strdup(wallet->errorString().c_str()); + } + + return inited; + } + + bool is_connected() + { + return get_current_wallet()->connected(); + } + + void start_refresh() + { + get_current_wallet()->refreshAsync(); + get_current_wallet()->startRefresh(); + } + + void set_refresh_from_block_height(uint64_t height) + { + get_current_wallet()->setRefreshFromBlockHeight(height); + } + + void set_recovering_from_seed(bool is_recovery) + { + get_current_wallet()->setRecoveringFromSeed(is_recovery); + } + + void store(char *path) + { + store_lock.lock(); + if (is_storing) { + return; + } + + is_storing = true; + get_current_wallet()->store(std::string(path)); + is_storing = false; + store_lock.unlock(); + } + + bool set_password(char *password, Utf8Box &error) { + bool is_changed = get_current_wallet()->setPassword(std::string(password)); + + if (!is_changed) { + error = Utf8Box(strdup(get_current_wallet()->errorString().c_str())); + } + + return is_changed; + } + + bool transaction_create(char *address, char *payment_id, char *amount, + uint8_t priority_raw, uint32_t subaddr_account, Utf8Box &error, PendingTransactionRaw &pendingTransaction) + { + nice(19); + + auto priority = static_cast(priority_raw); + std::string _payment_id; + Monero::PendingTransaction *transaction; + + if (payment_id != nullptr) + { + _payment_id = std::string(payment_id); + } + + if (amount != nullptr) + { + uint64_t _amount = Monero::Wallet::amountFromString(std::string(amount)); + transaction = m_wallet->createTransaction(std::string(address), _payment_id, _amount, m_wallet->defaultMixin(), priority, subaddr_account); + } + else + { + transaction = m_wallet->createTransaction(std::string(address), _payment_id, Monero::optional(), m_wallet->defaultMixin(), priority, subaddr_account); + } + + int status = transaction->status(); + + if (status == Monero::PendingTransaction::Status::Status_Error || status == Monero::PendingTransaction::Status::Status_Critical) + { + error = Utf8Box(strdup(transaction->errorString().c_str())); + return false; + } + + if (m_listener != nullptr) { + m_listener->m_new_transaction = true; + } + + pendingTransaction = PendingTransactionRaw(transaction); + return true; + } + + bool transaction_create_mult_dest(char **addresses, char *payment_id, char **amounts, uint32_t size, + uint8_t priority_raw, uint32_t subaddr_account, Utf8Box &error, PendingTransactionRaw &pendingTransaction) + { + nice(19); + + std::vector _addresses; + std::vector _amounts; + + for (int i = 0; i < size; i++) { + _addresses.push_back(std::string(*addresses)); + _amounts.push_back(Monero::Wallet::amountFromString(std::string(*amounts))); + addresses++; + amounts++; + } + + auto priority = static_cast(priority_raw); + std::string _payment_id; + Monero::PendingTransaction *transaction; + + if (payment_id != nullptr) + { + _payment_id = std::string(payment_id); + } + + transaction = m_wallet->createTransactionMultDest(_addresses, _payment_id, _amounts, m_wallet->defaultMixin(), priority, subaddr_account); + + int status = transaction->status(); + + if (status == Monero::PendingTransaction::Status::Status_Error || status == Monero::PendingTransaction::Status::Status_Critical) + { + error = Utf8Box(strdup(transaction->errorString().c_str())); + return false; + } + + if (m_listener != nullptr) { + m_listener->m_new_transaction = true; + } + + pendingTransaction = PendingTransactionRaw(transaction); + return true; + } + + bool transaction_commit(PendingTransactionRaw *transaction, Utf8Box &error) + { + bool committed = transaction->transaction->commit(); + + if (!committed) + { + error = Utf8Box(strdup(transaction->transaction->errorString().c_str())); + } else if (m_listener != nullptr) { + m_listener->m_new_transaction = true; + } + + return committed; + } + + uint64_t get_node_height_or_update(uint64_t base_eight) + { + if (m_cached_syncing_blockchain_height < base_eight) { + m_cached_syncing_blockchain_height = base_eight; + } + + return m_cached_syncing_blockchain_height; + } + + uint64_t get_syncing_height() + { + if (m_listener == nullptr) { + return 0; + } + + uint64_t height = m_listener->height(); + + if (height <= 1) { + return 0; + } + + if (height != m_last_known_wallet_height) + { + m_last_known_wallet_height = height; + } + + return height; + } + + uint64_t is_needed_to_refresh() + { + if (m_listener == nullptr) { + return false; + } + + bool should_refresh = m_listener->isNeedToRefresh(); + + if (should_refresh) { + m_listener->resetNeedToRefresh(); + } + + return should_refresh; + } + + uint8_t is_new_transaction_exist() + { + if (m_listener == nullptr) { + return false; + } + + bool is_new_transaction_exist = m_listener->isNewTransactionExist(); + + if (is_new_transaction_exist) + { + m_listener->resetIsNewTransactionExist(); + } + + return is_new_transaction_exist; + } + + void set_listener() + { + m_last_known_wallet_height = 0; + + if (m_listener != nullptr) + { + free(m_listener); + } + + m_listener = new MoneroWalletListener(); + get_current_wallet()->setListener(m_listener); + } + + int64_t *subaddrress_get_all() + { + std::vector _subaddresses = m_subaddress->getAll(); + size_t size = _subaddresses.size(); + int64_t *subaddresses = (int64_t *)malloc(size * sizeof(int64_t)); + + for (int i = 0; i < size; i++) + { + Monero::SubaddressRow *row = _subaddresses[i]; + SubaddressRow *_row = new SubaddressRow(row->getRowId(), strdup(row->getAddress().c_str()), strdup(row->getLabel().c_str())); + subaddresses[i] = reinterpret_cast(_row); + } + + return subaddresses; + } + + int32_t subaddrress_size() + { + std::vector _subaddresses = m_subaddress->getAll(); + return _subaddresses.size(); + } + + void subaddress_add_row(uint32_t accountIndex, char *label) + { + m_subaddress->addRow(accountIndex, std::string(label)); + } + + void subaddress_set_label(uint32_t accountIndex, uint32_t addressIndex, char *label) + { + m_subaddress->setLabel(accountIndex, addressIndex, std::string(label)); + } + + void subaddress_refresh(uint32_t accountIndex) + { + m_subaddress->refresh(accountIndex); + } + + int32_t account_size() + { + std::vector _accocunts = m_account->getAll(); + return _accocunts.size(); + } + + int64_t *account_get_all() + { + std::vector _accocunts = m_account->getAll(); + size_t size = _accocunts.size(); + int64_t *accocunts = (int64_t *)malloc(size * sizeof(int64_t)); + + for (int i = 0; i < size; i++) + { + Monero::SubaddressAccountRow *row = _accocunts[i]; + AccountRow *_row = new AccountRow(row->getRowId(), strdup(row->getLabel().c_str())); + accocunts[i] = reinterpret_cast(_row); + } + + return accocunts; + } + + void account_add_row(char *label) + { + m_account->addRow(std::string(label)); + } + + void account_set_label_row(uint32_t account_index, char *label) + { + m_account->setLabel(account_index, label); + } + + void account_refresh() + { + m_account->refresh(); + } + + int64_t *transactions_get_all() + { + std::vector transactions = m_transaction_history->getAll(); + size_t size = transactions.size(); + int64_t *transactionAddresses = (int64_t *)malloc(size * sizeof(int64_t)); + + for (int i = 0; i < size; i++) + { + Monero::TransactionInfo *row = transactions[i]; + TransactionInfoRow *tx = new TransactionInfoRow(row); + transactionAddresses[i] = reinterpret_cast(tx); + } + + return transactionAddresses; + } + + void transactions_refresh() + { + m_transaction_history->refresh(); + } + + int64_t transactions_count() + { + return m_transaction_history->count(); + } + + int LedgerExchange( + unsigned char *command, + unsigned int cmd_len, + unsigned char *response, + unsigned int max_resp_len) + { + return -1; + } + + int LedgerFind(char *buffer, size_t len) + { + return -1; + } + + void on_startup() + { + Monero::Utils::onStartup(); + Monero::WalletManagerFactory::setLogLevel(0); + } + + void rescan_blockchain() + { + m_wallet->rescanBlockchainAsync(); + } + + char * get_tx_key(char * txId) + { + return strdup(m_wallet->getTxKey(std::string(txId)).c_str()); + } + + char *get_subaddress_label(uint32_t accountIndex, uint32_t addressIndex) + { + return strdup(get_current_wallet()->getSubaddressLabel(accountIndex, addressIndex).c_str()); + } + + void set_trusted_daemon(bool arg) + { + m_wallet->setTrustedDaemon(arg); + } + + bool trusted_daemon() + { + return m_wallet->trustedDaemon(); + } + +#ifdef __cplusplus +} +#endif diff --git a/cw_monero/macos/Classes/monero_api.h b/cw_monero/macos/Classes/monero_api.h new file mode 100644 index 000000000..74258ba4c --- /dev/null +++ b/cw_monero/macos/Classes/monero_api.h @@ -0,0 +1,38 @@ +#include +#include +#include +#include "CwWalletListener.h" + +#ifdef __cplusplus +extern "C" { +#endif + +bool create_wallet(char *path, char *password, char *language, int32_t networkType, char *error); +bool restore_wallet_from_seed(char *path, char *password, char *seed, int32_t networkType, uint64_t restoreHeight, char *error); +bool restore_wallet_from_keys(char *path, char *password, char *language, char *address, char *viewKey, char *spendKey, int32_t networkType, uint64_t restoreHeight, char *error); +void load_wallet(char *path, char *password, int32_t nettype); +bool is_wallet_exist(char *path); + +char *get_filename(); +const char *seed(); +char *get_address(uint32_t account_index, uint32_t address_index); +uint64_t get_full_balance(uint32_t account_index); +uint64_t get_unlocked_balance(uint32_t account_index); +uint64_t get_current_height(); +uint64_t get_node_height(); + +bool is_connected(); + +bool setup_node(char *address, char *login, char *password, bool use_ssl, bool is_light_wallet, char *error); +bool connect_to_node(char *error); +void start_refresh(); +void set_refresh_from_block_height(uint64_t height); +void set_recovering_from_seed(bool is_recovery); +void store(char *path); + +void set_trusted_daemon(bool arg); +bool trusted_daemon(); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/cw_monero/macos/cw_monero_base.podspec b/cw_monero/macos/cw_monero_base.podspec new file mode 100644 index 000000000..aac972c0f --- /dev/null +++ b/cw_monero/macos/cw_monero_base.podspec @@ -0,0 +1,56 @@ +# +# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. +# Run `pod lib lint cw_monero.podspec` to validate before publishing. +# +Pod::Spec.new do |s| + s.name = 'cw_monero' + s.version = '0.0.1' + s.summary = 'CW Monero' + s.description = 'Cake Wallet wrapper over Monero project.' + s.homepage = 'http://cakewallet.com' + s.license = { :file => '../LICENSE' } + s.author = { 'CakeWallet' => 'support@cakewallet.com' } + s.source = { :path => '.' } + s.source_files = 'Classes/**/*' + s.dependency 'FlutterMacOS' + s.public_header_files = 'Classes/**/*.h, Classes/*.h, External/macos/libs/monero/include/External/ios/**/*.h' + s.platform = :osx, '10.11' + s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'VALID_ARCHS' => '#___VALID_ARCHS___#', 'ENABLE_BITCODE' => 'NO' } + s.swift_version = '5.0' + s.libraries = 'iconv' + + s.subspec 'OpenSSL' do |openssl| + openssl.preserve_paths = '../../../../../cw_shared_external/ios/External/macos/include/**/*.h' + openssl.vendored_libraries = '../../../../../cw_shared_external/ios/External/macos/lib/libcrypto.a', '../../../../../cw_shared_external/ios/External/ios/lib/libssl.a' + openssl.libraries = 'ssl', 'crypto' + openssl.xcconfig = { 'HEADER_SEARCH_PATHS' => "${PODS_ROOT}/#{s.name}/External/macos/include/**" } + end + + s.subspec 'Sodium' do |sodium| + sodium.preserve_paths = '../../../../../cw_shared_external/ios/External/macos/include/**/*.h' + sodium.vendored_libraries = '../../../../../cw_shared_external/ios/External/macos/lib/libsodium.a' + sodium.libraries = 'sodium' + sodium.xcconfig = { 'HEADER_SEARCH_PATHS' => "${PODS_ROOT}/#{s.name}/External/macos/include/**" } + end + + s.subspec 'Unbound' do |unbound| + unbound.preserve_paths = '../../../../../cw_shared_external/ios/External/macos/include/**/*.h' + unbound.vendored_libraries = '../../../../../cw_shared_external/ios/External/macos/lib/libunbound.a' + unbound.libraries = 'unbound' + unbound.xcconfig = { 'HEADER_SEARCH_PATHS' => "${PODS_ROOT}/#{s.name}/External/macos/include/**" } + end + + s.subspec 'Boost' do |boost| + boost.preserve_paths = '../../../../../cw_shared_external/ios/External/macos/include/**/*.h', + boost.vendored_libraries = '../../../../../cw_shared_external/ios/External/macos/lib/libboost.a', + boost.libraries = 'boost' + boost.xcconfig = { 'HEADER_SEARCH_PATHS' => "${PODS_ROOT}/#{s.name}/External/macos/include/**" } + end + + s.subspec 'Monero' do |monero| + monero.preserve_paths = 'External/macos/include/**/*.h' + monero.vendored_libraries = 'External/macos/lib/libmonero.a' + monero.libraries = 'monero' + monero.xcconfig = { 'HEADER_SEARCH_PATHS' => "${PODS_ROOT}/#{s.name}/External/macos/include" } + end +end diff --git a/cw_monero/pubspec.yaml b/cw_monero/pubspec.yaml index 6d5041dfa..066a0d4c3 100644 --- a/cw_monero/pubspec.yaml +++ b/cw_monero/pubspec.yaml @@ -40,8 +40,15 @@ flutter: # be modified. They are used by the tooling to maintain consistency when # adding or updating assets for this project. plugin: - androidPackage: com.cakewallet.monero - pluginClass: CwMoneroPlugin + platforms: + android: + package: com.cakewallet.monero + pluginClass: CwMoneroPlugin + ios: + pluginClass: CwMoneroPlugin + + macos: + pluginClass: CwMoneroPlugin # To add assets to your plugin package, add an assets section, like this: # assets: diff --git a/cw_monero/test/cw_monero_method_channel_test.dart b/cw_monero/test/cw_monero_method_channel_test.dart new file mode 100644 index 000000000..8c1f329f0 --- /dev/null +++ b/cw_monero/test/cw_monero_method_channel_test.dart @@ -0,0 +1,24 @@ +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:cw_monero/cw_monero_method_channel.dart'; + +void main() { + MethodChannelCwMonero platform = MethodChannelCwMonero(); + const MethodChannel channel = MethodChannel('cw_monero'); + + TestWidgetsFlutterBinding.ensureInitialized(); + + setUp(() { + channel.setMockMethodCallHandler((MethodCall methodCall) async { + return '42'; + }); + }); + + tearDown(() { + channel.setMockMethodCallHandler(null); + }); + + test('getPlatformVersion', () async { + expect(await platform.getPlatformVersion(), '42'); + }); +} diff --git a/cw_monero/test/cw_monero_test.dart b/cw_monero/test/cw_monero_test.dart new file mode 100644 index 000000000..1eb8d6f79 --- /dev/null +++ b/cw_monero/test/cw_monero_test.dart @@ -0,0 +1,29 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:cw_monero/cw_monero.dart'; +import 'package:cw_monero/cw_monero_platform_interface.dart'; +import 'package:cw_monero/cw_monero_method_channel.dart'; +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; + +class MockCwMoneroPlatform + with MockPlatformInterfaceMixin + implements CwMoneroPlatform { + + @override + Future getPlatformVersion() => Future.value('42'); +} + +void main() { + final CwMoneroPlatform initialPlatform = CwMoneroPlatform.instance; + + test('$MethodChannelCwMonero is the default instance', () { + expect(initialPlatform, isInstanceOf()); + }); + + test('getPlatformVersion', () async { + CwMonero cwMoneroPlugin = CwMonero(); + MockCwMoneroPlatform fakePlatform = MockCwMoneroPlatform(); + CwMoneroPlatform.instance = fakePlatform; + + expect(await cwMoneroPlugin.getPlatformVersion(), '42'); + }); +} diff --git a/ios/CakeWallet/wakeLock.swift b/ios/CakeWallet/wakeLock.swift deleted file mode 100644 index 35f23eafa..000000000 --- a/ios/CakeWallet/wakeLock.swift +++ /dev/null @@ -1,20 +0,0 @@ -// -// wakeLock.swift -// Runner -// -// Created by Godwin Asuquo on 1/21/22. -// - -import Foundation -import UIKit - -func enableWakeScreen() -> Bool{ - UIApplication.shared.isIdleTimerDisabled = true - - return true -} - -func disableWakeScreen() -> Bool{ - UIApplication.shared.isIdleTimerDisabled = false - return true -} diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 5148714e5..50b9da031 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -12,7 +12,6 @@ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 20ED0868E1BD7E12278C0CB3 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B26E3F56D69167FBB1DC160A /* Pods_Runner.framework */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; - 5AFFEBFD279AD49C00F906A4 /* wakeLock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AFFEBFC279AD49C00F906A4 /* wakeLock.swift */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; @@ -282,7 +281,6 @@ files = ( 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, - 5AFFEBFD279AD49C00F906A4 /* wakeLock.swift in Sources */, 0C9D68C9264854B60011B691 /* secRandom.swift in Sources */, 0C44A71A2518EF8000B570ED /* decrypt.swift in Sources */, ); diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index e3f3da418..6d5f51aa3 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -96,11 +96,6 @@ import UnstoppableDomainsResolution result(address) } - case "enableWakeScreen": - result(enableWakeScreen()) - - case "disableWakeScreen": - result(disableWakeScreen()) default: result(FlutterMethodNotImplemented) diff --git a/lib/buy/moonpay/moonpay_buy_provider.dart b/lib/buy/moonpay/moonpay_buy_provider.dart index 4ff3fb04c..372b6d6cc 100644 --- a/lib/buy/moonpay/moonpay_buy_provider.dart +++ b/lib/buy/moonpay/moonpay_buy_provider.dart @@ -23,7 +23,7 @@ class MoonPaySellProvider { final bool isTest; final String baseUrl; - Future requestUrl({required CryptoCurrency currency, required String refundWalletAddress}) async { + Future requestUrl({required CryptoCurrency currency, required String refundWalletAddress}) async { final originalUri = Uri.https( baseUrl, '', { 'apiKey': _apiKey, @@ -37,13 +37,13 @@ class MoonPaySellProvider { final signature = base64.encode(digest.bytes); if (isTest) { - return originalUri.toString(); + return originalUri; } final query = Map.from(originalUri.queryParameters); query['signature'] = signature; final signedUri = originalUri.replace(queryParameters: query); - return signedUri.toString(); + return signedUri; } } diff --git a/lib/buy/onramper/onramper_buy_provider.dart b/lib/buy/onramper/onramper_buy_provider.dart new file mode 100644 index 000000000..a887f98dc --- /dev/null +++ b/lib/buy/onramper/onramper_buy_provider.dart @@ -0,0 +1,69 @@ +import 'package:cake_wallet/.secrets.g.dart' as secrets; +import 'package:cake_wallet/store/settings_store.dart'; +import 'package:cake_wallet/themes/theme_base.dart'; +import 'package:cw_core/wallet_base.dart'; + +class OnRamperBuyProvider { + OnRamperBuyProvider({required SettingsStore settingsStore, required WalletBase wallet}) + : this._settingsStore = settingsStore, + this._wallet = wallet; + + final SettingsStore _settingsStore; + final WalletBase _wallet; + + static const _baseUrl = 'buy.onramper.com'; + + static String get _apiKey => secrets.onramperApiKey; + + Uri requestUrl() { + String primaryColor, + secondaryColor, + primaryTextColor, + secondaryTextColor, + containerColor, + cardColor; + + switch (_settingsStore.currentTheme.type) { + case ThemeType.bright: + primaryColor = '815dfbff'; + secondaryColor = 'ffffff'; + primaryTextColor = '141519'; + secondaryTextColor = '6b6f80'; + containerColor = 'ffffff'; + cardColor = 'f2f0faff'; + break; + case ThemeType.light: + primaryColor = '2194ffff'; + secondaryColor = 'ffffff'; + primaryTextColor = '141519'; + secondaryTextColor = '6b6f80'; + containerColor = 'ffffff'; + cardColor = 'e5f7ff'; + break; + case ThemeType.dark: + primaryColor = '456effff'; + secondaryColor = '1b2747ff'; + primaryTextColor = 'ffffff'; + secondaryTextColor = 'ffffff'; + containerColor = '19233C'; + cardColor = '232f4fff'; + break; + } + + + return Uri.https(_baseUrl, '', { + 'apiKey': _apiKey, + 'defaultCrypto': _wallet.currency.title, + 'defaultFiat': _settingsStore.fiatCurrency.title, + 'wallets': '${_wallet.currency.title}:${_wallet.walletAddresses.address}', + 'supportSell': "false", + 'supportSwap': "false", + 'primaryColor': primaryColor, + 'secondaryColor': secondaryColor, + 'primaryTextColor': primaryTextColor, + 'secondaryTextColor': secondaryTextColor, + 'containerColor': containerColor, + 'cardColor': cardColor + }); + } +} diff --git a/lib/buy/payfura/payfura_buy_provider.dart b/lib/buy/payfura/payfura_buy_provider.dart new file mode 100644 index 000000000..eb9104df0 --- /dev/null +++ b/lib/buy/payfura/payfura_buy_provider.dart @@ -0,0 +1,24 @@ +import 'package:cake_wallet/.secrets.g.dart' as secrets; +import 'package:cake_wallet/store/settings_store.dart'; +import 'package:cw_core/wallet_base.dart'; + +class PayfuraBuyProvider { + PayfuraBuyProvider({required SettingsStore settingsStore, required WalletBase wallet}) + : this._settingsStore = settingsStore, + this._wallet = wallet; + + final SettingsStore _settingsStore; + final WalletBase _wallet; + + static const _baseUrl = 'exchange.payfura.com'; + + Uri requestUrl() { + return Uri.https(_baseUrl, '', { + 'apiKey': secrets.payfuraApiKey, + 'to': _wallet.currency.title, + 'from': _settingsStore.fiatCurrency.title, + 'walletAddress': '${_wallet.currency.title}:${_wallet.walletAddresses.address}', + 'mode': 'buy' + }); + } +} diff --git a/lib/di.dart b/lib/di.dart index 166fa218f..2d6e1ec34 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -1,17 +1,22 @@ import 'package:cake_wallet/anonpay/anonpay_api.dart'; import 'package:cake_wallet/anonpay/anonpay_info_base.dart'; import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart'; +import 'package:cake_wallet/buy/onramper/onramper_buy_provider.dart'; +import 'package:cake_wallet/buy/payfura/payfura_buy_provider.dart'; import 'package:cake_wallet/core/yat_service.dart'; import 'package:cake_wallet/entities/exchange_api_mode.dart'; import 'package:cake_wallet/entities/parse_address_from_domain.dart'; import 'package:cake_wallet/entities/receive_page_option.dart'; -import 'package:cake_wallet/entities/wake_lock.dart'; 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/anonpay_details/anonpay_details_page.dart'; import 'package:cake_wallet/src/screens/buy/onramper_page.dart'; import 'package:cake_wallet/src/screens/buy/payfura_page.dart'; +import 'package:cake_wallet/src/screens/dashboard/desktop_dashboard_page.dart'; +import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_sidebar_wrapper.dart'; +import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart'; +import 'package:cake_wallet/src/screens/dashboard/widgets/transactions_page.dart'; import 'package:cake_wallet/src/screens/receive/anonpay_invoice_page.dart'; import 'package:cake_wallet/src/screens/receive/anonpay_receive_page.dart'; import 'package:cake_wallet/src/screens/settings/display_settings_page.dart'; @@ -22,8 +27,11 @@ import 'package:cake_wallet/src/screens/ionia/cards/ionia_custom_redeem_page.dar import 'package:cake_wallet/src/screens/ionia/cards/ionia_gift_card_detail_page.dart'; import 'package:cake_wallet/src/screens/ionia/cards/ionia_more_options_page.dart'; import 'package:cake_wallet/src/screens/settings/connection_sync_page.dart'; +import 'package:cake_wallet/themes/theme_list.dart'; +import 'package:cake_wallet/utils/device_info.dart'; import 'package:cake_wallet/store/anonpay/anonpay_transactions_store.dart'; import 'package:cake_wallet/utils/payment_request.dart'; +import 'package:cake_wallet/view_model/dashboard/desktop_sidebar_view_model.dart'; import 'package:cake_wallet/view_model/anon_invoice_page_view_model.dart'; import 'package:cake_wallet/view_model/anonpay_details_view_model.dart'; import 'package:cake_wallet/view_model/dashboard/receive_option_view_model.dart'; @@ -143,7 +151,6 @@ import 'package:cake_wallet/view_model/wallet_list/wallet_list_view_model.dart'; import 'package:cake_wallet/view_model/wallet_restore_view_model.dart'; import 'package:cake_wallet/view_model/wallet_seed_view_model.dart'; import 'package:cake_wallet/view_model/exchange/exchange_view_model.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; import 'package:get_it/get_it.dart'; import 'package:hive/hive.dart'; @@ -219,12 +226,16 @@ Future setup( () => SharedPreferences.getInstance()); } - final isBitcoinBuyEnabled = (secrets.wyreSecretKey?.isNotEmpty ?? false) && - (secrets.wyreApiKey?.isNotEmpty ?? false) && - (secrets.wyreAccountId?.isNotEmpty ?? false); + final isBitcoinBuyEnabled = (secrets.wyreSecretKey.isNotEmpty ?? false) && + (secrets.wyreApiKey.isNotEmpty ?? false) && + (secrets.wyreAccountId.isNotEmpty ?? false); final settingsStore = await SettingsStoreBase.load( - nodeSource: _nodeSource, isBitcoinBuyEnabled: isBitcoinBuyEnabled); + nodeSource: _nodeSource, + isBitcoinBuyEnabled: isBitcoinBuyEnabled, + // Enforce darkTheme on platforms other than mobile till the design for other themes is completed + initialTheme: DeviceInfo.instance.isMobile ? null : ThemeList.darkTheme, + ); if (_isSetupFinished) { return; @@ -375,42 +386,64 @@ Future setup( .registerFactoryParam( (onAuthFinished, closable) => AuthPage(getIt.get(), onAuthenticationFinished: onAuthFinished, - closable: closable ?? false)); + closable: closable)); getIt.registerFactory(() => BalancePage(dashboardViewModel: getIt.get(), settingsStore: getIt.get())); - getIt.registerFactory(() => DashboardPage( balancePage: getIt.get(), walletViewModel: getIt.get(), addressListViewModel: getIt.get())); - + getIt.registerFactory(() => DashboardPage( + balancePage: getIt.get(), + dashboardViewModel: getIt.get(), + addressListViewModel: getIt.get(), + )); + getIt.registerFactory(() { + final GlobalKey _navigatorKey = GlobalKey(); + return DesktopSidebarWrapper( + dashboardViewModel: getIt.get(), + desktopSidebarViewModel: getIt.get(), + child: getIt.get(param1: _navigatorKey), + desktopNavigatorKey: _navigatorKey, + ); + }); + getIt.registerFactoryParam, void>( + (desktopKey, _) => DesktopDashboardPage( + balancePage: getIt.get(), + dashboardViewModel: getIt.get(), + addressListViewModel: getIt.get(), + desktopKey: desktopKey, + )); + + getIt.registerFactory(() => TransactionsPage(dashboardViewModel: getIt.get())); + getIt.registerFactoryParam((pageOption, _) => ReceiveOptionViewModel( getIt.get().wallet!, pageOption)); - getIt.registerFactoryParam, void>((args, _) { + getIt.registerFactoryParam, void>((args, _) { final address = args.first as String; final pageOption = args.last as ReceivePageOption; return AnonInvoicePageViewModel( - getIt.get(), - address, - getIt.get(), - getIt.get().wallet!, - _anonpayInvoiceInfoSource, + getIt.get(), + address, + getIt.get(), + getIt.get().wallet!, + _anonpayInvoiceInfoSource, getIt.get(), pageOption, - ); + ); }); - getIt.registerFactoryParam, void>((List args, _) { + getIt.registerFactoryParam, void>((List args, _) { final pageOption = args.last as ReceivePageOption; return AnonPayInvoicePage( - getIt.get(param1: args), + getIt.get(param1: args), getIt.get(param1: pageOption)); - }); - + }); + getIt.registerFactory(() => ReceivePage( addressListViewModel: getIt.get())); getIt.registerFactory(() => AddressPage( addressListViewModel: getIt.get(), - walletViewModel: getIt.get(), + dashboardViewModel: getIt.get(), receiveOptionViewModel: getIt.get())); getIt.registerFactoryParam( @@ -445,13 +478,25 @@ Future setup( getIt.registerFactory(() => SendTemplatePage( sendTemplateViewModel: getIt.get())); - getIt.registerFactory(() => WalletListViewModel( - _walletInfoSource, - getIt.get(), - getIt.get(), - getIt.get(), - ), - ); + if (DeviceInfo.instance.isMobile) { + getIt.registerFactory(() => WalletListViewModel( + _walletInfoSource, + getIt.get(), + getIt.get(), + getIt.get(), + ), + ); + } else { + // register wallet list view model as singleton on desktop since it can be accessed + // from multiple places at the same time (Wallets DropDown, Wallets List in settings) + getIt.registerLazySingleton(() => WalletListViewModel( + _walletInfoSource, + getIt.get(), + getIt.get(), + getIt.get(), + ), + ); + } getIt.registerFactory(() => WalletListPage(walletListViewModel: getIt.get())); @@ -523,7 +568,7 @@ Future setup( isNewWalletCreated: isWalletCreated)); getIt - .registerFactory(() => WalletKeysViewModel(getIt.get().wallet!)); + .registerFactory(() => WalletKeysViewModel(getIt.get())); getIt.registerFactory(() => WalletKeysPage(getIt.get())); @@ -543,8 +588,7 @@ Future setup( getIt.registerFactory(() { final appStore = getIt.get(); - return NodeListViewModel( - _nodeSource, appStore.wallet!, appStore.settingsStore); + return NodeListViewModel(_nodeSource, appStore); }); getIt.registerFactory(() => ConnectionSyncPage(getIt.get(), getIt.get())); @@ -570,13 +614,19 @@ Future setup( editingNode: editingNode, isSelected: isSelected)); - getIt.registerFactory(() => OnRamperPage( + getIt.registerFactory(() => OnRamperBuyProvider( settingsStore: getIt.get().settingsStore, - wallet: getIt.get().wallet!)); + wallet: getIt.get().wallet!, + )); - getIt.registerFactory(() => PayFuraPage( - settingsStore: getIt.get().settingsStore, - wallet: getIt.get().wallet!)); + getIt.registerFactory(() => OnRamperPage(getIt.get())); + + getIt.registerFactory(() => PayfuraBuyProvider( + settingsStore: getIt.get().settingsStore, + wallet: getIt.get().wallet!, + )); + + getIt.registerFactory(() => PayFuraPage(getIt.get())); getIt.registerFactory(() => ExchangeViewModel( getIt.get().wallet!, @@ -759,8 +809,6 @@ Future setup( param1: item, param2: unspentCoinsListViewModel)); }); - getIt.registerFactory(() => WakeLock()); - getIt.registerFactory(() => YatService()); getIt.registerFactory(() => AddressResolver(yatService: getIt.get(), @@ -873,11 +921,15 @@ Future setup( getIt.registerFactory(() => IoniaAccountPage(getIt.get())); getIt.registerFactory(() => IoniaAccountCardsPage(getIt.get())); - + getIt.registerFactory(() => AnonPayApi(useTorOnly: getIt.get().exchangeStatus == ExchangeApiMode.torOnly, - wallet: getIt.get().wallet!) + wallet: getIt.get().wallet!) ); + getIt.registerFactory(() => DesktopWalletSelectionDropDown(getIt.get())); + + getIt.registerFactory(() => DesktopSidebarViewModel()); + getIt.registerFactoryParam( (AnonpayInvoiceInfo anonpayInvoiceInfo, _) => AnonpayDetailsViewModel( @@ -890,7 +942,7 @@ Future setup( (AnonpayInfoBase anonpayInvoiceInfo, _) => AnonPayReceivePage(invoiceInfo: anonpayInvoiceInfo)); getIt.registerFactoryParam( - (AnonpayInvoiceInfo anonpayInvoiceInfo, _) + (AnonpayInvoiceInfo anonpayInvoiceInfo, _) => AnonpayDetailsPage(anonpayDetailsViewModel: getIt.get(param1: anonpayInvoiceInfo))); getIt.registerFactoryParam( diff --git a/lib/entities/desktop_dropdown_item.dart b/lib/entities/desktop_dropdown_item.dart new file mode 100644 index 000000000..3a542f5e8 --- /dev/null +++ b/lib/entities/desktop_dropdown_item.dart @@ -0,0 +1,9 @@ +import 'package:flutter/material.dart'; + +class DesktopDropdownItem { + final Function() onSelected; + final Widget child; + final bool isSelected; + + DesktopDropdownItem({required this.onSelected, required this.child, this.isSelected = false}); +} diff --git a/lib/entities/main_actions.dart b/lib/entities/main_actions.dart new file mode 100644 index 000000000..ec70b95d9 --- /dev/null +++ b/lib/entities/main_actions.dart @@ -0,0 +1,142 @@ +import 'package:cake_wallet/buy/moonpay/moonpay_buy_provider.dart'; +import 'package:cake_wallet/buy/onramper/onramper_buy_provider.dart'; +import 'package:cake_wallet/buy/payfura/payfura_buy_provider.dart'; +import 'package:cake_wallet/di.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/device_info.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: + case WalletType.litecoin: + if (DeviceInfo.instance.isMobile) { + Navigator.of(context).pushNamed(Routes.onramperPage); + } else { + final uri = getIt + .get() + .requestUrl(); + await launchUrl(uri); + } + break; + case WalletType.monero: + if (DeviceInfo.instance.isMobile) { + Navigator.of(context).pushNamed(Routes.payfuraPage); + } else { + final uri = getIt + .get() + .requestUrl(); + await launchUrl(uri); + } + 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 launchUrl(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/entities/unstoppable_domain_address.dart b/lib/entities/unstoppable_domain_address.dart index ab94e31fb..c5ec71ab5 100644 --- a/lib/entities/unstoppable_domain_address.dart +++ b/lib/entities/unstoppable_domain_address.dart @@ -1,3 +1,4 @@ +import 'package:cake_wallet/utils/device_info.dart'; import 'package:flutter/services.dart'; const channel = MethodChannel('com.cake_wallet/native_utils'); @@ -6,13 +7,18 @@ Future fetchUnstoppableDomainAddress(String domain, String ticker) async var address = ''; try { - address = await channel.invokeMethod( - 'getUnstoppableDomainAddress', - { - 'domain' : domain, - 'ticker' : ticker - } - ) ?? ''; + if (DeviceInfo.instance.isMobile) { + address = await channel.invokeMethod( + 'getUnstoppableDomainAddress', + { + 'domain' : domain, + 'ticker' : ticker + } + ) ?? ''; + } else { + // TODO: Integrate with Unstoppable domains resolution API + return address; + } } catch (e) { print('Unstoppable domain error: ${e.toString()}'); address = ''; diff --git a/lib/entities/wake_lock.dart b/lib/entities/wake_lock.dart deleted file mode 100644 index 99acc65ee..000000000 --- a/lib/entities/wake_lock.dart +++ /dev/null @@ -1,21 +0,0 @@ -import 'package:flutter/services.dart'; - -class WakeLock { - static const _utils = const MethodChannel('com.cake_wallet/native_utils'); - - Future enableWake() async { - try { - await _utils.invokeMethod('enableWakeScreen'); - } on PlatformException catch (_) { - print('Failed enabling screen wakelock'); - } - } - - Future disableWake() async { - try { - await _utils.invokeMethod('disableWakeScreen'); - } on PlatformException catch (_) { - print('Failed enabling screen wakelock'); - } - } -} diff --git a/lib/exchange/changenow/changenow_exchange_provider.dart b/lib/exchange/changenow/changenow_exchange_provider.dart index e173dbdf6..89d32fa09 100644 --- a/lib/exchange/changenow/changenow_exchange_provider.dart +++ b/lib/exchange/changenow/changenow_exchange_provider.dart @@ -1,5 +1,6 @@ import 'dart:convert'; import 'package:cake_wallet/exchange/trade_not_found_exeption.dart'; +import 'package:cake_wallet/utils/device_info.dart'; import 'package:http/http.dart'; import 'package:cake_wallet/.secrets.g.dart' as secrets; import 'package:cw_core/crypto_currency.dart'; @@ -24,7 +25,7 @@ class ChangeNowExchangeProvider extends ExchangeProvider { .expand((i) => i) .toList()); - static const apiKey = secrets.changeNowApiKey; + static final apiKey = DeviceInfo.instance.isMobile ? secrets.changeNowApiKey : secrets.changeNowApiKeyDesktop; static const apiAuthority = 'api.changenow.io'; static const createTradePath = '/v2/exchange'; static const findTradeByIdPath = '/v2/exchange/by-id'; diff --git a/lib/exchange/simpleswap/simpleswap_exchange_provider.dart b/lib/exchange/simpleswap/simpleswap_exchange_provider.dart index 6c5c23460..4c1072d11 100644 --- a/lib/exchange/simpleswap/simpleswap_exchange_provider.dart +++ b/lib/exchange/simpleswap/simpleswap_exchange_provider.dart @@ -6,6 +6,7 @@ import 'package:cake_wallet/exchange/simpleswap/simpleswap_request.dart'; import 'package:cake_wallet/exchange/trade_not_created_exeption.dart'; import 'package:cake_wallet/exchange/trade_not_found_exeption.dart'; import 'package:cake_wallet/exchange/trade_state.dart'; +import 'package:cake_wallet/utils/device_info.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cake_wallet/exchange/trade_request.dart'; import 'package:cake_wallet/exchange/trade.dart'; @@ -29,7 +30,7 @@ class SimpleSwapExchangeProvider extends ExchangeProvider { static const rangePath = '/v1/get_ranges'; static const getExchangePath = '/v1/get_exchange'; static const createExchangePath = '/v1/create_exchange'; - static const apiKey = secrets.simpleSwapApiKey; + static final apiKey = DeviceInfo.instance.isMobile ? secrets.simpleSwapApiKey : secrets.simpleSwapApiKeyDesktop; @override ExchangeProviderDescription get description => diff --git a/lib/reactions/on_authentication_state_change.dart b/lib/reactions/on_authentication_state_change.dart index 7521170e6..5f1214b76 100644 --- a/lib/reactions/on_authentication_state_change.dart +++ b/lib/reactions/on_authentication_state_change.dart @@ -9,8 +9,8 @@ ReactionDisposer? _onAuthenticationStateChange; dynamic loginError; -void startAuthenticationStateChange(AuthenticationStore authenticationStore, - GlobalKey navigatorKey) { +void startAuthenticationStateChange( + AuthenticationStore authenticationStore, GlobalKey navigatorKey) { _onAuthenticationStateChange ??= autorun((_) async { final state = authenticationStore.state; diff --git a/lib/reactions/on_wallet_sync_status_change.dart b/lib/reactions/on_wallet_sync_status_change.dart index 68bd4b3c2..767bfd7e8 100644 --- a/lib/reactions/on_wallet_sync_status_change.dart +++ b/lib/reactions/on_wallet_sync_status_change.dart @@ -1,6 +1,4 @@ -import 'package:cake_wallet/di.dart'; import 'package:cake_wallet/entities/update_haven_rate.dart'; -import 'package:cake_wallet/entities/wake_lock.dart'; import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:mobx/mobx.dart'; @@ -9,7 +7,7 @@ import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/balance.dart'; import 'package:cw_core/transaction_info.dart'; import 'package:cw_core/sync_status.dart'; -import 'package:flutter/services.dart'; +import 'package:wakelock/wakelock.dart'; ReactionDisposer? _onWalletSyncStatusChangeReaction; @@ -17,7 +15,6 @@ void startWalletSyncStatusChangeReaction( WalletBase, TransactionInfo> wallet, FiatConversionStore fiatConversionStore) { - final _wakeLock = getIt.get(); _onWalletSyncStatusChangeReaction?.reaction.dispose(); _onWalletSyncStatusChangeReaction = reaction((_) => wallet.syncStatus, (SyncStatus status) async { @@ -30,10 +27,10 @@ void startWalletSyncStatusChangeReaction( } } if (status is SyncingSyncStatus) { - await _wakeLock.enableWake(); + await Wakelock.enable(); } if (status is SyncedSyncStatus || status is FailedSyncStatus) { - await _wakeLock.disableWake(); + await Wakelock.disable(); } } catch(e) { print(e.toString()); diff --git a/lib/router.dart b/lib/router.dart index 634a69966..aebee0942 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -11,6 +11,9 @@ import 'package:cake_wallet/src/screens/buy/payfura_page.dart'; import 'package:cake_wallet/src/screens/buy/pre_order_page.dart'; import 'package:cake_wallet/src/screens/receive/anonpay_invoice_page.dart'; import 'package:cake_wallet/src/screens/receive/anonpay_receive_page.dart'; +import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_dashboard_actions.dart'; +import 'package:cake_wallet/src/screens/dashboard/widgets/transactions_page.dart'; +import 'package:cake_wallet/src/screens/settings/desktop_settings/desktop_settings_page.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'; @@ -32,6 +35,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'; @@ -295,22 +299,27 @@ Route createRoute(RouteSettings settings) { case Routes.connectionSync: return CupertinoPageRoute( + fullscreenDialog: true, builder: (_) => getIt.get()); case Routes.securityBackupPage: return CupertinoPageRoute( + fullscreenDialog: true, builder: (_) => getIt.get()); case Routes.privacyPage: return CupertinoPageRoute( + fullscreenDialog: true, builder: (_) => getIt.get()); case Routes.displaySettingsPage: return CupertinoPageRoute( + fullscreenDialog: true, builder: (_) => getIt.get()); case Routes.otherSettingsPage: return CupertinoPageRoute( + fullscreenDialog: true, builder: (_) => getIt.get()); case Routes.newNode: @@ -336,8 +345,8 @@ Route createRoute(RouteSettings settings) { case Routes.addressBook: return MaterialPageRoute( - builder: (_) => - getIt.get()); + fullscreenDialog: true, + builder: (_) => getIt.get()); case Routes.pickerAddressBook: final selectedCurrency = settings.arguments as CryptoCurrency?; @@ -394,6 +403,7 @@ Route createRoute(RouteSettings settings) { case Routes.exchange: return CupertinoPageRoute( + fullscreenDialog: true, builder: (_) => getIt.get()); case Routes.exchangeTemplate: @@ -425,6 +435,7 @@ Route createRoute(RouteSettings settings) { case Routes.support: return CupertinoPageRoute( + fullscreenDialog: true, builder: (_) => getIt.get()); case Routes.unspentCoinsList: @@ -447,11 +458,14 @@ Route createRoute(RouteSettings settings) { getIt.get( param1: args['qrData'] as String, param2: args['version'] as int?, - + )); case Routes.ioniaWelcomePage: - return CupertinoPageRoute(builder: (_) => getIt.get()); + return CupertinoPageRoute( + fullscreenDialog: true, + builder: (_) => getIt.get(), + ); case Routes.ioniaLoginPage: return CupertinoPageRoute( builder: (_) => getIt.get()); @@ -524,19 +538,39 @@ Route createRoute(RouteSettings settings) { getIt.get(param1: type), getIt.get(param1: type), )); - + case Routes.anonPayInvoicePage: final args = settings.arguments as List; return CupertinoPageRoute(builder: (_) => getIt.get(param1: args)); - + case Routes.anonPayReceivePage: final anonInvoiceViewData = settings.arguments as AnonpayInfoBase; return CupertinoPageRoute(builder: (_) => getIt.get(param1: anonInvoiceViewData)); - + case Routes.anonPayDetailsPage: final anonInvoiceViewData = settings.arguments as AnonpayInvoiceInfo; return CupertinoPageRoute(builder: (_) => getIt.get(param1: anonInvoiceViewData)); - + + case Routes.desktop_actions: + return PageRouteBuilder( + opaque: false, + pageBuilder: (_, __, ___) => DesktopDashboardActions(getIt()), + ); + + case Routes.desktop_settings_page: + return CupertinoPageRoute( + builder: (_) => DesktopSettingsPage()); + + case Routes.empty_no_route: + return MaterialPageRoute( + builder: (_) => SizedBox.shrink()); + + case Routes.transactionsPage: + return CupertinoPageRoute( + settings: settings, + fullscreenDialog: true, + builder: (_) => getIt.get()); + default: return MaterialPageRoute( builder: (_) => Scaffold( diff --git a/lib/routes.dart b/lib/routes.dart index 99d0f438e..295b55ae0 100644 --- a/lib/routes.dart +++ b/lib/routes.dart @@ -37,6 +37,8 @@ class Routes { static const restoreWalletFromSeedDetails = '/restore_from_seed_details'; static const exchange = '/exchange'; static const settings = '/settings'; + static const desktop_settings_page = '/desktop_settings_page'; + static const empty_no_route = '/empty_no_route'; static const unlock = '/auth_not_closable'; static const rescan = '/rescan'; static const faq = '/faq'; @@ -86,4 +88,6 @@ class Routes { static const anonPayReceivePage = '/anon_pay_receive_page'; static const anonPayDetailsPage = '/anon_pay_details_page'; static const payfuraPage = '/pay_fura_page'; + static const desktop_actions = '/desktop_actions'; + static const transactionsPage = '/transactions_page'; } diff --git a/lib/src/screens/backup/backup_page.dart b/lib/src/screens/backup/backup_page.dart index f819e88e5..966b289d1 100644 --- a/lib/src/screens/backup/backup_page.dart +++ b/lib/src/screens/backup/backup_page.dart @@ -1,6 +1,8 @@ import 'dart:io'; import 'package:cake_wallet/palette.dart'; +import 'package:cake_wallet/utils/exception_handler.dart'; import 'package:cake_wallet/utils/share_util.dart'; +import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; @@ -27,8 +29,7 @@ class BackupPage extends BasePage { @override Widget trailing(BuildContext context) => TrailButton( caption: S.of(context).change_password, - onPressed: () => - Navigator.of(context).pushNamed(Routes.editBackupPassword), + onPressed: () => Navigator.of(context).pushNamed(Routes.editBackupPassword), textColor: Palette.blueCraiola); @override @@ -51,9 +52,8 @@ class BackupPage extends BasePage { child: Observer( builder: (_) => GestureDetector( onTap: () { - Clipboard.setData(ClipboardData( - text: - backupViewModelBase.backupPassword)); + Clipboard.setData( + ClipboardData(text: backupViewModelBase.backupPassword)); showBar( context, S.of(context).transaction_details_copied( @@ -108,8 +108,10 @@ class BackupPage extends BasePage { if (Platform.isAndroid) { onExportAndroid(context, backup); - } else { + } else if (Platform.isIOS) { await share(backup, context); + } else { + await _saveFile(backup); } }, actionLeftButton: () => Navigator.of(dialogContext).pop()); @@ -133,8 +135,7 @@ class BackupPage extends BasePage { return; } - await backupViewModelBase.saveToDownload( - backup.name, backup.content); + await backupViewModelBase.saveToDownload(backup.name, backup.content); Navigator.of(dialogContext).pop(); }, actionLeftButton: () async { @@ -149,4 +150,20 @@ class BackupPage extends BasePage { await ShareUtil.shareFile(filePath: path, fileName: backup.name, context: context); await backupViewModelBase.removeBackupFileLocally(backup); } + + Future _saveFile(BackupExportFile backup) async { + String? outputFile = await FilePicker.platform + .saveFile(dialogTitle: 'Save Your File to desired location', fileName: backup.name); + + try { + File returnedFile = File(outputFile!); + await returnedFile.writeAsBytes(backup.content); + } catch (exception, stackTrace) { + ExceptionHandler.onError(FlutterErrorDetails( + exception: exception, + stack: stackTrace, + library: "Export Backup", + )); + } + } } diff --git a/lib/src/screens/backup/edit_backup_password_page.dart b/lib/src/screens/backup/edit_backup_password_page.dart index 47ae77c0e..f1ddaf4e1 100644 --- a/lib/src/screens/backup/edit_backup_password_page.dart +++ b/lib/src/screens/backup/edit_backup_password_page.dart @@ -66,7 +66,8 @@ class EditBackupPasswordPage extends BasePage { leftButtonText: S.of(context).cancel, actionRightButton: () async { await editBackupPasswordViewModel.save(); - Navigator.of(dialogContext)..pop()..pop(); + Navigator.of(dialogContext).pop(); + Navigator.of(context).pop(); }, actionLeftButton: () => Navigator.of(dialogContext).pop()); }); diff --git a/lib/src/screens/base_page.dart b/lib/src/screens/base_page.dart index 92bf56eaf..123eef65c 100644 --- a/lib/src/screens/base_page.dart +++ b/lib/src/screens/base_page.dart @@ -1,4 +1,5 @@ import 'package:cake_wallet/themes/theme_base.dart'; +import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:cake_wallet/palette.dart'; @@ -20,7 +21,7 @@ abstract class BasePage extends StatelessWidget { String? get title => null; - bool get isModalBackButton => false; + bool get canUseCloseIcon => false; Color get backgroundLightColor => Colors.white; @@ -50,23 +51,27 @@ abstract class BasePage extends StatelessWidget { } final _backButton = Icon(Icons.arrow_back_ios, - color: titleColor ?? Theme.of(context).primaryTextTheme!.headline6!.color!, + color: titleColor ?? Theme.of(context).primaryTextTheme.headline6!.color!, size: 16,); final _closeButton = currentTheme.type == ThemeType.dark ? closeButtonImageDarkTheme : closeButtonImage; + bool isMobileView = ResponsiveLayoutUtil.instance.isMobile(context); + return SizedBox( - height: 37, - width: 37, + height: isMobileView ? 37 : 45, + width: isMobileView ? 37 : 45, child: ButtonTheme( minWidth: double.minPositive, child: TextButton( - // FIX-ME: Style - //highlightColor: Colors.transparent, - //splashColor: Colors.transparent, - //padding: EdgeInsets.all(0), + style: ButtonStyle( + overlayColor: MaterialStateColor.resolveWith((states) => Colors.transparent), + ), onPressed: () => onClose(context), - child: isModalBackButton ? _closeButton : _backButton), + child: canUseCloseIcon && !isMobileView + ? _closeButton + : _backButton, + ), ), ); } @@ -92,7 +97,7 @@ abstract class BasePage extends StatelessWidget { ObstructingPreferredSizeWidget appBar(BuildContext context) { final appBarColor = currentTheme.type == ThemeType.dark ? backgroundDarkColor : backgroundLightColor; - + switch (appBarStyle) { case AppBarStyle.regular: // FIX-ME: NavBar no context diff --git a/lib/src/screens/buy/onramper_page.dart b/lib/src/screens/buy/onramper_page.dart index 4973cef0b..cbdf9e1b1 100644 --- a/lib/src/screens/buy/onramper_page.dart +++ b/lib/src/screens/buy/onramper_page.dart @@ -1,60 +1,30 @@ +import 'package:cake_wallet/buy/onramper/onramper_buy_provider.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/src/screens/base_page.dart'; -import 'package:cake_wallet/store/settings_store.dart'; -import 'package:cw_core/wallet_base.dart'; import 'package:flutter/material.dart'; -import 'package:cake_wallet/.secrets.g.dart' as secrets; import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import 'package:permission_handler/permission_handler.dart'; class OnRamperPage extends BasePage { - OnRamperPage({required this.settingsStore, required this.wallet}); + OnRamperPage(this._onRamperBuyProvider); - final SettingsStore settingsStore; - final WalletBase wallet; + final OnRamperBuyProvider _onRamperBuyProvider; @override String get title => S.current.buy; @override Widget body(BuildContext context) { - final darkMode = Theme.of(context).brightness == Brightness.dark; - return OnRamperPageBody( - settingsStore: settingsStore, - wallet: wallet, - darkMode: darkMode, - backgroundColor: darkMode ? backgroundDarkColor : backgroundLightColor, - supportSell: false, - supportSwap: false); + return OnRamperPageBody(_onRamperBuyProvider); } } class OnRamperPageBody extends StatefulWidget { - OnRamperPageBody( - {required this.settingsStore, - required this.wallet, - required this.darkMode, - required this.supportSell, - required this.supportSwap, - required this.backgroundColor}); + OnRamperPageBody(this.onRamperBuyProvider); - static const baseUrl = 'widget.onramper.com'; - final SettingsStore settingsStore; - final WalletBase wallet; - final Color backgroundColor; - final bool darkMode; - final bool supportSell; - final bool supportSwap; + final OnRamperBuyProvider onRamperBuyProvider; - Uri get uri => Uri.https(baseUrl, '', { - 'apiKey': secrets.onramperApiKey, - 'defaultCrypto': wallet.currency.title, - 'defaultFiat': settingsStore.fiatCurrency.title, - 'wallets': '${wallet.currency.title}:${wallet.walletAddresses.address}', - 'darkMode': darkMode.toString(), - 'supportSell': supportSell.toString(), - 'supportSwap': supportSwap.toString() - }); + Uri get uri => onRamperBuyProvider.requestUrl(); @override OnRamperPageBodyState createState() => OnRamperPageBodyState(); diff --git a/lib/src/screens/buy/payfura_page.dart b/lib/src/screens/buy/payfura_page.dart index 4bada4a9e..a974aec25 100644 --- a/lib/src/screens/buy/payfura_page.dart +++ b/lib/src/screens/buy/payfura_page.dart @@ -1,45 +1,30 @@ +import 'package:cake_wallet/buy/payfura/payfura_buy_provider.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/src/screens/base_page.dart'; -import 'package:cake_wallet/store/settings_store.dart'; -import 'package:cw_core/wallet_base.dart'; import 'package:flutter/material.dart'; -import 'package:cake_wallet/.secrets.g.dart' as secrets; import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import 'package:permission_handler/permission_handler.dart'; class PayFuraPage extends BasePage { - PayFuraPage({required this.settingsStore, required this.wallet}); + PayFuraPage(this._PayfuraBuyProvider); - final SettingsStore settingsStore; - final WalletBase wallet; + final PayfuraBuyProvider _PayfuraBuyProvider; @override String get title => S.current.buy; @override Widget body(BuildContext context) { - return PayFuraPageBody( - settingsStore: settingsStore, - wallet: wallet); + return PayFuraPageBody(_PayfuraBuyProvider); } } class PayFuraPageBody extends StatefulWidget { - PayFuraPageBody( - {required this.settingsStore, - required this.wallet}); + PayFuraPageBody(this._PayfuraBuyProvider); - static const baseUrl = 'exchange.payfura.com'; - final SettingsStore settingsStore; - final WalletBase wallet; + final PayfuraBuyProvider _PayfuraBuyProvider; - Uri get uri => Uri.https(baseUrl, '', { - 'apiKey': secrets.payfuraApiKey, - 'to': wallet.currency.title, - 'from': settingsStore.fiatCurrency.title, - 'walletAddress': '${wallet.currency.title}:${wallet.walletAddresses.address}', - 'mode': 'buy' - }); + Uri get uri => _PayfuraBuyProvider.requestUrl(); @override PayFuraPageBodyState createState() => PayFuraPageBodyState(); diff --git a/lib/src/screens/contact/contact_list_page.dart b/lib/src/screens/contact/contact_list_page.dart index 0065b9281..f8ccf5807 100644 --- a/lib/src/screens/contact/contact_list_page.dart +++ b/lib/src/screens/contact/contact_list_page.dart @@ -24,10 +24,6 @@ class ContactListPage extends BasePage { @override Widget? trailing(BuildContext context) { - if (!contactListViewModel.isEditable) { - return null; - } - return Container( width: 32.0, height: 32.0, diff --git a/lib/src/screens/dashboard/dashboard_page.dart b/lib/src/screens/dashboard/dashboard_page.dart index b0df7f36c..d0e947508 100644 --- a/lib/src/screens/dashboard/dashboard_page.dart +++ b/lib/src/screens/dashboard/dashboard_page.dart @@ -1,12 +1,15 @@ 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_sidebar_wrapper.dart'; import 'package:cake_wallet/src/screens/dashboard/widgets/market_place_page.dart'; import 'package:cake_wallet/wallet_type_utils.dart'; -import 'package:cw_core/wallet_type.dart'; import 'package:cake_wallet/generated/i18n.dart'; 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/responsive_layout_util.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'; @@ -21,17 +24,41 @@ import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:mobx/mobx.dart'; import 'package:smooth_page_indicator/smooth_page_indicator.dart'; import 'package:cake_wallet/main.dart'; -import 'package:cake_wallet/buy/moonpay/moonpay_buy_provider.dart'; -import 'package:url_launcher/url_launcher.dart'; -class DashboardPage extends BasePage { +class DashboardPage extends StatelessWidget { DashboardPage({ required this.balancePage, - required this.walletViewModel, + required this.dashboardViewModel, required this.addressListViewModel, }); + final BalancePage balancePage; - + final DashboardViewModel dashboardViewModel; + final WalletAddressListViewModel addressListViewModel; + + @override + Widget build(BuildContext context) { + return Scaffold( + body: ResponsiveLayoutUtil.instance.isMobile(context) + ? _DashboardPageView( + balancePage: balancePage, + dashboardViewModel: dashboardViewModel, + addressListViewModel: addressListViewModel, + ) + : getIt.get(), + ); + } +} + +class _DashboardPageView extends BasePage { + _DashboardPageView({ + required this.balancePage, + required this.dashboardViewModel, + required this.addressListViewModel, + }); + + final BalancePage balancePage; + @override Color get backgroundLightColor => currentTheme.type == ThemeType.bright ? Colors.transparent : Colors.white; @@ -54,19 +81,19 @@ class DashboardPage extends BasePage { bool get resizeToAvoidBottomInset => false; @override - Widget get endDrawer => MenuWidget(walletViewModel); + Widget get endDrawer => MenuWidget(dashboardViewModel); @override Widget middle(BuildContext context) { - return SyncIndicator(dashboardViewModel: walletViewModel, - onTap: () => Navigator.of(context, rootNavigator: true) - .pushNamed(Routes.connectionSync)); + return SyncIndicator( + dashboardViewModel: dashboardViewModel, + 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, @@ -80,7 +107,7 @@ class DashboardPage extends BasePage { child: menuButton)); } - final DashboardViewModel walletViewModel; + final DashboardViewModel dashboardViewModel; final WalletAddressListViewModel addressListViewModel; final controller = PageController(initialPage: 1); @@ -90,141 +117,97 @@ class DashboardPage extends BasePage { @override Widget body(BuildContext context) { - final sendImage = Image.asset('assets/images/upload.png', - height: 24, - width: 24, - 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!); _setEffects(context); return SafeArea( - minimum: EdgeInsets.only(bottom: 24), + 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!), + 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( - 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) - .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), - ], - ),), - ),),); - }), - - ], - )); + 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(dashboardViewModel) ?? true) + .map((action) => ActionButton( + image: Image.asset(action.image, + height: 24, + width: 24, + color: action.isEnabled?.call(dashboardViewModel) ?? true + ? Theme.of(context) + .accentTextTheme + .headline2! + .backgroundColor! + : Theme.of(context) + .accentTextTheme + .headline3! + .backgroundColor!), + title: action.name(context), + onClick: () async => await action.onTap(context, dashboardViewModel), + textColor: action.isEnabled?.call(dashboardViewModel) ?? true + ? null + : Theme.of(context) + .accentTextTheme + .headline3! + .backgroundColor!, + )) + .toList(), + ), + ), + ), + ), + ); + }), + ], + )); } void _setEffects(BuildContext context) async { if (_isEffectsInstalled) { return; } - pages.add(MarketPlacePage(dashboardViewModel: walletViewModel)); + pages.add(MarketPlacePage(dashboardViewModel: dashboardViewModel)); pages.add(balancePage); - pages.add(TransactionsPage(dashboardViewModel: walletViewModel)); + pages.add(TransactionsPage(dashboardViewModel: dashboardViewModel)); _isEffectsInstalled = true; autorun((_) async { - if (!walletViewModel.isOutdatedElectrumWallet) { + if (!dashboardViewModel.isOutdatedElectrumWallet) { return; } @@ -235,8 +218,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()); }); @@ -253,13 +235,13 @@ class DashboardPage extends BasePage { Future.delayed(Duration(milliseconds: 500)).then((_) { showPopUp( context: navigatorKey.currentContext!, - builder: (_) => YatEmojiId(walletViewModel.yatStore.emoji)); + builder: (_) => YatEmojiId(dashboardViewModel.yatStore.emoji)); needToPresentYat = false; }); } }); - walletViewModel.yatStore.emojiIncommingStream.listen((String emoji) { + dashboardViewModel.yatStore.emojiIncommingStream.listen((String emoji) { if (!_isEffectsInstalled || emoji.isEmpty) { return; } @@ -267,62 +249,4 @@ class DashboardPage extends BasePage { needToPresentYat = true; }); } - - Future _onClickBuyButton(BuildContext context) async { - final walletType = walletViewModel.type; - - switch (walletType) { - case WalletType.bitcoin: - Navigator.of(context).pushNamed(Routes.onramperPage); - break; - case WalletType.litecoin: - Navigator.of(context).pushNamed(Routes.onramperPage); - break; - case WalletType.monero: - Navigator.of(context).pushNamed(Routes.payfuraPage); - 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()); - }); - } - } - - Future _onClickSellButton(BuildContext context) async { - final walletType = walletViewModel.type; - - switch (walletType) { - case WalletType.bitcoin: - final moonPaySellProvider = MoonPaySellProvider(); - final uri = await moonPaySellProvider.requestUrl( - currency: walletViewModel.wallet.currency, - refundWalletAddress: - walletViewModel.wallet.walletAddresses.address); - await launch(uri); - break; - default: - await showPopUp( - context: context, - builder: (BuildContext context) { - return AlertWithOneAction( - alertTitle: S.of(context).sell, - alertContent: isMoneroOnly ? S.of(context).sell_monero_com_alert_content - : S.of(context).sell_alert_content, - buttonText: S.of(context).ok, - buttonAction: () => Navigator.of(context).pop()); - }); - } - } - - Future _onClickExchangeButton(BuildContext context) async { - if (walletViewModel.isEnabledExchangeAction) { - await Navigator.of(context).pushNamed(Routes.exchange); - } - } } diff --git a/lib/src/screens/dashboard/desktop_dashboard_page.dart b/lib/src/screens/dashboard/desktop_dashboard_page.dart new file mode 100644 index 000000000..64f8a9aac --- /dev/null +++ b/lib/src/screens/dashboard/desktop_dashboard_page.dart @@ -0,0 +1,111 @@ +import 'dart:async'; +import 'package:cake_wallet/generated/i18n.dart'; +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/utils/show_pop_up.dart'; +import 'package:flutter/material.dart'; +import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; +import 'package:cake_wallet/src/screens/dashboard/widgets/balance_page.dart'; +import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart'; +import 'package:mobx/mobx.dart'; +import 'package:cake_wallet/main.dart'; +import 'package:cake_wallet/router.dart' as Router; + +class DesktopDashboardPage extends StatelessWidget { + DesktopDashboardPage({ + required this.balancePage, + required this.dashboardViewModel, + required this.addressListViewModel, + required this.desktopKey, + }); + + final BalancePage balancePage; + final DashboardViewModel dashboardViewModel; + final WalletAddressListViewModel addressListViewModel; + final GlobalKey desktopKey; + + bool _isEffectsInstalled = false; + StreamSubscription? _onInactiveSub; + + @override + Widget build(BuildContext context) { + _setEffects(context); + + return Container( + color: Theme.of(context).backgroundColor, + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: 400, + child: balancePage, + ), + Flexible( + child: ConstrainedBox( + constraints: BoxConstraints(maxWidth: 500), + child: Navigator( + key: desktopKey, + initialRoute: Routes.desktop_actions, + onGenerateRoute: (settings) => Router.createRoute(settings), + onGenerateInitialRoutes: (NavigatorState navigator, String initialRouteName) { + return [ + navigator.widget.onGenerateRoute!(RouteSettings(name: initialRouteName))! + ]; + }, + ), + ), + ), + ], + ), + ); + } + + void _setEffects(BuildContext context) async { + if (_isEffectsInstalled) { + return; + } + _isEffectsInstalled = true; + + autorun((_) async { + if (!dashboardViewModel.isOutdatedElectrumWallet) { + return; + } + + await Future.delayed(Duration(seconds: 1)); + await showPopUp( + context: context, + builder: (BuildContext context) { + return AlertWithOneAction( + alertTitle: S.of(context).pre_seed_title, + alertContent: S.of(context).outdated_electrum_wallet_description, + buttonText: S.of(context).understand, + buttonAction: () => Navigator.of(context).pop()); + }); + }); + + var needToPresentYat = false; + var isInactive = false; + + _onInactiveSub = rootKey.currentState!.isInactive.listen((inactive) { + isInactive = inactive; + + if (needToPresentYat) { + Future.delayed(Duration(milliseconds: 500)).then((_) { + showPopUp( + context: navigatorKey.currentContext!, + builder: (_) => YatEmojiId(dashboardViewModel.yatStore.emoji)); + needToPresentYat = false; + }); + } + }); + + dashboardViewModel.yatStore.emojiIncommingStream.listen((String emoji) { + if (!_isEffectsInstalled || emoji.isEmpty) { + return; + } + + needToPresentYat = true; + }); + } +} 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..0e3588a17 --- /dev/null +++ b/lib/src/screens/dashboard/desktop_widgets/desktop_action_button.dart @@ -0,0 +1,69 @@ +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: GestureDetector( + onTap: onTap, + 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..e5fa42390 --- /dev/null +++ b/lib/src/screens/dashboard/desktop_widgets/desktop_dashboard_actions.dart @@ -0,0 +1,80 @@ +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/src/screens/dashboard/widgets/market_place_page.dart'; +import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; + +class DesktopDashboardActions extends StatelessWidget { + final DashboardViewModel dashboardViewModel; + + const DesktopDashboardActions(this.dashboardViewModel, {Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Observer( + builder: (_) { + 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), + ), + ), + ], + ), + Expanded( + child: MarketPlacePage(dashboardViewModel: dashboardViewModel), + ), + ], + ); + } + ); + } +} diff --git a/lib/src/screens/dashboard/desktop_widgets/desktop_dashboard_navbar.dart b/lib/src/screens/dashboard/desktop_widgets/desktop_dashboard_navbar.dart new file mode 100644 index 000000000..b97def132 --- /dev/null +++ b/lib/src/screens/dashboard/desktop_widgets/desktop_dashboard_navbar.dart @@ -0,0 +1,48 @@ +import 'package:cake_wallet/di.dart'; +import 'package:cake_wallet/store/settings_store.dart'; +import 'package:cake_wallet/themes/theme_base.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +class DesktopDashboardNavbar extends StatelessWidget implements ObstructingPreferredSizeWidget { + final Widget leading; + final Widget middle; + final Widget trailing; + + DesktopDashboardNavbar({ + super.key, + required this.leading, + required this.middle, + required this.trailing, + }); + + ThemeBase get currentTheme => getIt.get().currentTheme; + + @override + Widget build(BuildContext context) { + final appBarColor = + currentTheme.type == ThemeType.dark ? Colors.black.withOpacity(0.1) : Colors.white; + + return Container( + padding: const EdgeInsetsDirectional.only(end: 24), + color: appBarColor, + child: Center( + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible(child: leading), + middle, + trailing, + ], + ), + ), + ); + } + + @override + Size get preferredSize => Size.fromHeight(60); + + @override + bool shouldFullyObstruct(BuildContext context) => false; +} diff --git a/lib/src/screens/dashboard/desktop_widgets/desktop_sidebar/side_menu.dart b/lib/src/screens/dashboard/desktop_widgets/desktop_sidebar/side_menu.dart new file mode 100644 index 000000000..bc7c0af84 --- /dev/null +++ b/lib/src/screens/dashboard/desktop_widgets/desktop_sidebar/side_menu.dart @@ -0,0 +1,32 @@ +import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_sidebar/side_menu_item.dart'; +import 'package:flutter/material.dart'; + +class SideMenu extends StatelessWidget { + const SideMenu({ + super.key, + required this.topItems, + required this.bottomItems, + required this.width, + }); + + final List topItems; + final List bottomItems; + final double width; + + @override + Widget build(BuildContext context) { + return Container( + color: Colors.black.withOpacity(0.1), + width: width, + height: MediaQuery.of(context).size.height, + child: Column( + children: [ + ...topItems, + Spacer(), + ...bottomItems, + SizedBox(height: 30), + ], + ), + ); + } +} diff --git a/lib/src/screens/dashboard/desktop_widgets/desktop_sidebar/side_menu_item.dart b/lib/src/screens/dashboard/desktop_widgets/desktop_sidebar/side_menu_item.dart new file mode 100644 index 000000000..42f4b0e4e --- /dev/null +++ b/lib/src/screens/dashboard/desktop_widgets/desktop_sidebar/side_menu_item.dart @@ -0,0 +1,50 @@ +import 'package:flutter/material.dart'; + +class SideMenuItem extends StatelessWidget { + const SideMenuItem({ + Key? key, + required this.onTap, + this.imagePath, + this.icon, + this.isSelected = false, + }) : assert((icon != null && imagePath == null) || (icon == null && imagePath != null)); + + final void Function() onTap; + final String? imagePath; + final IconData? icon; + final bool isSelected; + + Color _setColor(BuildContext context) { + if (isSelected) { + return Theme.of(context).primaryTextTheme.headline6!.color!; + } else { + return Theme.of(context).highlightColor; + } + } + + @override + Widget build(BuildContext context) { + return InkWell( + child: Padding( + padding: EdgeInsets.all(20), + child: icon != null + ? Icon( + icon, + color: _setColor(context), + ) + : Image.asset( + imagePath ?? '', + fit: BoxFit.cover, + height: 30, + width: 30, + color: _setColor(context), + ), + ), + onTap: () => onTap.call(), + highlightColor: Colors.transparent, + focusColor: Colors.transparent, + hoverColor: Colors.transparent, + splashColor: Colors.transparent, + ); + } +} diff --git a/lib/src/screens/dashboard/desktop_widgets/desktop_sidebar_wrapper.dart b/lib/src/screens/dashboard/desktop_widgets/desktop_sidebar_wrapper.dart new file mode 100644 index 000000000..72e65da34 --- /dev/null +++ b/lib/src/screens/dashboard/desktop_widgets/desktop_sidebar_wrapper.dart @@ -0,0 +1,175 @@ +import 'package:cake_wallet/di.dart'; +import 'package:cake_wallet/routes.dart'; +import 'package:cake_wallet/src/screens/auth/auth_page.dart'; +import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_dashboard_navbar.dart'; +import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_sidebar/side_menu.dart'; +import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_sidebar/side_menu_item.dart'; +import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart'; +import 'package:cake_wallet/src/screens/dashboard/widgets/sync_indicator.dart'; +import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; +import 'package:cake_wallet/view_model/dashboard/desktop_sidebar_view_model.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; +import 'package:cake_wallet/router.dart' as Router; +import 'package:mobx/mobx.dart'; + +class DesktopSidebarWrapper extends BasePage { + final Widget child; + final DesktopSidebarViewModel desktopSidebarViewModel; + final DashboardViewModel dashboardViewModel; + final GlobalKey desktopNavigatorKey; + + DesktopSidebarWrapper({ + required this.child, + required this.desktopSidebarViewModel, + required this.dashboardViewModel, + required this.desktopNavigatorKey, + }); + + @override + ObstructingPreferredSizeWidget appBar(BuildContext context) => DesktopDashboardNavbar( + leading: Padding( + padding: EdgeInsets.only(left: sideMenuWidth), + child: getIt(), + ), + middle: SyncIndicator( + dashboardViewModel: dashboardViewModel, + onTap: () => Navigator.of(context, rootNavigator: true).pushNamed(Routes.connectionSync), + ), + trailing: InkWell( + onTap: () { + Navigator.of(context).pushNamed( + Routes.unlock, + arguments: (bool isAuthenticatedSuccessfully, AuthPageState auth) { + if (isAuthenticatedSuccessfully) { + auth.close(); + } + }, + ); + }, + child: Icon(Icons.lock_outline), + ), + ); + + @override + bool get resizeToAvoidBottomInset => false; + + final pageController = PageController(); + + final selectedIconPath = 'assets/images/desktop_transactions_solid_icon.png'; + final unselectedIconPath = 'assets/images/desktop_transactions_outline_icon.png'; + + double get sideMenuWidth => 76.0; + + @override + Widget body(BuildContext context) { + _setEffects(); + + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Observer(builder: (_) { + return SideMenu( + width: sideMenuWidth, + topItems: [ + SideMenuItem( + imagePath: 'assets/images/wallet_outline.png', + isSelected: desktopSidebarViewModel.currentPage == SidebarItem.dashboard, + onTap: () => desktopSidebarViewModel.onPageChange(SidebarItem.dashboard), + ), + SideMenuItem( + onTap: () { + String? currentPath; + + desktopNavigatorKey.currentState?.popUntil((route) { + currentPath = route.settings.name; + return true; + }); + + switch (currentPath) { + case Routes.transactionsPage: + desktopSidebarViewModel.resetSidebar(); + break; + default: + desktopSidebarViewModel.resetSidebar(); + Future.delayed(Duration(milliseconds: 10), () { + desktopSidebarViewModel.onPageChange(SidebarItem.transactions); + desktopNavigatorKey.currentState?.pushNamed(Routes.transactionsPage); + }); + } + }, + isSelected: desktopSidebarViewModel.currentPage == SidebarItem.transactions, + imagePath: desktopSidebarViewModel.currentPage == SidebarItem.transactions + ? selectedIconPath + : unselectedIconPath, + ), + ], + bottomItems: [ + SideMenuItem( + imagePath: 'assets/images/support_icon.png', + isSelected: desktopSidebarViewModel.currentPage == SidebarItem.support, + onTap: () => desktopSidebarViewModel.onPageChange(SidebarItem.support)), + SideMenuItem( + imagePath: 'assets/images/settings_outline.png', + isSelected: desktopSidebarViewModel.currentPage == SidebarItem.settings, + onTap: () => desktopSidebarViewModel.onPageChange(SidebarItem.settings), + ), + ], + ); + }), + Expanded( + child: PageView( + controller: pageController, + physics: NeverScrollableScrollPhysics(), + children: [ + child, + Container( + color: Theme.of(context).backgroundColor, + padding: EdgeInsets.all(20), + child: Navigator( + initialRoute: Routes.support, + onGenerateRoute: (settings) => Router.createRoute(settings), + onGenerateInitialRoutes: (NavigatorState navigator, String initialRouteName) { + return [ + navigator.widget.onGenerateRoute!(RouteSettings(name: initialRouteName))! + ]; + }, + ), + ), + Navigator( + initialRoute: Routes.desktop_settings_page, + onGenerateRoute: (settings) => Router.createRoute(settings), + onGenerateInitialRoutes: (NavigatorState navigator, String initialRouteName) { + return [ + navigator.widget.onGenerateRoute!(RouteSettings(name: initialRouteName))! + ]; + }, + ), + ], + ), + ), + ], + ); + } + + void _setEffects() async { + reaction((_) => desktopSidebarViewModel.currentPage, (page) { + String? currentPath; + + desktopNavigatorKey.currentState?.popUntil((route) { + currentPath = route.settings.name; + return true; + }); + if (page == SidebarItem.transactions) { + return; + } + + if (currentPath == Routes.transactionsPage) { + Navigator.of(desktopNavigatorKey.currentContext!).pop(); + } + pageController.jumpToPage(page.index); + }); + } +} 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..1ad831b1b --- /dev/null +++ b/lib/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart @@ -0,0 +1,199 @@ +import 'package:another_flushbar/flushbar.dart'; +import 'package:cake_wallet/entities/desktop_dropdown_item.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/screens/dashboard/desktop_widgets/dropdown_item_widget.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:cake_wallet/wallet_type_utils.dart'; +import 'package:cw_core/wallet_type.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_mobx/flutter_mobx.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); + + Image _newWalletImage(BuildContext context) => Image.asset( + 'assets/images/new_wallet.png', + height: 12, + width: 12, + color: Theme.of(context).primaryTextTheme.headline6!.color!, + ); + + Image _restoreWalletImage(BuildContext context) => Image.asset( + 'assets/images/restore_wallet.png', + height: 12, + width: 12, + color: Theme.of(context).primaryTextTheme.headline6!.color!, + ); + + Flushbar? _progressBar; + + @override + Widget build(BuildContext context) { + final themeData = Theme.of(context); + return Observer(builder: (context) { + final dropDownItems = [ + ...widget.walletListViewModel.wallets + .map((wallet) => DesktopDropdownItem( + isSelected: wallet.isCurrent, + child: ConstrainedBox( + constraints: BoxConstraints(maxWidth: 500), + child: DropDownItemWidget( + title: wallet.name, + image: wallet.isEnabled ? _imageFor(type: wallet.type) : nonWalletTypeIcon), + ), + onSelected: () => _onSelectedWallet(wallet), + )) + .toList(), + DesktopDropdownItem( + onSelected: () => _navigateToCreateWallet(), + child: DropDownItemWidget( + title: S.of(context).create_new, + image: _newWalletImage(context), + ), + ), + DesktopDropdownItem( + onSelected: () => _navigateToRestoreWallet(), + child: DropDownItemWidget( + title: S.of(context).restore_wallet, + image: _restoreWalletImage(context), + ), + ), + ]; + + return DropdownButton( + items: dropDownItems + .map( + (wallet) => DropdownMenuItem( + child: wallet.child, + value: wallet, + ), + ) + .toList(), + onChanged: (item) { + item?.onSelected(); + }, + dropdownColor: themeData.textTheme.bodyText1?.decorationColor, + style: TextStyle(color: themeData.primaryTextTheme.headline6?.color), + selectedItemBuilder: (context) => dropDownItems.map((item) => item.child).toList(), + value: dropDownItems.firstWhere((element) => element.isSelected), + underline: const SizedBox(), + focusColor: Colors.transparent, + borderRadius: BorderRadius.circular(15.0), + ); + }); + } + + void _onSelectedWallet(WalletListItem 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); + } + } + + 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 _navigateToCreateWallet() { + if (isSingleCoin) { + Navigator.of(context) + .pushNamed(Routes.newWallet, arguments: widget.walletListViewModel.currentWalletType); + } else { + Navigator.of(context).pushNamed(Routes.newWalletType); + } + } + + void _navigateToRestoreWallet() { + if (isSingleCoin) { + Navigator.of(context) + .pushNamed(Routes.restoreWallet, arguments: widget.walletListViewModel.currentWalletType); + } else { + Navigator.of(context).pushNamed(Routes.restoreWalletType); + } + } + + void changeProcessText(String text) { + _progressBar = createBar(text, duration: null)..show(context); + } + + void hideProgressText() { + _progressBar?.dismiss(); + _progressBar = null; + } +} diff --git a/lib/src/screens/dashboard/desktop_widgets/dropdown_item_widget.dart b/lib/src/screens/dashboard/desktop_widgets/dropdown_item_widget.dart new file mode 100644 index 000000000..47efd04a7 --- /dev/null +++ b/lib/src/screens/dashboard/desktop_widgets/dropdown_item_widget.dart @@ -0,0 +1,36 @@ +import 'package:flutter/material.dart'; + +class DropDownItemWidget extends StatelessWidget { + const DropDownItemWidget({super.key, required this.title, required this.image}); + final double tileHeight = 60; + final Image image; + final String title; + + @override + Widget build(BuildContext context) { + return Container( + height: tileHeight, + padding: EdgeInsets.symmetric(horizontal: 20), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + image, + SizedBox(width: 10), + Flexible( + child: Text( + title, + style: TextStyle( + fontSize: 22, + fontWeight: FontWeight.w500, + color: Theme.of(context).primaryTextTheme.headline6!.color!, + ), + overflow: TextOverflow.ellipsis, + maxLines: 1, + ), + ) + ], + ), + ); + } +} diff --git a/lib/src/screens/dashboard/wallet_menu.dart b/lib/src/screens/dashboard/wallet_menu.dart deleted file mode 100644 index 1638c0bc4..000000000 --- a/lib/src/screens/dashboard/wallet_menu.dart +++ /dev/null @@ -1,74 +0,0 @@ -import 'package:cake_wallet/palette.dart'; -import 'package:cake_wallet/src/screens/dashboard/wallet_menu_item.dart'; -import 'package:flutter/material.dart'; -import 'package:cake_wallet/routes.dart'; -import 'package:cake_wallet/generated/i18n.dart'; - -// FIXME: terrible design - -class WalletMenu { - WalletMenu(this.context, this.reconnect, this.hasRescan) : items = [] { - items.addAll([ - WalletMenuItem( - title: S.current.connection_sync, - image: Image.asset('assets/images/nodes_menu.png', - height: 16, width: 16), - handler: () => Navigator.of(context).pushNamed(Routes.connectionSync), - ), - WalletMenuItem( - title: S.current.wallets, - image: Image.asset('assets/images/wallet_menu.png', - height: 16, width: 16), - handler: () => Navigator.of(context).pushNamed(Routes.walletList), - ), - WalletMenuItem( - title: S.current.address_book_menu, - image: Image.asset('assets/images/open_book_menu.png', - height: 16, width: 16), - handler: () => Navigator.of(context).pushNamed(Routes.addressBook), - ), - WalletMenuItem( - title: S.current.security_and_backup, - image: - Image.asset('assets/images/key_menu.png', height: 16, width: 16), - handler: () { - Navigator.of(context).pushNamed(Routes.securityBackupPage); - }), - WalletMenuItem( - title: S.current.privacy_settings, - image: - Image.asset('assets/images/privacy_menu.png', height: 16, width: 16), - handler: () { - Navigator.of(context).pushNamed(Routes.privacyPage); - }), - WalletMenuItem( - title: S.current.display_settings, - image: Image.asset('assets/images/eye_menu.png', - height: 16, width: 16), - handler: () => Navigator.of(context).pushNamed(Routes.displaySettingsPage), - ), - WalletMenuItem( - title: S.current.other_settings, - image: Image.asset('assets/images/settings_menu.png', - height: 16, width: 16), - handler: () => Navigator.of(context).pushNamed(Routes.otherSettingsPage), - ), - WalletMenuItem( - title: S.current.settings_support, - image: Image.asset('assets/images/question_mark.png', - height: 16, width: 16, color: Palette.darkBlue), - handler: () => Navigator.of(context).pushNamed(Routes.support), - ), - ]); - } - - final List items; - final BuildContext context; - final Future Function() reconnect; - final bool hasRescan; - - void action(int index) { - final item = items[index]; - item.handler(); - } -} diff --git a/lib/src/screens/dashboard/wallet_menu_item.dart b/lib/src/screens/dashboard/wallet_menu_item.dart deleted file mode 100644 index 31a11f31e..000000000 --- a/lib/src/screens/dashboard/wallet_menu_item.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'package:flutter/material.dart'; - -class WalletMenuItem { - WalletMenuItem({ - required this.title, - required this.image, - required this.handler}); - - final String title; - final Image image; - final void Function() handler; -} \ No newline at end of file diff --git a/lib/src/screens/dashboard/widgets/address_page.dart b/lib/src/screens/dashboard/widgets/address_page.dart index 5de8124c4..ebfabcd02 100644 --- a/lib/src/screens/dashboard/widgets/address_page.dart +++ b/lib/src/screens/dashboard/widgets/address_page.dart @@ -24,12 +24,12 @@ import 'package:cake_wallet/di.dart'; class AddressPage extends BasePage { AddressPage({ required this.addressListViewModel, - required this.walletViewModel, + required this.dashboardViewModel, required this.receiveOptionViewModel, }) : _cryptoAmountFocus = FocusNode(); final WalletAddressListViewModel addressListViewModel; - final DashboardViewModel walletViewModel; + final DashboardViewModel dashboardViewModel; final ReceiveOptionViewModel receiveOptionViewModel; final FocusNode _cryptoAmountFocus; @@ -47,28 +47,10 @@ class AddressPage extends BasePage { bool effectsInstalled = false; @override - Widget leading(BuildContext context) { - final _backButton = Icon( - Icons.arrow_back_ios, - color: Theme.of(context).accentTextTheme.headline2!.backgroundColor!, - size: 16, - ); + Color get titleColor => Colors.white; - return SizedBox( - height: 37, - width: 37, - child: ButtonTheme( - minWidth: double.minPositive, - child: TextButton( - // FIX-ME: Style - //highlightColor: Colors.transparent, - //splashColor: Colors.transparent, - //padding: EdgeInsets.all(0), - onPressed: () => onClose(context), - child: _backButton), - ), - ); - } + @override + bool get canUseCloseIcon => true; @override Widget middle(BuildContext context) => @@ -116,8 +98,8 @@ class AddressPage extends BasePage { _setEffects(context); autorun((_) async { - if (!walletViewModel.isOutdatedElectrumWallet || - !walletViewModel.settingsStore.shouldShowReceiveWarning) { + if (!dashboardViewModel.isOutdatedElectrumWallet || + !dashboardViewModel.settingsStore.shouldShowReceiveWarning) { return; } @@ -133,7 +115,7 @@ class AddressPage extends BasePage { actionLeftButton: () => Navigator.of(context).pop(), rightButtonText: S.of(context).do_not_show_me, actionRightButton: () { - walletViewModel.settingsStore.setShouldShowReceiveWarning(false); + dashboardViewModel.settingsStore.setShouldShowReceiveWarning(false); Navigator.of(context).pop(); }); }); @@ -163,7 +145,7 @@ class AddressPage extends BasePage { addressListViewModel: addressListViewModel, amountTextFieldFocusNode: _cryptoAmountFocus, isAmountFieldShow: !addressListViewModel.hasAccounts, - isLight: walletViewModel.settingsStore.currentTheme.type == ThemeType.light)) + isLight: dashboardViewModel.settingsStore.currentTheme.type == ThemeType.light)) ), Observer(builder: (_) { return addressListViewModel.hasAddressList @@ -222,7 +204,6 @@ class AddressPage extends BasePage { } reaction((_) => receiveOptionViewModel.selectedReceiveOption, (ReceivePageOption option) { - Navigator.pop(context); switch (option) { case ReceivePageOption.anonPayInvoice: Navigator.pushReplacementNamed( diff --git a/lib/src/screens/dashboard/widgets/balance_page.dart b/lib/src/screens/dashboard/widgets/balance_page.dart index 48eaa0a3e..bf8b1ae17 100644 --- a/lib/src/screens/dashboard/widgets/balance_page.dart +++ b/lib/src/screens/dashboard/widgets/balance_page.dart @@ -1,10 +1,8 @@ -import 'package:cake_wallet/di.dart'; -import 'package:cake_wallet/src/widgets/standard_list.dart'; import 'package:cake_wallet/store/settings_store.dart'; import 'package:cake_wallet/themes/theme_base.dart'; +import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:flutter/material.dart'; import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; -import 'package:flutter/scheduler.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:auto_size_text/auto_size_text.dart'; import 'package:cake_wallet/src/widgets/introducing_card.dart'; @@ -16,9 +14,6 @@ class BalancePage extends StatelessWidget{ final DashboardViewModel dashboardViewModel; final SettingsStore settingsStore; - - Color get backgroundLightColor => - settingsStore.currentTheme.type == ThemeType.bright ? Colors.transparent : Colors.white; @override Widget build(BuildContext context) { @@ -29,7 +24,7 @@ class BalancePage extends StatelessWidget{ child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - SizedBox(height: 56), + SizedBox(height: ResponsiveLayoutUtil.instance.isMobile(context) ? 56 : 16), Container( margin: const EdgeInsets.only(left: 24, bottom: 16), child: Observer(builder: (_) { diff --git a/lib/src/screens/dashboard/widgets/menu_widget.dart b/lib/src/screens/dashboard/widgets/menu_widget.dart index 87bf956b5..17acdab87 100644 --- a/lib/src/screens/dashboard/widgets/menu_widget.dart +++ b/lib/src/screens/dashboard/widgets/menu_widget.dart @@ -1,10 +1,9 @@ -import 'dart:ui'; +import 'package:cake_wallet/src/widgets/setting_action_button.dart'; +import 'package:cake_wallet/src/widgets/setting_actions.dart'; import 'package:flutter/material.dart'; import 'package:cake_wallet/palette.dart'; import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; import 'package:cw_core/wallet_type.dart'; -import 'package:cake_wallet/src/screens/dashboard/wallet_menu.dart'; -import 'package:flutter/rendering.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; // FIXME: terrible design. @@ -19,18 +18,18 @@ class MenuWidget extends StatefulWidget { } class MenuWidgetState extends State { - MenuWidgetState() - : this.menuWidth = 0, - this.screenWidth = 0, - this.screenHeight = 0, - this.headerHeight = 120, - this.tileHeight = 60, - this.fromTopEdge = 50, - this.fromBottomEdge = 25, - this.moneroIcon = Image.asset('assets/images/monero_menu.png'), - this.bitcoinIcon = Image.asset('assets/images/bitcoin_menu.png'), - this.litecoinIcon = Image.asset('assets/images/litecoin_menu.png'), - this.havenIcon = Image.asset('assets/images/haven_menu.png'); + MenuWidgetState() + : this.menuWidth = 0, + this.screenWidth = 0, + this.screenHeight = 0, + this.headerHeight = 120, + this.tileHeight = 60, + this.fromTopEdge = 50, + this.fromBottomEdge = 25, + this.moneroIcon = Image.asset('assets/images/monero_menu.png'), + this.bitcoinIcon = Image.asset('assets/images/bitcoin_menu.png'), + this.litecoinIcon = Image.asset('assets/images/litecoin_menu.png'), + this.havenIcon = Image.asset('assets/images/haven_menu.png'); final largeScreen = 731; @@ -82,16 +81,12 @@ class MenuWidgetState extends State { @override Widget build(BuildContext context) { - final walletMenu = WalletMenu( - context, - () async => widget.dashboardViewModel.reconnect(), - widget.dashboardViewModel.hasRescan); - final itemCount = walletMenu.items.length; + final itemCount = SettingActions.all.length; moneroIcon = Image.asset('assets/images/monero_menu.png', - color: Theme.of(context).accentTextTheme!.overline!.decorationColor!); + color: Theme.of(context).accentTextTheme.overline!.decorationColor!); bitcoinIcon = Image.asset('assets/images/bitcoin_menu.png', - color: Theme.of(context).accentTextTheme!.overline!.decorationColor!); + color: Theme.of(context).accentTextTheme.overline!.decorationColor!); litecoinIcon = Image.asset('assets/images/litecoin_menu.png'); havenIcon = Image.asset('assets/images/haven_menu.png'); @@ -105,17 +100,15 @@ class MenuWidgetState extends State { height: 60, width: 4, decoration: BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(2)), - color: PaletteDark.gray), + borderRadius: BorderRadius.all(Radius.circular(2)), color: PaletteDark.gray), )), SizedBox(width: 12), Expanded( child: ClipRRect( borderRadius: BorderRadius.only( - topLeft: Radius.circular(24), - bottomLeft: Radius.circular(24)), + topLeft: Radius.circular(24), bottomLeft: Radius.circular(24)), child: Container( - color: Theme.of(context).textTheme!.bodyText1!.decorationColor!, + color: Theme.of(context).textTheme.bodyText1!.decorationColor!, child: ListView.separated( padding: EdgeInsets.only(top: 0), itemBuilder: (_, index) { @@ -123,25 +116,13 @@ class MenuWidgetState extends State { return Container( height: headerHeight, decoration: BoxDecoration( - gradient: LinearGradient( - colors: [ - Theme.of(context) - .accentTextTheme! - .headline4! - .color!, - Theme.of(context) - .accentTextTheme! - .headline4! - .decorationColor!, - ], - begin: Alignment.topLeft, - end: Alignment.bottomRight), + gradient: LinearGradient(colors: [ + Theme.of(context).accentTextTheme.headline4!.color!, + Theme.of(context).accentTextTheme.headline4!.decorationColor!, + ], begin: Alignment.topLeft, end: Alignment.bottomRight), ), padding: EdgeInsets.only( - left: 24, - top: fromTopEdge, - right: 24, - bottom: fromBottomEdge), + left: 24, top: fromTopEdge, right: 24, bottom: fromBottomEdge), child: Row( mainAxisAlignment: MainAxisAlignment.start, children: [ @@ -150,13 +131,10 @@ class MenuWidgetState extends State { SingleChildScrollView( child: Container( child: Column( - crossAxisAlignment: - CrossAxisAlignment.start, - mainAxisAlignment: - widget.dashboardViewModel.subname != - null - ? MainAxisAlignment.spaceBetween - : MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: widget.dashboardViewModel.subname.isNotEmpty + ? MainAxisAlignment.spaceBetween + : MainAxisAlignment.center, children: [ Text( widget.dashboardViewModel.name, @@ -165,19 +143,16 @@ class MenuWidgetState extends State { fontSize: 16, fontWeight: FontWeight.bold), ), - if (widget.dashboardViewModel.subname != - null) + if (widget.dashboardViewModel.subname.isNotEmpty) Observer( builder: (_) => Text( - widget.dashboardViewModel - .subname, + widget.dashboardViewModel.subname, style: TextStyle( color: Theme.of(context) - .accentTextTheme! + .accentTextTheme .overline! .decorationColor!, - fontWeight: - FontWeight.w500, + fontWeight: FontWeight.w500, fontSize: 12), )) ], @@ -190,58 +165,24 @@ class MenuWidgetState extends State { index--; - final item = walletMenu.items[index]; - final title = item.title; - final image = item.image ?? Offstage(); + final item = SettingActions.all[index]; + final isLastTile = index == itemCount - 1; - return GestureDetector( - onTap: () { - Navigator.of(context).pop(); - walletMenu.action(index); - }, - child: Container( - color: Theme.of(context) - .textTheme! - .bodyText1! - .decorationColor!, - height: isLastTile ? headerHeight : tileHeight, - padding: isLastTile - ? EdgeInsets.only( - left: 24, - right: 24, - top: fromBottomEdge, - //bottom: fromTopEdge - ) - : EdgeInsets.only(left: 24, right: 24), - alignment: isLastTile ? Alignment.topLeft : null, - child: Row( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - image, - SizedBox(width: 16), - Expanded( - child: Text( - title, - style: TextStyle( - color: Theme.of(context) - .textTheme! - .headline3! - .color!, - fontSize: 16, - fontWeight: FontWeight.bold), - )) - ], - ), - )); + return SettingActionButton( + isLastTile: isLastTile, + tileHeight: tileHeight, + selectionActive: false, + fromBottomEdge: fromBottomEdge, + fromTopEdge: fromTopEdge, + onTap: () => item.onTap.call(context), + image: item.image, + title: item.name, + ); }, separatorBuilder: (_, index) => Container( height: 1, - color: Theme.of(context) - .primaryTextTheme! - .caption! - .decorationColor!, + color: Theme.of(context).primaryTextTheme.caption!.decorationColor!, ), itemCount: itemCount + 1), ))) diff --git a/lib/src/screens/dashboard/widgets/present_receive_option_picker.dart b/lib/src/screens/dashboard/widgets/present_receive_option_picker.dart index 2ca63dd82..8b3ffb894 100644 --- a/lib/src/screens/dashboard/widgets/present_receive_option_picker.dart +++ b/lib/src/screens/dashboard/widgets/present_receive_option_picker.dart @@ -86,7 +86,11 @@ class PresentReceiveOptionPicker extends StatelessWidget { itemBuilder: (_, index) { final option = receiveOptionViewModel.options[index]; return InkWell( - onTap: () => receiveOptionViewModel.selectReceiveOption(option), + onTap: () { + Navigator.pop(popUpContext); + + receiveOptionViewModel.selectReceiveOption(option); + }, child: Padding( padding: const EdgeInsets.only(left: 24, right: 24), child: Observer(builder: (_) { diff --git a/lib/src/screens/dashboard/widgets/transactions_page.dart b/lib/src/screens/dashboard/widgets/transactions_page.dart index f773fa8fc..de26d8da6 100644 --- a/lib/src/screens/dashboard/widgets/transactions_page.dart +++ b/lib/src/screens/dashboard/widgets/transactions_page.dart @@ -25,6 +25,7 @@ class TransactionsPage extends StatelessWidget { @override Widget build(BuildContext context) { return Container( + color: Theme.of(context).backgroundColor, padding: EdgeInsets.only(top: 24, bottom: 24), child: Column( children: [ diff --git a/lib/src/screens/exchange/exchange_page.dart b/lib/src/screens/exchange/exchange_page.dart index 1f17d38fd..99eeae7dc 100644 --- a/lib/src/screens/exchange/exchange_page.dart +++ b/lib/src/screens/exchange/exchange_page.dart @@ -1,13 +1,14 @@ -import 'dart:ui'; import 'package:cake_wallet/di.dart'; +import 'package:cake_wallet/src/screens/exchange/widgets/desktop_exchange_cards_section.dart'; +import 'package:cake_wallet/src/screens/exchange/widgets/mobile_exchange_cards_section.dart'; +import 'package:cake_wallet/src/widgets/add_template_button.dart'; import 'package:cake_wallet/utils/debounce.dart'; +import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:cw_core/sync_status.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:cake_wallet/entities/parse_address_from_domain.dart'; import 'package:cake_wallet/src/screens/send/widgets/extract_address_from_parsed.dart'; import 'package:cake_wallet/src/widgets/standard_checkbox.dart'; -import 'package:dotted_border/dotted_border.dart'; -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:keyboard_actions/keyboard_actions.dart'; @@ -35,7 +36,14 @@ import 'package:cake_wallet/src/screens/exchange/widgets/present_provider_picker import 'package:cake_wallet/src/screens/dashboard/widgets/sync_indicator_icon.dart'; class ExchangePage extends BasePage { - ExchangePage(this.exchangeViewModel); + ExchangePage(this.exchangeViewModel) { + depositWalletName = exchangeViewModel.depositCurrency == CryptoCurrency.xmr + ? exchangeViewModel.wallet.name + : null; + receiveWalletName = exchangeViewModel.receiveCurrency == CryptoCurrency.xmr + ? exchangeViewModel.wallet.name + : null; + } final ExchangeViewModel exchangeViewModel; final depositKey = GlobalKey(); @@ -49,6 +57,20 @@ class ExchangePage extends BasePage { final _depositAmountDebounce = Debounce(Duration(milliseconds: 500)); var _isReactionsSet = false; + final arrowBottomPurple = Image.asset( + 'assets/images/arrow_bottom_purple_icon.png', + color: Colors.white, + height: 8, + ); + final arrowBottomCakeGreen = Image.asset( + 'assets/images/arrow_bottom_cake_green.png', + color: Colors.white, + height: 8, + ); + + late final String? depositWalletName; + late final String? receiveWalletName; + @override String get title => S.current.exchange; @@ -85,28 +107,11 @@ class ExchangePage extends BasePage { exchangeViewModel.reset(); }); + @override + bool get canUseCloseIcon => true; + @override Widget body(BuildContext context) { - final arrowBottomPurple = Image.asset( - 'assets/images/arrow_bottom_purple_icon.png', - color: Colors.white, - height: 8, - ); - final arrowBottomCakeGreen = Image.asset( - 'assets/images/arrow_bottom_cake_green.png', - color: Colors.white, - height: 8, - ); - - final depositWalletName = - exchangeViewModel.depositCurrency == CryptoCurrency.xmr - ? exchangeViewModel.wallet.name - : null; - final receiveWalletName = - exchangeViewModel.receiveCurrency == CryptoCurrency.xmr - ? exchangeViewModel.wallet.name - : null; - WidgetsBinding.instance .addPostFrameCallback((_) => _setReactions(context, exchangeViewModel)); @@ -137,201 +142,7 @@ class ExchangePage extends BasePage { contentPadding: EdgeInsets.only(bottom: 24), content: Observer(builder: (_) => Column( children: [ - Container( - padding: EdgeInsets.only(bottom: 32), - decoration: BoxDecoration( - borderRadius: BorderRadius.only( - bottomLeft: Radius.circular(24), - bottomRight: Radius.circular(24)), - gradient: LinearGradient( - colors: [ - Theme.of(context).primaryTextTheme!.bodyText2!.color!, - Theme.of(context) - .primaryTextTheme! - .bodyText2! - .decorationColor!, - ], - stops: [ - 0.35, - 1.0 - ], - begin: Alignment.topLeft, - end: Alignment.bottomRight), - ), - child: Column( - children: [ - Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.only( - bottomLeft: Radius.circular(24), - bottomRight: Radius.circular(24)), - gradient: LinearGradient( - colors: [ - Theme.of(context) - .primaryTextTheme! - .subtitle2! - .color!, - Theme.of(context) - .primaryTextTheme! - .subtitle2! - .decorationColor!, - ], - begin: Alignment.topLeft, - end: Alignment.bottomRight), - ), - padding: EdgeInsets.fromLTRB(24, 100, 24, 32), - child: Observer( - builder: (_) => ExchangeCard( - onDispose: disposeBestRateSync, - hasAllAmount: exchangeViewModel.hasAllAmount, - allAmount: exchangeViewModel.hasAllAmount - ? () => exchangeViewModel - .calculateDepositAllAmount() - : null, - amountFocusNode: _depositAmountFocus, - addressFocusNode: _depositAddressFocus, - key: depositKey, - title: S.of(context).you_will_send, - initialCurrency: - exchangeViewModel.depositCurrency, - initialWalletName: depositWalletName ?? '', - initialAddress: - exchangeViewModel.depositCurrency == - exchangeViewModel.wallet.currency - ? exchangeViewModel.wallet.walletAddresses.address - : exchangeViewModel.depositAddress, - initialIsAmountEditable: true, - initialIsAddressEditable: - exchangeViewModel.isDepositAddressEnabled, - isAmountEstimated: false, - hasRefundAddress: true, - isMoneroWallet: exchangeViewModel.isMoneroWallet, - currencies: exchangeViewModel.depositCurrencies, - onCurrencySelected: (currency) { - // FIXME: need to move it into view model - if (currency == CryptoCurrency.xmr && - exchangeViewModel.wallet.type != - WalletType.monero) { - showPopUp( - context: context, - builder: (dialogContext) { - return AlertWithOneAction( - alertTitle: S.of(context).error, - alertContent: S - .of(context) - .exchange_incorrect_current_wallet_for_xmr, - buttonText: S.of(context).ok, - buttonAction: () => - Navigator.of(dialogContext) - .pop()); - }); - return; - } - - exchangeViewModel.changeDepositCurrency( - currency: currency); - }, - imageArrow: arrowBottomPurple, - currencyButtonColor: Colors.transparent, - addressButtonsColor: - Theme.of(context).focusColor!, - borderColor: Theme.of(context) - .primaryTextTheme! - .bodyText1! - .color!, - currencyValueValidator: AmountValidator( - currency: exchangeViewModel.depositCurrency), - addressTextFieldValidator: AddressValidator( - type: exchangeViewModel.depositCurrency), - onPushPasteButton: (context) async { - final domain = - exchangeViewModel.depositAddress; - final ticker = exchangeViewModel - .depositCurrency.title.toLowerCase(); - exchangeViewModel.depositAddress = - await fetchParsedAddress( - context, domain, ticker); - }, - onPushAddressBookButton: (context) async { - final domain = - exchangeViewModel.depositAddress; - final ticker = exchangeViewModel - .depositCurrency.title.toLowerCase(); - exchangeViewModel.depositAddress = - await fetchParsedAddress( - context, domain, ticker); - }, - ), - ), - ), - Padding( - padding: - EdgeInsets.only(top: 29, left: 24, right: 24), - child: Observer( - builder: (_) => ExchangeCard( - onDispose: disposeBestRateSync, - amountFocusNode: _receiveAmountFocus, - addressFocusNode: _receiveAddressFocus, - key: receiveKey, - title: S.of(context).you_will_get, - initialCurrency: - exchangeViewModel.receiveCurrency, - initialWalletName: receiveWalletName ?? '', - initialAddress: exchangeViewModel - .receiveCurrency == - exchangeViewModel.wallet.currency - ? exchangeViewModel.wallet.walletAddresses.address - : exchangeViewModel.receiveAddress, - initialIsAmountEditable: exchangeViewModel - .isReceiveAmountEditable, - initialIsAddressEditable: - exchangeViewModel - .isReceiveAddressEnabled, - isAmountEstimated: true, - isMoneroWallet: exchangeViewModel.isMoneroWallet, - currencies: - exchangeViewModel.receiveCurrencies, - onCurrencySelected: (currency) => - exchangeViewModel - .changeReceiveCurrency( - currency: currency), - imageArrow: arrowBottomCakeGreen, - currencyButtonColor: Colors.transparent, - addressButtonsColor: - Theme.of(context).focusColor!, - borderColor: Theme.of(context) - .primaryTextTheme! - .bodyText1! - .decorationColor!, - currencyValueValidator: AmountValidator( - currency: exchangeViewModel.receiveCurrency), - addressTextFieldValidator: - AddressValidator( - type: exchangeViewModel - .receiveCurrency), - onPushPasteButton: (context) async { - final domain = - exchangeViewModel.receiveAddress; - final ticker = exchangeViewModel - .receiveCurrency.title.toLowerCase(); - exchangeViewModel.receiveAddress = - await fetchParsedAddress( - context, domain, ticker); - }, - onPushAddressBookButton: (context) async { - final domain = - exchangeViewModel.receiveAddress; - final ticker = exchangeViewModel - .receiveCurrency.title.toLowerCase(); - exchangeViewModel.receiveAddress = - await fetchParsedAddress( - context, domain, ticker); - }, - )), - ) - ], - ), - ), + _exchangeCardsSection(context), Padding( padding: EdgeInsets.only(top: 12, left: 24), child: Row( @@ -427,50 +238,9 @@ class ExchangePage extends BasePage { return Row( children: [ - GestureDetector( - onTap: () => - Navigator.of(context).pushNamed(Routes.exchangeTemplate), - child: Container( - padding: EdgeInsets.only(left: 1, right: 10), - child: DottedBorder( - borderType: BorderType.RRect, - dashPattern: [6, 4], - color: Theme.of(context) - .primaryTextTheme! - .headline3! - .decorationColor!, - strokeWidth: 2, - radius: Radius.circular(20), - child: Container( - height: 34, - padding: EdgeInsets.only(left: 10, right: 10), - alignment: Alignment.center, - decoration: BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(20)), - color: Colors.transparent, - ), - child: templates.length >= 1 - ? Icon( - Icons.add, - color: Theme.of(context) - .primaryTextTheme! - .headline2! - .color!, - ) - : Text( - S.of(context).new_template, - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w600, - color: Theme.of(context) - .primaryTextTheme! - .headline2! - .color!, - ), - ), - ), - ), - ), + AddTemplateButton( + onTap: () => Navigator.of(context).pushNamed(Routes.exchangeTemplate), + currentTemplatesLength: templates.length, ), ListView.builder( scrollDirection: Axis.horizontal, @@ -808,5 +578,124 @@ class ExchangePage extends BasePage { } } - void disposeBestRateSync() => exchangeViewModel.bestRateSync?.cancel(); + void disposeBestRateSync() => exchangeViewModel.bestRateSync.cancel(); + + Widget _exchangeCardsSection(BuildContext context) { + final firstExchangeCard = Observer(builder: (_) => ExchangeCard( + onDispose: disposeBestRateSync, + hasAllAmount: exchangeViewModel.hasAllAmount, + allAmount: exchangeViewModel.hasAllAmount + ? () => exchangeViewModel.calculateDepositAllAmount() + : null, + amountFocusNode: _depositAmountFocus, + addressFocusNode: _depositAddressFocus, + key: depositKey, + title: S.of(context).you_will_send, + initialCurrency: exchangeViewModel.depositCurrency, + initialWalletName: depositWalletName ?? '', + initialAddress: + exchangeViewModel.depositCurrency == exchangeViewModel.wallet.currency + ? exchangeViewModel.wallet.walletAddresses.address + : exchangeViewModel.depositAddress, + initialIsAmountEditable: true, + initialIsAddressEditable: exchangeViewModel.isDepositAddressEnabled, + isAmountEstimated: false, + hasRefundAddress: true, + isMoneroWallet: exchangeViewModel.isMoneroWallet, + currencies: exchangeViewModel.depositCurrencies, + onCurrencySelected: (currency) { + // FIXME: need to move it into view model + if (currency == CryptoCurrency.xmr && + exchangeViewModel.wallet.type != WalletType.monero) { + showPopUp( + context: context, + builder: (dialogContext) { + return AlertWithOneAction( + alertTitle: S.of(context).error, + alertContent: + S.of(context).exchange_incorrect_current_wallet_for_xmr, + buttonText: S.of(context).ok, + buttonAction: () => Navigator.of(dialogContext).pop()); + }); + return; + } + + exchangeViewModel.changeDepositCurrency(currency: currency); + }, + imageArrow: arrowBottomPurple, + currencyButtonColor: Colors.transparent, + addressButtonsColor: Theme.of(context).focusColor!, + borderColor: Theme.of(context).primaryTextTheme!.bodyText1!.color!, + currencyValueValidator: + AmountValidator(currency: exchangeViewModel.depositCurrency), + addressTextFieldValidator: + AddressValidator(type: exchangeViewModel.depositCurrency), + onPushPasteButton: (context) async { + final domain = exchangeViewModel.depositAddress; + final ticker = exchangeViewModel.depositCurrency.title.toLowerCase(); + exchangeViewModel.depositAddress = + await fetchParsedAddress(context, domain, ticker); + }, + onPushAddressBookButton: (context) async { + final domain = exchangeViewModel.depositAddress; + final ticker = exchangeViewModel.depositCurrency.title.toLowerCase(); + exchangeViewModel.depositAddress = + await fetchParsedAddress(context, domain, ticker); + }, + )); + + final secondExchangeCard = Observer(builder: (_) => ExchangeCard( + onDispose: disposeBestRateSync, + amountFocusNode: _receiveAmountFocus, + addressFocusNode: _receiveAddressFocus, + key: receiveKey, + title: S.of(context).you_will_get, + initialCurrency: exchangeViewModel.receiveCurrency, + initialWalletName: receiveWalletName ?? '', + initialAddress: + exchangeViewModel.receiveCurrency == exchangeViewModel.wallet.currency + ? exchangeViewModel.wallet.walletAddresses.address + : exchangeViewModel.receiveAddress, + initialIsAmountEditable: exchangeViewModel.isReceiveAmountEditable, + initialIsAddressEditable: exchangeViewModel.isReceiveAddressEnabled, + isAmountEstimated: true, + isMoneroWallet: exchangeViewModel.isMoneroWallet, + currencies: exchangeViewModel.receiveCurrencies, + onCurrencySelected: (currency) => + exchangeViewModel.changeReceiveCurrency(currency: currency), + imageArrow: arrowBottomCakeGreen, + currencyButtonColor: Colors.transparent, + addressButtonsColor: Theme.of(context).focusColor!, + borderColor: + Theme.of(context).primaryTextTheme!.bodyText1!.decorationColor!, + currencyValueValidator: + AmountValidator(currency: exchangeViewModel.receiveCurrency), + addressTextFieldValidator: + AddressValidator(type: exchangeViewModel.receiveCurrency), + onPushPasteButton: (context) async { + final domain = exchangeViewModel.receiveAddress; + final ticker = exchangeViewModel.receiveCurrency.title.toLowerCase(); + exchangeViewModel.receiveAddress = + await fetchParsedAddress(context, domain, ticker); + }, + onPushAddressBookButton: (context) async { + final domain = exchangeViewModel.receiveAddress; + final ticker = exchangeViewModel.receiveCurrency.title.toLowerCase(); + exchangeViewModel.receiveAddress = + await fetchParsedAddress(context, domain, ticker); + }, + )); + + if (ResponsiveLayoutUtil.instance.isMobile(context)) { + return MobileExchangeCardsSection( + firstExchangeCard: firstExchangeCard, + secondExchangeCard: secondExchangeCard, + ); + } + + return DesktopExchangeCardsSection( + firstExchangeCard: firstExchangeCard, + secondExchangeCard: secondExchangeCard, + ); + } } diff --git a/lib/src/screens/exchange/exchange_template_page.dart b/lib/src/screens/exchange/exchange_template_page.dart index cacc52498..50faf7eb2 100644 --- a/lib/src/screens/exchange/exchange_template_page.dart +++ b/lib/src/screens/exchange/exchange_template_page.dart @@ -1,13 +1,9 @@ -import 'dart:ui'; import 'package:cake_wallet/exchange/exchange_provider.dart'; import 'package:cake_wallet/src/screens/base_page.dart'; import 'package:cake_wallet/src/widgets/keyboard_done_button.dart'; -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:keyboard_actions/keyboard_actions.dart'; -import 'package:keyboard_actions/keyboard_actions_config.dart'; -import 'package:keyboard_actions/keyboard_actions_item.dart'; import 'package:mobx/mobx.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cw_core/crypto_currency.dart'; @@ -78,7 +74,7 @@ class ExchangeTemplatePage extends BasePage { config: KeyboardActionsConfig( keyboardActionsPlatform: KeyboardActionsPlatform.IOS, keyboardBarColor: - Theme.of(context).accentTextTheme!.bodyText1!.backgroundColor!, + Theme.of(context).accentTextTheme.bodyText1!.backgroundColor!, nextFocus: false, actions: [ KeyboardActionsItem( @@ -103,115 +99,114 @@ class ExchangeTemplatePage extends BasePage { ), gradient: LinearGradient( colors: [ - Theme.of(context).primaryTextTheme!.bodyText2!.color!, - Theme.of(context).primaryTextTheme!.bodyText2!.decorationColor!, + Theme.of(context).primaryTextTheme.bodyText2!.color!, + Theme.of(context).primaryTextTheme.bodyText2!.decorationColor!, ], stops: [0.35, 1.0], begin: Alignment.topLeft, end: Alignment.bottomRight), ), - child: Column( - children: [ - Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.only( - bottomLeft: Radius.circular(24), - bottomRight: Radius.circular(24) + child: FocusTraversalGroup( + policy: OrderedTraversalPolicy(), + child: Column( + children: [ + Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(24), + bottomRight: Radius.circular(24) + ), + gradient: LinearGradient( + colors: [ + Theme.of(context) + .primaryTextTheme.subtitle2! + .color!, + Theme.of(context) + .primaryTextTheme.subtitle2! + .decorationColor!, + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight), ), - gradient: LinearGradient( - colors: [ - Theme.of(context) - .primaryTextTheme! - .subtitle2! - .color!, - Theme.of(context) - .primaryTextTheme! - .subtitle2! - .decorationColor!, - ], - begin: Alignment.topLeft, - end: Alignment.bottomRight), - ), - padding: EdgeInsets.fromLTRB(24, 100, 24, 32), - child: Observer( - builder: (_) => ExchangeCard( - amountFocusNode: _depositAmountFocus, - key: depositKey, - title: S.of(context).you_will_send, - initialCurrency: - exchangeViewModel.depositCurrency, - initialWalletName: depositWalletName ?? '', - initialAddress: exchangeViewModel - .depositCurrency == - exchangeViewModel.wallet.currency - ? exchangeViewModel.wallet.walletAddresses.address - : exchangeViewModel.depositAddress, - initialIsAmountEditable: true, - initialIsAddressEditable: exchangeViewModel - .isDepositAddressEnabled, - isAmountEstimated: false, - hasRefundAddress: true, - isMoneroWallet: exchangeViewModel.isMoneroWallet, - currencies: CryptoCurrency.all, - onCurrencySelected: (currency) => - exchangeViewModel.changeDepositCurrency( - currency: currency), - imageArrow: arrowBottomPurple, - currencyButtonColor: Colors.transparent, - addressButtonsColor: - Theme.of(context).focusColor!, - borderColor: Theme.of(context) - .primaryTextTheme! - .bodyText1! - .color!, - currencyValueValidator: AmountValidator( - currency: exchangeViewModel.depositCurrency), - //addressTextFieldValidator: AddressValidator( - // type: exchangeViewModel.depositCurrency), - ), - ), - ), - Padding( - padding: EdgeInsets.only(top: 29, left: 24, right: 24), - child: Observer( + padding: EdgeInsets.fromLTRB(24, 100, 24, 32), + child: Observer( builder: (_) => ExchangeCard( - amountFocusNode: _receiveAmountFocus, - key: receiveKey, - title: S.of(context).you_will_get, + amountFocusNode: _depositAmountFocus, + key: depositKey, + title: S.of(context).you_will_send, initialCurrency: - exchangeViewModel.receiveCurrency, - initialWalletName: receiveWalletName ?? '', - initialAddress: - exchangeViewModel.receiveCurrency == + exchangeViewModel.depositCurrency, + initialWalletName: depositWalletName ?? '', + initialAddress: exchangeViewModel + .depositCurrency == exchangeViewModel.wallet.currency ? exchangeViewModel.wallet.walletAddresses.address - : exchangeViewModel.receiveAddress, - initialIsAmountEditable: - exchangeViewModel.provider is - XMRTOExchangeProvider ? true : false, - initialIsAddressEditable: - exchangeViewModel.isReceiveAddressEnabled, - isAmountEstimated: true, + : exchangeViewModel.depositAddress, + initialIsAmountEditable: true, + initialIsAddressEditable: exchangeViewModel + .isDepositAddressEnabled, + isAmountEstimated: false, + hasRefundAddress: true, isMoneroWallet: exchangeViewModel.isMoneroWallet, - currencies: exchangeViewModel.receiveCurrencies, + currencies: CryptoCurrency.all, onCurrencySelected: (currency) => - exchangeViewModel.changeReceiveCurrency( + exchangeViewModel.changeDepositCurrency( currency: currency), - imageArrow: arrowBottomCakeGreen, + imageArrow: arrowBottomPurple, currencyButtonColor: Colors.transparent, addressButtonsColor: - Theme.of(context).focusColor!, + Theme.of(context).focusColor, borderColor: Theme.of(context) - .primaryTextTheme! - .bodyText1! - .decorationColor!, + .primaryTextTheme.bodyText1! + .color!, currencyValueValidator: AmountValidator( - currency: exchangeViewModel.receiveCurrency), + currency: exchangeViewModel.depositCurrency), //addressTextFieldValidator: AddressValidator( - // type: exchangeViewModel.receiveCurrency), - )), - ) - ], + // type: exchangeViewModel.depositCurrency), + ), + ), + ), + Padding( + padding: EdgeInsets.only(top: 29, left: 24, right: 24), + child: Observer( + builder: (_) => ExchangeCard( + amountFocusNode: _receiveAmountFocus, + key: receiveKey, + title: S.of(context).you_will_get, + initialCurrency: + exchangeViewModel.receiveCurrency, + initialWalletName: receiveWalletName ?? '', + initialAddress: + exchangeViewModel.receiveCurrency == + exchangeViewModel.wallet.currency + ? exchangeViewModel.wallet.walletAddresses.address + : exchangeViewModel.receiveAddress, + initialIsAmountEditable: + exchangeViewModel.provider is + XMRTOExchangeProvider ? true : false, + initialIsAddressEditable: + exchangeViewModel.isReceiveAddressEnabled, + isAmountEstimated: true, + isMoneroWallet: exchangeViewModel.isMoneroWallet, + currencies: exchangeViewModel.receiveCurrencies, + onCurrencySelected: (currency) => + exchangeViewModel.changeReceiveCurrency( + currency: currency), + imageArrow: arrowBottomCakeGreen, + currencyButtonColor: Colors.transparent, + addressButtonsColor: + Theme.of(context).focusColor, + borderColor: Theme.of(context) + .primaryTextTheme.bodyText1! + .decorationColor!, + currencyValueValidator: AmountValidator( + currency: exchangeViewModel.receiveCurrency), + //addressTextFieldValidator: AddressValidator( + // type: exchangeViewModel.receiveCurrency), + )), + ) + ], + ), ), ), bottomSectionPadding: @@ -230,8 +225,7 @@ class ExchangeTemplatePage extends BasePage { textAlign: TextAlign.center, style: TextStyle( color: Theme.of(context) - .primaryTextTheme! - .headline1! + .primaryTextTheme.headline1! .decorationColor!, fontWeight: FontWeight.w500, fontSize: 12), diff --git a/lib/src/screens/exchange/widgets/currency_picker.dart b/lib/src/screens/exchange/widgets/currency_picker.dart index 442ee1b24..5ed9c6f7d 100644 --- a/lib/src/screens/exchange/widgets/currency_picker.dart +++ b/lib/src/screens/exchange/widgets/currency_picker.dart @@ -1,9 +1,8 @@ -import 'dart:ui'; import 'package:cake_wallet/src/screens/exchange/widgets/currency_picker_item_widget.dart'; import 'package:cake_wallet/src/screens/exchange/widgets/picker_item.dart'; import 'package:cake_wallet/src/widgets/alert_close_button.dart'; +import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:cw_core/currency.dart'; -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cake_wallet/src/widgets/alert_background.dart'; @@ -68,11 +67,9 @@ class CurrencyPickerState extends State { @override Widget build(BuildContext context) { return AlertBackground( - child: Stack( - alignment: Alignment.center, - children: [ - Column( + child: Column( mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, children: [ if (widget.title?.isNotEmpty ?? false) Container( @@ -94,10 +91,11 @@ class CurrencyPickerState extends State { child: ClipRRect( borderRadius: BorderRadius.all(Radius.circular(30)), child: Container( - color: Theme.of(context).accentTextTheme!.headline6!.color!, + color: Theme.of(context).accentTextTheme.headline6!.color!, child: ConstrainedBox( constraints: BoxConstraints( maxHeight: MediaQuery.of(context).size.height * 0.65, + maxWidth: ResponsiveLayoutUtil.kPopupWidth ), child: Column( mainAxisSize: MainAxisSize.min, @@ -133,7 +131,7 @@ class CurrencyPickerState extends State { ), ), Divider( - color: Theme.of(context).accentTextTheme!.headline6!.backgroundColor!, + color: Theme.of(context).accentTextTheme.headline6!.backgroundColor!, height: 1, ), if (widget.selectedAtIndex != -1) @@ -171,8 +169,7 @@ class CurrencyPickerState extends State { ), ), ), - ], - ), + SizedBox(height: ResponsiveLayoutUtil.kPopupSpaceHeight), AlertCloseButton(), ], ), diff --git a/lib/src/screens/exchange/widgets/desktop_exchange_cards_section.dart b/lib/src/screens/exchange/widgets/desktop_exchange_cards_section.dart new file mode 100644 index 000000000..0a97d7bad --- /dev/null +++ b/lib/src/screens/exchange/widgets/desktop_exchange_cards_section.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; + +class DesktopExchangeCardsSection extends StatelessWidget { + final Widget firstExchangeCard; + final Widget secondExchangeCard; + + const DesktopExchangeCardsSection({ + Key? key, + required this.firstExchangeCard, + required this.secondExchangeCard, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return FocusTraversalGroup( + policy: OrderedTraversalPolicy(), + child: Column( + children: [ + Padding( + padding: EdgeInsets.only(top: 55, left: 24, right: 24), + child: firstExchangeCard, + ), + Padding( + padding: EdgeInsets.only(top: 29, left: 24, right: 24), + child: secondExchangeCard, + ), + ], + ), + ); + } +} diff --git a/lib/src/screens/exchange/widgets/exchange_card.dart b/lib/src/screens/exchange/widgets/exchange_card.dart index 2bacbac3c..0ba112215 100644 --- a/lib/src/screens/exchange/widgets/exchange_card.dart +++ b/lib/src/screens/exchange/widgets/exchange_card.dart @@ -160,7 +160,7 @@ class ExchangeCardState extends State { final copyImage = Image.asset('assets/images/copy_content.png', height: 16, width: 16, - color: Theme.of(context).primaryTextTheme!.headline3!.color!); + color: Theme.of(context).primaryTextTheme.headline3!.color!); return Container( width: double.infinity, @@ -175,7 +175,7 @@ class ExchangeCardState extends State { style: TextStyle( fontSize: 18, fontWeight: FontWeight.w600, - color: Theme.of(context).textTheme!.headline5!.color!), + color: Theme.of(context).textTheme.headline5!.color!), ) ], ), @@ -210,7 +210,7 @@ class ExchangeCardState extends State { child: Container( height: 32, decoration: BoxDecoration( - color: widget.addressButtonsColor ?? Theme.of(context).primaryTextTheme!.headline4!.color!, + color: widget.addressButtonsColor ?? Theme.of(context).primaryTextTheme.headline4!.color!, borderRadius: BorderRadius.all(Radius.circular(6))), child: Center( @@ -221,8 +221,7 @@ class ExchangeCardState extends State { fontSize: 12, fontWeight: FontWeight.bold, color: Theme.of(context) - .primaryTextTheme! - .headline4! + .primaryTextTheme.headline4! .decorationColor!)), ), ), @@ -241,34 +240,36 @@ class ExchangeCardState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Flexible( - child: BaseTextFormField( - focusNode: widget.amountFocusNode, - controller: amountController, - enabled: _isAmountEditable, - textAlign: TextAlign.left, - keyboardType: TextInputType.numberWithOptions( - signed: false, decimal: true), - inputFormatters: [ - FilteringTextInputFormatter.deny( - RegExp('[\\-|\\ ]')) - ], - hintText: '0.0000', - borderColor: Colors.transparent, - //widget.borderColor, - textStyle: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - color: Colors.white), - placeholderTextStyle: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - color: Theme.of(context) - .accentTextTheme! - .headline1! - .decorationColor!), - validator: _isAmountEditable - ? widget.currencyValueValidator - : null), + child: FocusTraversalOrder( + order: NumericFocusOrder(1), + child: BaseTextFormField( + focusNode: widget.amountFocusNode, + controller: amountController, + enabled: _isAmountEditable, + textAlign: TextAlign.left, + keyboardType: TextInputType.numberWithOptions( + signed: false, decimal: true), + inputFormatters: [ + FilteringTextInputFormatter.deny( + RegExp('[\\-|\\ ]')) + ], + hintText: '0.0000', + borderColor: Colors.transparent, + //widget.borderColor, + textStyle: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: Colors.white), + placeholderTextStyle: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: Theme.of(context) + .accentTextTheme.headline1! + .decorationColor!), + validator: _isAmountEditable + ? widget.currencyValueValidator + : null), + ), ), if (widget.hasAllAmount) Container( @@ -276,8 +277,7 @@ class ExchangeCardState extends State { width: 32, decoration: BoxDecoration( color: Theme.of(context) - .primaryTextTheme! - .headline4! + .primaryTextTheme.headline4! .color!, borderRadius: BorderRadius.all(Radius.circular(6))), @@ -290,8 +290,7 @@ class ExchangeCardState extends State { fontSize: 12, fontWeight: FontWeight.bold, color: Theme.of(context) - .primaryTextTheme! - .headline4! + .primaryTextTheme.headline4! .decorationColor!)), ), ), @@ -302,8 +301,7 @@ class ExchangeCardState extends State { ], )), Divider(height: 1,color: Theme.of(context) - .primaryTextTheme! - .headline5! + .primaryTextTheme.headline5! .decorationColor!), Padding( padding: EdgeInsets.only(top: 5), @@ -321,8 +319,7 @@ class ExchangeCardState extends State { fontSize: 10, height: 1.2, color: Theme.of(context) - .accentTextTheme! - .headline1! + .accentTextTheme.headline1! .decorationColor!), ) : Offstage(), @@ -336,8 +333,7 @@ class ExchangeCardState extends State { fontSize: 10, height: 1.2, color: Theme.of(context) - .accentTextTheme! - .headline1! + .accentTextTheme.headline1! .decorationColor!)) : Offstage(), ])), @@ -351,71 +347,75 @@ class ExchangeCardState extends State { fontSize: 14, fontWeight: FontWeight.w500, color: Theme.of(context) - .accentTextTheme! - .headline1! + .accentTextTheme.headline1! .decorationColor!), )) : Offstage(), _isAddressEditable - ? Padding( - padding: EdgeInsets.only(top: 20), - child: AddressTextField( - focusNode: widget.addressFocusNode, - controller: addressController, - onURIScanned: (uri) { - final paymentRequest = PaymentRequest.fromUri(uri); - addressController.text = paymentRequest.address; - - if (amountController.text.isNotEmpty) { - _showAmountPopup(context, paymentRequest); - return; - } - widget.amountFocusNode?.requestFocus(); - amountController.text = paymentRequest.amount; - }, - placeholder: widget.hasRefundAddress - ? S.of(context).refund_address - : null, - options: [ - AddressTextFieldOption.paste, - AddressTextFieldOption.qrCode, - AddressTextFieldOption.addressBook, - ], - isBorderExist: false, - textStyle: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - color: Colors.white), - hintStyle: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - color: Theme.of(context) - .accentTextTheme! - .headline1! - .decorationColor!), - buttonColor: widget.addressButtonsColor, - validator: widget.addressTextFieldValidator, - onPushPasteButton: widget.onPushPasteButton, - onPushAddressBookButton: widget.onPushAddressBookButton, - selectedCurrency: _selectedCurrency + ? FocusTraversalOrder( + order: NumericFocusOrder(2), + child: Padding( + padding: EdgeInsets.only(top: 20), + child: AddressTextField( + focusNode: widget.addressFocusNode, + controller: addressController, + onURIScanned: (uri) { + final paymentRequest = PaymentRequest.fromUri(uri); + addressController.text = paymentRequest.address; + + if (amountController.text.isNotEmpty) { + _showAmountPopup(context, paymentRequest); + return; + } + widget.amountFocusNode?.requestFocus(); + amountController.text = paymentRequest.amount; + }, + placeholder: widget.hasRefundAddress + ? S.of(context).refund_address + : null, + options: [ + AddressTextFieldOption.paste, + AddressTextFieldOption.qrCode, + AddressTextFieldOption.addressBook, + ], + isBorderExist: false, + textStyle: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: Colors.white), + hintStyle: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: Theme.of(context) + .accentTextTheme.headline1! + .decorationColor!), + buttonColor: widget.addressButtonsColor, + validator: widget.addressTextFieldValidator, + onPushPasteButton: widget.onPushPasteButton, + onPushAddressBookButton: widget.onPushAddressBookButton, + selectedCurrency: _selectedCurrency + ), + ), - - ) + ) : Padding( padding: EdgeInsets.only(top: 10), child: Builder( builder: (context) => Stack(children: [ - BaseTextFormField( - controller: addressController, - readOnly: true, - borderColor: Colors.transparent, - suffixIcon: - SizedBox(width: _isMoneroWallet ? 80 : 36), - textStyle: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - color: Colors.white), - validator: widget.addressTextFieldValidator), + FocusTraversalOrder( + order: NumericFocusOrder(3), + child: BaseTextFormField( + controller: addressController, + readOnly: true, + borderColor: Colors.transparent, + suffixIcon: + SizedBox(width: _isMoneroWallet ? 80 : 36), + textStyle: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: Colors.white), + validator: widget.addressTextFieldValidator), + ), Positioned( top: 2, right: 0, @@ -432,10 +432,8 @@ class ExchangeCardState extends State { child: InkWell( onTap: () async { final contact = - await Navigator.of(context, - rootNavigator: true) - .pushNamed(Routes - .pickerAddressBook); + await Navigator.of(context) + .pushNamed(Routes.pickerAddressBook); if (contact is ContactBase && contact.address != null) { @@ -458,8 +456,7 @@ class ExchangeCardState extends State { child: Image.asset( 'assets/images/open_book.png', color: Theme.of(context) - .primaryTextTheme! - .headline4! + .primaryTextTheme.headline4! .decorationColor!, )), )), diff --git a/lib/src/screens/exchange/widgets/mobile_exchange_cards_section.dart b/lib/src/screens/exchange/widgets/mobile_exchange_cards_section.dart new file mode 100644 index 000000000..762c36a55 --- /dev/null +++ b/lib/src/screens/exchange/widgets/mobile_exchange_cards_section.dart @@ -0,0 +1,58 @@ +import 'package:flutter/material.dart'; + +class MobileExchangeCardsSection extends StatelessWidget { + final Widget firstExchangeCard; + final Widget secondExchangeCard; + + const MobileExchangeCardsSection({ + Key? key, + required this.firstExchangeCard, + required this.secondExchangeCard, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + padding: EdgeInsets.only(bottom: 32), + decoration: BoxDecoration( + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(24), + bottomRight: Radius.circular(24), + ), + gradient: LinearGradient( + colors: [ + Theme.of(context).primaryTextTheme.bodyText2!.color!, + Theme.of(context).primaryTextTheme.bodyText2!.decorationColor!, + ], + stops: [0.35, 1.0], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + ), + child: Column( + children: [ + Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(24), bottomRight: Radius.circular(24)), + gradient: LinearGradient( + colors: [ + Theme.of(context).primaryTextTheme.subtitle2!.color!, + Theme.of(context).primaryTextTheme.subtitle2!.decorationColor!, + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + ), + padding: EdgeInsets.fromLTRB(24, 100, 24, 32), + child: firstExchangeCard, + ), + Padding( + padding: EdgeInsets.only(top: 29, left: 24, right: 24), + child: secondExchangeCard, + ) + ], + ), + ); + } +} diff --git a/lib/src/screens/ionia/auth/ionia_create_account_page.dart b/lib/src/screens/ionia/auth/ionia_create_account_page.dart index d2ced5dae..abdf3501c 100644 --- a/lib/src/screens/ionia/auth/ionia_create_account_page.dart +++ b/lib/src/screens/ionia/auth/ionia_create_account_page.dart @@ -66,6 +66,7 @@ class IoniaCreateAccountPage extends BasePage { validator: EmailValidator(), keyboardType: TextInputType.emailAddress, controller: _emailController, + onSubmit: (_) => _createAccount(), ), ), bottomSectionPadding: EdgeInsets.symmetric(vertical: 36, horizontal: 24), @@ -77,12 +78,7 @@ class IoniaCreateAccountPage extends BasePage { Observer( builder: (_) => LoadingPrimaryButton( text: S.of(context).create_account, - onPressed: () async { - if (_formKey.currentState != null && !_formKey.currentState!.validate()) { - return; - } - await _authViewModel.createUser(_emailController.text); - }, + onPressed: _createAccount, isLoading: _authViewModel.createUserState is IoniaCreateStateLoading, color: Theme.of(context).accentTextTheme!.bodyText1!.color!, textColor: Colors.white, @@ -151,4 +147,11 @@ class IoniaCreateAccountPage extends BasePage { Routes.ioniaVerifyIoniaOtpPage, arguments: [authViewModel.email, false], ); + + void _createAccount() async { + if (_formKey.currentState != null && !_formKey.currentState!.validate()) { + return; + } + await _authViewModel.createUser(_emailController.text); + } } diff --git a/lib/src/screens/ionia/auth/ionia_login_page.dart b/lib/src/screens/ionia/auth/ionia_login_page.dart index 6dc4aa6e5..e6e8680f3 100644 --- a/lib/src/screens/ionia/auth/ionia_login_page.dart +++ b/lib/src/screens/ionia/auth/ionia_login_page.dart @@ -57,6 +57,7 @@ class IoniaLoginPage extends BasePage { keyboardType: TextInputType.emailAddress, validator: EmailValidator(), controller: _emailController, + onSubmit: (text) => _login(), ), ), bottomSectionPadding: EdgeInsets.symmetric(vertical: 36, horizontal: 24), @@ -68,12 +69,7 @@ class IoniaLoginPage extends BasePage { Observer( builder: (_) => LoadingPrimaryButton( text: S.of(context).login, - onPressed: () async { - if (_formKey.currentState != null && !_formKey.currentState!.validate()) { - return; - } - await _authViewModel.signIn(_emailController.text); - }, + onPressed: _login, isLoading: _authViewModel.signInState is IoniaCreateStateLoading, color: Theme.of(context).accentTextTheme!.bodyText1!.color!, textColor: Colors.white, @@ -106,4 +102,11 @@ class IoniaLoginPage extends BasePage { Routes.ioniaVerifyIoniaOtpPage, arguments: [authViewModel.email, true], ); + + void _login() async { + if (_formKey.currentState != null && !_formKey.currentState!.validate()) { + return; + } + await _authViewModel.signIn(_emailController.text); + } } diff --git a/lib/src/screens/ionia/auth/ionia_verify_otp_page.dart b/lib/src/screens/ionia/auth/ionia_verify_otp_page.dart index 625bd36b0..e2123e164 100644 --- a/lib/src/screens/ionia/auth/ionia_verify_otp_page.dart +++ b/lib/src/screens/ionia/auth/ionia_verify_otp_page.dart @@ -82,6 +82,7 @@ class IoniaVerifyIoniaOtp extends BasePage { keyboardType: TextInputType.numberWithOptions(signed: false, decimal: true), focusNode: _codeFocus, controller: _codeController, + onSubmit: (_) => _verify(), ), SizedBox(height: 14), Text( @@ -116,7 +117,7 @@ class IoniaVerifyIoniaOtp extends BasePage { Observer( builder: (_) => LoadingPrimaryButton( text: S.of(context).continue_text, - onPressed: () async => await _authViewModel.verifyEmail(_codeController.text), + onPressed: _verify, isDisabled: _authViewModel.otpState is IoniaOtpSendDisabled, isLoading: _authViewModel.otpState is IoniaOtpValidating, color: Theme.of(context).accentTextTheme!.bodyText1!.color!, @@ -148,4 +149,6 @@ class IoniaVerifyIoniaOtp extends BasePage { void _onOtpSuccessful(BuildContext context) => Navigator.of(context) .pushNamedAndRemoveUntil(Routes.ioniaManageCardsPage, (route) => route.isFirst); + + void _verify() async => await _authViewModel.verifyEmail(_codeController.text); } diff --git a/lib/src/screens/ionia/cards/ionia_buy_gift_card.dart b/lib/src/screens/ionia/cards/ionia_buy_gift_card.dart index 3182d366f..a60b967f2 100644 --- a/lib/src/screens/ionia/cards/ionia_buy_gift_card.dart +++ b/lib/src/screens/ionia/cards/ionia_buy_gift_card.dart @@ -6,6 +6,7 @@ import 'package:cake_wallet/src/widgets/keyboard_done_button.dart'; import 'package:cake_wallet/src/widgets/primary_button.dart'; import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart'; import 'package:cake_wallet/themes/theme_base.dart'; +import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:cake_wallet/view_model/ionia/ionia_buy_card_view_model.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -44,7 +45,6 @@ class IoniaBuyGiftCardPage extends BasePage { @override Widget body(BuildContext context) { - final _width = MediaQuery.of(context).size.width; final merchant = ioniaBuyCardViewModel.ioniaMerchant; return KeyboardActions( disableScroll: true, @@ -67,7 +67,10 @@ class IoniaBuyGiftCardPage extends BasePage { Container( padding: EdgeInsets.symmetric(horizontal: 25), decoration: BoxDecoration( - borderRadius: BorderRadius.only(bottomLeft: Radius.circular(24), bottomRight: Radius.circular(24)), + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(24), + bottomRight: Radius.circular(24), + ), gradient: LinearGradient(colors: [ Theme.of(context).primaryTextTheme!.subtitle1!.color!, Theme.of(context).primaryTextTheme!.subtitle1!.decorationColor!, @@ -75,35 +78,28 @@ class IoniaBuyGiftCardPage extends BasePage { ), child: Column( mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, mainAxisAlignment: MainAxisAlignment.center, children: [ SizedBox(height: 150), - BaseTextFormField( - controller: _amountController, - focusNode: _amountFieldFocus, - keyboardType: TextInputType.numberWithOptions(signed: false, decimal: true), - inputFormatters: [ - FilteringTextInputFormatter.deny(RegExp('[\-|\ ]')), - FilteringTextInputFormatter.allow(RegExp(r'^\d+(\.|\,)?\d{0,2}'))], - hintText: '1000', - placeholderTextStyle: TextStyle( - color: Theme.of(context).primaryTextTheme!.headline5!.color!, - fontWeight: FontWeight.w600, - fontSize: 36, - ), - borderColor: Theme.of(context).primaryTextTheme!.headline5!.color!, - textColor: Colors.white, - textStyle: TextStyle( - color: Colors.white, - fontSize: 36, - ), - prefixIcon: Padding( - padding: EdgeInsets.only( - top: 5.0, - left: _width / 4, + SizedBox( + width: 200, + child: BaseTextFormField( + controller: _amountController, + focusNode: _amountFieldFocus, + keyboardType: TextInputType.numberWithOptions(signed: false, decimal: true), + inputFormatters: [ + FilteringTextInputFormatter.deny(RegExp('[\-|\ ]')), + FilteringTextInputFormatter.allow( + RegExp(r'^\d+(\.|\,)?\d{0,2}'), + ), + ], + hintText: '1000', + placeholderTextStyle: TextStyle( + color: Theme.of(context).primaryTextTheme.headline5!.color!, + fontWeight: FontWeight.w600, + fontSize: 36, ), - child: Text( + prefixIcon: Text( 'USD: ', style: TextStyle( color: Colors.white, @@ -111,8 +107,17 @@ class IoniaBuyGiftCardPage extends BasePage { fontSize: 36, ), ), + textColor: Colors.white, + textStyle: TextStyle( + color: Colors.white, + fontSize: 36, + ), ), ), + Divider( + color: Theme.of(context).primaryTextTheme.headline5!.color!, + height: 1, + ), SizedBox(height: 8), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -140,7 +145,11 @@ class IoniaBuyGiftCardPage extends BasePage { padding: const EdgeInsets.all(24.0), child: CardItem( title: merchant.legalName, - backgroundColor: Theme.of(context).accentTextTheme!.headline1!.backgroundColor!.withOpacity(0.1), + backgroundColor: Theme.of(context) + .accentTextTheme! + .headline1! + .backgroundColor! + .withOpacity(0.1), discount: merchant.discount, titleColor: Theme.of(context).accentTextTheme!.headline1!.backgroundColor!, subtitleColor: Theme.of(context).hintColor, diff --git a/lib/src/screens/new_wallet/new_wallet_page.dart b/lib/src/screens/new_wallet/new_wallet_page.dart index 9e0cd898c..0f15e23c5 100644 --- a/lib/src/screens/new_wallet/new_wallet_page.dart +++ b/lib/src/screens/new_wallet/new_wallet_page.dart @@ -1,11 +1,12 @@ import 'package:cake_wallet/entities/generate_name.dart'; +import 'package:cake_wallet/main.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/themes/theme_base.dart'; +import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:mobx/mobx.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/cupertino.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/core/wallet_name_validator.dart'; import 'package:cake_wallet/src/widgets/seed_language_selector.dart'; @@ -24,18 +25,14 @@ class NewWalletPage extends BasePage { final walletNameImage = Image.asset('assets/images/wallet_name.png'); - final walletNameLightImage = - Image.asset('assets/images/wallet_name_light.png'); + final walletNameLightImage = Image.asset('assets/images/wallet_name_light.png'); @override String get title => S.current.new_wallet; @override Widget body(BuildContext context) => WalletNameForm( - _walletNewVM, - currentTheme.type == ThemeType.dark - ? walletNameImage - : walletNameLightImage); + _walletNewVM, currentTheme.type == ThemeType.dark ? walletNameImage : walletNameLightImage); } class WalletNameForm extends StatefulWidget { @@ -50,9 +47,9 @@ class WalletNameForm extends StatefulWidget { class _WalletNameFormState extends State { _WalletNameFormState(this._walletNewVM) - : _formKey = GlobalKey(), - _languageSelectorKey = GlobalKey(), - _controller = TextEditingController(); + : _formKey = GlobalKey(), + _languageSelectorKey = GlobalKey(), + _controller = TextEditingController(); static const aspectRatioImage = 1.22; @@ -64,10 +61,9 @@ class _WalletNameFormState extends State { @override void initState() { - _stateReaction ??= - reaction((_) => _walletNewVM.state, (ExecutionState state) { + _stateReaction ??= reaction((_) => _walletNewVM.state, (ExecutionState state) async { if (state is ExecutedSuccessfullyState) { - Navigator.of(context) + Navigator.of(navigatorKey.currentContext!) .pushNamed(Routes.preSeed, arguments: _walletNewVM.type); } @@ -90,117 +86,118 @@ class _WalletNameFormState extends State { @override Widget build(BuildContext context) { - return Container( + return Padding( padding: EdgeInsets.only(top: 24), child: ScrollableWithBottomSection( contentPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24), - content: - Column(crossAxisAlignment: CrossAxisAlignment.center, children: [ - Padding( - padding: EdgeInsets.only(left: 12, right: 12), - child: AspectRatio( - aspectRatio: aspectRatioImage, - child: - FittedBox(child: widget.walletImage, fit: BoxFit.fill)), - ), - Padding( - padding: EdgeInsets.only(top: 24), - child: Form( - key: _formKey, - child: Stack( - alignment: Alignment.centerRight, - children: [ - TextFormField( - onChanged: (value) => _walletNewVM.name = value, - controller: _controller, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 20.0, - fontWeight: FontWeight.w600, - color: - Theme.of(context).primaryTextTheme!.headline6!.color!), - decoration: InputDecoration( - hintStyle: TextStyle( - fontSize: 18.0, - fontWeight: FontWeight.w500, - color: Theme.of(context) - .accentTextTheme! - .headline2! - .color!), - hintText: S.of(context).wallet_name, - focusedBorder: UnderlineInputBorder( - borderSide: BorderSide( - color: Theme.of(context) - .accentTextTheme! - .headline2! - .decorationColor!, - width: 1.0)), - enabledBorder: UnderlineInputBorder( - borderSide: BorderSide( - color: Theme.of(context) - .accentTextTheme! - .headline2! - .decorationColor!, - width: 1.0), - ), - suffixIcon: IconButton( - onPressed: () async { - final rName = await generateName(); - FocusManager.instance.primaryFocus?.unfocus(); + content: Center( + child: ConstrainedBox( + constraints: + BoxConstraints(maxWidth: ResponsiveLayoutUtil.kDesktopMaxWidthConstraint), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Padding( + padding: EdgeInsets.only(left: 12, right: 12), + child: AspectRatio( + aspectRatio: aspectRatioImage, + child: FittedBox(child: widget.walletImage, fit: BoxFit.fill)), + ), + Padding( + padding: EdgeInsets.only(top: 24), + child: Form( + key: _formKey, + child: Stack( + alignment: Alignment.centerRight, + children: [ + TextFormField( + onChanged: (value) => _walletNewVM.name = value, + controller: _controller, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 20.0, + fontWeight: FontWeight.w600, + color: Theme.of(context).primaryTextTheme!.headline6!.color!), + decoration: InputDecoration( + hintStyle: TextStyle( + fontSize: 18.0, + fontWeight: FontWeight.w500, + color: Theme.of(context).accentTextTheme!.headline2!.color!), + hintText: S.of(context).wallet_name, + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Theme.of(context) + .accentTextTheme! + .headline2! + .decorationColor!, + width: 1.0)), + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Theme.of(context) + .accentTextTheme! + .headline2! + .decorationColor!, + width: 1.0), + ), + suffixIcon: IconButton( + onPressed: () async { + final rName = await generateName(); + FocusManager.instance.primaryFocus?.unfocus(); - setState(() { - _controller.text = rName; - _walletNewVM.name = rName; - _controller.selection = TextSelection.fromPosition( - TextPosition(offset: _controller.text.length)); - }); - }, - icon: Container( - padding: const EdgeInsets.all(8), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(6.0), - color: Theme.of(context).hintColor, - ), - width: 34, - height: 34, - child: Image.asset( - 'assets/images/refresh_icon.png', - color: Theme.of(context) - .primaryTextTheme! - .headline4! - .decorationColor!, + setState(() { + _controller.text = rName; + _walletNewVM.name = rName; + _controller.selection = TextSelection.fromPosition( + TextPosition(offset: _controller.text.length)); + }); + }, + icon: Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(6.0), + color: Theme.of(context).hintColor, + ), + width: 34, + height: 34, + child: Image.asset( + 'assets/images/refresh_icon.png', + color: Theme.of(context) + .primaryTextTheme! + .headline4! + .decorationColor!, + ), + ), + ), ), + validator: WalletNameValidator(), ), - ), + ], ), - validator: WalletNameValidator(), ), - ], - ), + ), + if (_walletNewVM.hasLanguageSelector) ...[ + Padding( + padding: EdgeInsets.only(top: 40), + child: Text( + S.of(context).seed_language_choose, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.w500, + color: Theme.of(context).primaryTextTheme!.headline6!.color!), + ), + ), + Padding( + padding: EdgeInsets.only(top: 24), + child: SeedLanguageSelector( + key: _languageSelectorKey, initialSelected: defaultSeedLanguage), + ) + ] + ], ), ), - if (_walletNewVM.hasLanguageSelector) ...[ - Padding( - padding: EdgeInsets.only(top: 40), - child: Text( - S.of(context).seed_language_choose, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 16.0, - fontWeight: FontWeight.w500, - color: Theme.of(context).primaryTextTheme!.headline6!.color!), - ), - ), - Padding( - padding: EdgeInsets.only(top: 24), - child: SeedLanguageSelector( - key: _languageSelectorKey, - initialSelected: defaultSeedLanguage), - ) - ] - ]), - bottomSectionPadding: - EdgeInsets.all(24), + ), + bottomSectionPadding: EdgeInsets.all(24), bottomSection: Column( children: [ Observer( diff --git a/lib/src/screens/new_wallet/new_wallet_type_page.dart b/lib/src/screens/new_wallet/new_wallet_type_page.dart index 41179f34c..407582923 100644 --- a/lib/src/screens/new_wallet/new_wallet_type_page.dart +++ b/lib/src/screens/new_wallet/new_wallet_type_page.dart @@ -1,3 +1,4 @@ +import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:cake_wallet/themes/theme_base.dart'; import 'package:flutter/material.dart'; @@ -13,23 +14,19 @@ class NewWalletTypePage extends BasePage { final void Function(BuildContext, WalletType) onTypeSelected; final walletTypeImage = Image.asset('assets/images/wallet_type.png'); - final walletTypeLightImage = - Image.asset('assets/images/wallet_type_light.png'); + final walletTypeLightImage = Image.asset('assets/images/wallet_type_light.png'); @override String get title => S.current.wallet_list_restore_wallet; @override Widget body(BuildContext context) => WalletTypeForm( - onTypeSelected: onTypeSelected, - walletImage: currentTheme.type == ThemeType.dark - ? walletTypeImage - : walletTypeLightImage); + onTypeSelected: onTypeSelected, + walletImage: currentTheme.type == ThemeType.dark ? walletTypeImage : walletTypeLightImage); } class WalletTypeForm extends StatefulWidget { - WalletTypeForm({required this.onTypeSelected, - required this.walletImage}); + WalletTypeForm({required this.onTypeSelected, required this.walletImage}); final void Function(BuildContext, WalletType) onTypeSelected; final Image walletImage; @@ -39,22 +36,16 @@ class WalletTypeForm extends StatefulWidget { } class WalletTypeFormState extends State { - WalletTypeFormState() - : types = availableWalletTypes; + WalletTypeFormState() : types = availableWalletTypes; static const aspectRatioImage = 1.22; - 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 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 walletTypeImage = Image.asset('assets/images/wallet_type.png'); - final walletTypeLightImage = - Image.asset('assets/images/wallet_type_light.png'); - final havenIcon = - Image.asset('assets/images/haven_logo.png', height: 24, width: 24); + final walletTypeLightImage = Image.asset('assets/images/wallet_type_light.png'); + final havenIcon = Image.asset('assets/images/haven_logo.png', height: 24, width: 24); WalletType? selected; List types; @@ -69,35 +60,40 @@ class WalletTypeFormState extends State { Widget build(BuildContext context) { return ScrollableWithBottomSection( contentPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24), - content: Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Padding( - padding: EdgeInsets.only(left: 12, right: 12), - child: AspectRatio( - aspectRatio: aspectRatioImage, - child: FittedBox(child: widget.walletImage, fit: BoxFit.fill)), + content: Center( + child: ConstrainedBox( + constraints: BoxConstraints(maxWidth: ResponsiveLayoutUtil.kDesktopMaxWidthConstraint), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Padding( + padding: EdgeInsets.only(left: 12, right: 12), + child: AspectRatio( + aspectRatio: aspectRatioImage, + child: FittedBox(child: widget.walletImage, fit: BoxFit.fill)), + ), + Padding( + padding: EdgeInsets.only(top: 48), + child: Text( + S.of(context).choose_wallet_currency, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Theme.of(context).primaryTextTheme.headline6!.color!), + ), + ), + ...types.map((type) => Padding( + padding: EdgeInsets.only(top: 24), + child: SelectButton( + image: _iconFor(type), + text: walletTypeToDisplayName(type), + isSelected: selected == type, + onTap: () => setState(() => selected = type)), + )) + ], ), - Padding( - padding: EdgeInsets.only(top: 48), - child: Text( - S.of(context).choose_wallet_currency, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - color: Theme.of(context).primaryTextTheme.headline6!.color!), - ), - ), - ...types.map((type) => Padding( - padding: EdgeInsets.only(top: 24), - child: SelectButton( - image: _iconFor(type), - text: walletTypeToDisplayName(type), - isSelected: selected == type, - onTap: () => setState(() => selected = type)), - )) - ], + ), ), bottomSectionPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24), bottomSection: PrimaryButton( @@ -121,7 +117,8 @@ class WalletTypeFormState extends State { case WalletType.haven: return havenIcon; default: - throw Exception('_iconFor: Incorrect Wallet Type. Cannot find icon for Wallet Type: ${type.toString()}'); + throw Exception( + '_iconFor: Incorrect Wallet Type. Cannot find icon for Wallet Type: ${type.toString()}'); } } diff --git a/lib/src/screens/nodes/node_create_or_edit_page.dart b/lib/src/screens/nodes/node_create_or_edit_page.dart index 723d8b1cc..6ca77d8de 100644 --- a/lib/src/screens/nodes/node_create_or_edit_page.dart +++ b/lib/src/screens/nodes/node_create_or_edit_page.dart @@ -4,7 +4,6 @@ import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cw_core/node.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/cupertino.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:mobx/mobx.dart'; import 'package:cake_wallet/generated/i18n.dart'; diff --git a/lib/src/screens/pin_code/pin_code_widget.dart b/lib/src/screens/pin_code/pin_code_widget.dart index a647f3d95..8f30136d0 100644 --- a/lib/src/screens/pin_code/pin_code_widget.dart +++ b/lib/src/screens/pin_code/pin_code_widget.dart @@ -1,17 +1,19 @@ +import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:cake_wallet/utils/show_bar.dart'; import 'package:another_flushbar/flushbar.dart'; import 'package:flutter/material.dart'; import 'package:cake_wallet/generated/i18n.dart'; +import 'package:flutter/services.dart'; class PinCodeWidget extends StatefulWidget { - PinCodeWidget( - {required Key key, - required this.onFullPin, - required this.initialPinLength, - required this.onChangedPin, - required this.hasLengthSwitcher, - this.onChangedPinLength,}) - : super(key: key); + PinCodeWidget({ + required Key key, + required this.onFullPin, + required this.initialPinLength, + required this.onChangedPin, + required this.hasLengthSwitcher, + this.onChangedPinLength, + }) : super(key: key); final void Function(String pin, PinCodeState state) onFullPin; final void Function(String pin) onChangedPin; @@ -25,10 +27,10 @@ class PinCodeWidget extends StatefulWidget { class PinCodeState extends State { PinCodeState() - : _aspectRatio = 0, - pinLength = 0, - pin = '', - title = ''; + : _aspectRatio = 0, + pinLength = 0, + pin = '', + title = ''; static const defaultPinLength = fourPinLength; static const sixPinLength = 6; static const fourPinLength = 4; @@ -75,8 +77,7 @@ class PinCodeState extends State { void setDefaultPinLength() => changePinLength(widget.initialPinLength); void calculateAspectRatio() { - final renderBox = - _gridViewKey.currentContext!.findRenderObject() as RenderBox; + final renderBox = _gridViewKey.currentContext!.findRenderObject() as RenderBox; final cellWidth = renderBox.size.width / 3; final cellHeight = renderBox.size.height / 4; @@ -89,8 +90,7 @@ class PinCodeState extends State { void changeProcessText(String text) { hideProgressText(); - _progressBar = createBar(text, duration: null) - ..show(_key.currentContext!); + _progressBar = createBar(text, duration: null)..show(_key.currentContext!); } void close() { @@ -104,8 +104,8 @@ class PinCodeState extends State { } @override - Widget build(BuildContext context) => Scaffold( - key: _key, body: body(context), resizeToAvoidBottomInset: false); + Widget build(BuildContext context) => + Scaffold(key: _key, body: body(context), resizeToAvoidBottomInset: false); Widget body(BuildContext context) { final deleteIconImage = Image.asset( @@ -117,157 +117,184 @@ class PinCodeState extends State { color: Theme.of(context).primaryTextTheme!.headline6!.color!, ); - return Container( - color: Theme.of(context).backgroundColor, - padding: EdgeInsets.only(left: 40.0, right: 40.0, bottom: 40.0), - child: Column(children: [ - Spacer(flex: 2), - Text(title, - style: TextStyle( - fontSize: 20, - fontWeight: FontWeight.w500, - color: Theme.of(context).primaryTextTheme!.headline6!.color!)), - Spacer(flex: 3), - Container( - width: 180, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: List.generate(pinLength, (index) { - const size = 10.0; - final isFilled = pin.length > index ? pin[index] != null : false; - - return Container( - width: size, - height: size, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: isFilled - ? Theme.of(context).primaryTextTheme!.headline6!.color! - : Theme.of(context) - .accentTextTheme! - .bodyText2! - .color! - .withOpacity(0.25), - )); - }), - ), - ), - Spacer(flex: 2), - if (widget.hasLengthSwitcher) ...[ - TextButton( - onPressed: () { - changePinLength(pinLength == PinCodeState.fourPinLength - ? PinCodeState.sixPinLength - : PinCodeState.fourPinLength); - }, - child: Text( - _changePinLengthText(), + return RawKeyboardListener( + focusNode: FocusNode(), + autofocus: true, + onKey: (keyEvent) { + if (keyEvent is RawKeyDownEvent) { + if (keyEvent.logicalKey.keyLabel == "Backspace") { + _pop(); + return; + } + int? number = int.tryParse(keyEvent.character ?? ''); + if (number != null) { + _push(number); + } + } + }, + child: Container( + color: Theme.of(context).backgroundColor, + padding: EdgeInsets.only(left: 40.0, right: 40.0, bottom: 40.0), + child: Column( + children: [ + Spacer(flex: 2), + Text(title, style: TextStyle( - fontSize: 14.0, - fontWeight: FontWeight.normal, - color: Theme.of(context) - .accentTextTheme! - .bodyText2! - .decorationColor!), - )) - ], - Spacer(flex: 1), - Flexible( - flex: 24, - child: Container( - key: _gridViewKey, - child: _aspectRatio > 0 - ? GridView.count( - shrinkWrap: true, - crossAxisCount: 3, - childAspectRatio: _aspectRatio, - physics: const NeverScrollableScrollPhysics(), - children: List.generate(12, (index) { - const double marginRight = 15; - const double marginLeft = 15; + fontSize: 20, + fontWeight: FontWeight.w500, + color: Theme.of(context).primaryTextTheme!.headline6!.color!)), + Spacer(flex: 3), + Container( + width: 180, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: List.generate(pinLength, (index) { + const size = 10.0; + final isFilled = pin.length > index ? pin[index] != null : false; - if (index == 9) { - return Container( - margin: EdgeInsets.only( - left: marginLeft, right: marginRight), - child: TextButton( - onPressed: () => null, - // (widget.hasLengthSwitcher || - // !settingsStore - // .allowBiometricalAuthentication) - // ? null - // : () { - // FIXME -// if (authStore != null) { -// WidgetsBinding.instance.addPostFrameCallback((_) { -// final biometricAuth = BiometricAuth(); -// biometricAuth.isAuthenticated().then( -// (isAuth) { -// if (isAuth) { -// authStore.biometricAuth(); -// _key.currentState.showSnackBar( -// SnackBar( -// content: Text(S.of(context).authenticated), -// backgroundColor: Colors.green, -// ), -// ); -// } -// } -// ); -// }); -// } -// }, - // FIX-ME: Style - //color: Theme.of(context).backgroundColor, - //shape: CircleBorder(), - child: Container() - // (widget.hasLengthSwitcher || - // !settingsStore - // .allowBiometricalAuthentication) - // ? Offstage() - // : faceImage, + return Container( + width: size, + height: size, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: isFilled + ? Theme.of(context).primaryTextTheme!.headline6!.color! + : Theme.of(context) + .accentTextTheme! + .bodyText2! + .color! + .withOpacity(0.25), + )); + }), + ), + ), + Spacer(flex: 2), + if (widget.hasLengthSwitcher) ...[ + TextButton( + onPressed: () { + changePinLength(pinLength == PinCodeState.fourPinLength + ? PinCodeState.sixPinLength + : PinCodeState.fourPinLength); + }, + child: Text( + _changePinLengthText(), + style: TextStyle( + fontSize: 14.0, + fontWeight: FontWeight.normal, + color: Theme.of(context).accentTextTheme!.bodyText2!.decorationColor!), + ), + ) + ], + Spacer(flex: 1), + Flexible( + flex: 24, + child: Center( + child: ConstrainedBox( + constraints: BoxConstraints( + maxWidth: ResponsiveLayoutUtil.kDesktopMaxWidthConstraint, + ), + child: Container( + key: _gridViewKey, + child: _aspectRatio > 0 + ? ScrollConfiguration( + behavior: ScrollConfiguration.of(context).copyWith(scrollbars: false), + child: GridView.count( + shrinkWrap: true, + crossAxisCount: 3, + childAspectRatio: _aspectRatio, + physics: const NeverScrollableScrollPhysics(), + children: List.generate(12, (index) { + const double marginRight = 15; + const double marginLeft = 15; + + if (index == 9) { + return Container( + margin: EdgeInsets.only(left: marginLeft, right: marginRight), + child: TextButton( + onPressed: () => null, + // (widget.hasLengthSwitcher || + // !settingsStore + // .allowBiometricalAuthentication) + // ? null + // : () { + // FIXME + // if (authStore != null) { + // WidgetsBinding.instance.addPostFrameCallback((_) { + // final biometricAuth = BiometricAuth(); + // biometricAuth.isAuthenticated().then( + // (isAuth) { + // if (isAuth) { + // authStore.biometricAuth(); + // _key.currentState.showSnackBar( + // SnackBar( + // content: Text(S.of(context).authenticated), + // backgroundColor: Colors.green, + // ), + // ); + // } + // } + // ); + // }); + // } + // }, + // FIX-ME: Style + //color: Theme.of(context).backgroundColor, + //shape: CircleBorder(), + child: Container() + // (widget.hasLengthSwitcher || + // !settingsStore + // .allowBiometricalAuthentication) + // ? Offstage() + // : faceImage, + ), + ); + } else if (index == 10) { + index = 0; + } else if (index == 11) { + return Container( + margin: EdgeInsets.only(left: marginLeft, right: marginRight), + child: TextButton( + onPressed: () => _pop(), + style: TextButton.styleFrom( + backgroundColor: Theme.of(context).backgroundColor, + shape: CircleBorder(), + ), + child: deleteIconImage, + ), + ); + } else { + index++; + } + + return Container( + margin: EdgeInsets.only(left: marginLeft, right: marginRight), + child: TextButton( + onPressed: () => _push(index), + style: TextButton.styleFrom( + backgroundColor: Theme.of(context).backgroundColor, + shape: CircleBorder(), + ), + child: Text('$index', + style: TextStyle( + fontSize: 30.0, + fontWeight: FontWeight.w600, + color: Theme.of(context) + .primaryTextTheme! + .headline6! + .color!)), ), - ); - } else if (index == 10) { - index = 0; - } else if (index == 11) { - return Container( - margin: EdgeInsets.only( - left: marginLeft, right: marginRight), - child: TextButton( - onPressed: () => _pop(), - // FIX-ME: Style - //color: Theme.of(context).backgroundColor, - //shape: CircleBorder(), - child: deleteIconImage, - ), - ); - } else { - index++; - } - - return Container( - margin: EdgeInsets.only( - left: marginLeft, right: marginRight), - child: TextButton( - onPressed: () => _push(index), - // FIX-ME: Style - //color: Theme.of(context).backgroundColor, - //shape: CircleBorder(), - child: Text('$index', - style: TextStyle( - fontSize: 30.0, - fontWeight: FontWeight.w600, - color: Theme.of(context) - .primaryTextTheme! - .headline6! - .color!)), + ); + }), ), - ); - }), - ) - : null)) - ]), + ) + : null, + ), + ), + ), + ) + ], + ), + ), ); } diff --git a/lib/src/screens/receive/anonpay_invoice_page.dart b/lib/src/screens/receive/anonpay_invoice_page.dart index 91c9aaef1..055307d08 100644 --- a/lib/src/screens/receive/anonpay_invoice_page.dart +++ b/lib/src/screens/receive/anonpay_invoice_page.dart @@ -8,6 +8,7 @@ import 'package:cake_wallet/src/screens/dashboard/widgets/present_receive_option import 'package:cake_wallet/src/screens/receive/widgets/anonpay_input_form.dart'; import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; import 'package:cake_wallet/src/widgets/keyboard_done_button.dart'; +import 'package:cake_wallet/utils/device_info.dart'; import 'package:cake_wallet/view_model/anon_invoice_page_view_model.dart'; import 'package:cake_wallet/view_model/dashboard/receive_option_view_model.dart'; import 'package:flutter/material.dart'; @@ -85,7 +86,7 @@ class AnonPayInvoicePage extends BasePage { child: ScrollableWithBottomSection( contentPadding: EdgeInsets.only(bottom: 24), content: Container( - decoration: BoxDecoration( + decoration: DeviceInfo.instance.isMobile ? BoxDecoration( borderRadius: BorderRadius.only( bottomLeft: Radius.circular(24), bottomRight: Radius.circular(24)), gradient: LinearGradient( @@ -96,7 +97,7 @@ class AnonPayInvoicePage extends BasePage { begin: Alignment.topLeft, end: Alignment.bottomRight, ), - ), + ) : null, child: Observer(builder: (_) { return Padding( padding: EdgeInsets.fromLTRB(24, 120, 24, 0), @@ -174,7 +175,6 @@ class AnonPayInvoicePage extends BasePage { } reaction((_) => receiveOptionViewModel.selectedReceiveOption, (ReceivePageOption option) { - Navigator.pop(context); switch (option) { case ReceivePageOption.mainnet: Navigator.popAndPushNamed(context, Routes.addressPage); diff --git a/lib/src/screens/receive/widgets/qr_widget.dart b/lib/src/screens/receive/widgets/qr_widget.dart index 96a19f850..9e68ff0e1 100644 --- a/lib/src/screens/receive/widgets/qr_widget.dart +++ b/lib/src/screens/receive/widgets/qr_widget.dart @@ -1,4 +1,5 @@ import 'package:cake_wallet/routes.dart'; +import 'package:cake_wallet/utils/device_info.dart'; import 'package:cake_wallet/utils/show_bar.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:device_display_brightness/device_display_brightness.dart'; @@ -35,7 +36,7 @@ class QRWidget extends StatelessWidget { @override Widget build(BuildContext context) { final copyImage = Image.asset('assets/images/copy_address.png', - color: Theme.of(context).textTheme!.subtitle1!.decorationColor!); + color: Theme.of(context).textTheme.subtitle1!.decorationColor!); return Column( mainAxisSize: MainAxisSize.min, @@ -51,23 +52,18 @@ class QRWidget extends StatelessWidget { style: TextStyle( fontSize: 14, fontWeight: FontWeight.w500, - color: Theme.of(context).accentTextTheme!.headline2!.backgroundColor!), + color: Theme.of(context).accentTextTheme.headline2!.backgroundColor!), ), ), Row( children: [ Spacer(flex: 3), Observer( - builder: (_) { - return Flexible( - flex: 5, - child: GestureDetector( - onTap: () async { - // Get the current brightness: - final double brightness = await DeviceDisplayBrightness.getBrightness(); - - // ignore: unawaited_futures - DeviceDisplayBrightness.setBrightness(1.0); + builder: (_) => Flexible( + flex: 5, + child: GestureDetector( + onTap: () { + changeBrightnessForRoute(() async { await Navigator.pushNamed( context, Routes.fullscreenQR, @@ -75,31 +71,28 @@ class QRWidget extends StatelessWidget { 'qrData': addressListViewModel.uri.toString(), }, ); - // ignore: unawaited_futures - DeviceDisplayBrightness.setBrightness(brightness); - }, - child: Hero( - tag: Key(addressListViewModel.uri.toString()), - child: Center( - child: AspectRatio( - aspectRatio: 1.0, - child: Container( - padding: EdgeInsets.all(5), - decoration: BoxDecoration( - border: Border.all( - width: 3, - color: - Theme.of(context).accentTextTheme!.headline2!.backgroundColor!, - ), + }); + }, + child: Hero( + tag: Key(addressListViewModel.uri.toString()), + child: Center( + child: AspectRatio( + aspectRatio: 1.0, + child: Container( + padding: EdgeInsets.all(5), + decoration: BoxDecoration( + border: Border.all( + width: 3, + color: Theme.of(context).accentTextTheme.headline2!.backgroundColor!, ), - child: QrImage(data: addressListViewModel.uri.toString(), version: qrVersion), ), + child: QrImage(data: addressListViewModel.uri.toString()), ), ), ), ), - ); - } + ), + ), ), Spacer(flex: 3) ], @@ -176,4 +169,23 @@ class QRWidget extends StatelessWidget { ], ); } + + Future changeBrightnessForRoute(Future Function() navigation) async { + // if not mobile, just navigate + if (!DeviceInfo.instance.isMobile) { + navigation(); + return; + } + + // Get the current brightness: + final brightness = await DeviceDisplayBrightness.getBrightness(); + + // ignore: unawaited_futures + DeviceDisplayBrightness.setBrightness(1.0); + + await navigation(); + + // ignore: unawaited_futures + DeviceDisplayBrightness.setBrightness(brightness); + } } diff --git a/lib/src/screens/restore/restore_from_backup_page.dart b/lib/src/screens/restore/restore_from_backup_page.dart index 1ed2aec4b..16aa3dbef 100644 --- a/lib/src/screens/restore/restore_from_backup_page.dart +++ b/lib/src/screens/restore/restore_from_backup_page.dart @@ -1,6 +1,7 @@ import 'package:cake_wallet/core/execution_state.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; +import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:flutter/material.dart'; import 'package:flutter/src/widgets/framework.dart'; @@ -39,43 +40,48 @@ class RestoreFromBackupPage extends BasePage { } }); - return Container( - padding: EdgeInsets.only(bottom: 24, left: 24, right: 24), - child: Column(children: [ - Expanded( - child: Container( - child: Center( - child: TextFormField( - obscureText: true, - enableSuggestions: false, - autocorrect: false, - decoration: InputDecoration( - hintText: S.of(context).enter_backup_password), - keyboardType: TextInputType.visiblePassword, - controller: textEditingController, - style: TextStyle(fontSize: 26, color: Colors.black))), - ), - ), - Container( - child: Row(children: [ - Expanded( - child: PrimaryButton( - onPressed: () => presentFilePicker(), - text: S.of(context).select_backup_file, - color: Colors.grey, - textColor: Colors.white)), - SizedBox(width: 20), - Expanded(child: Observer(builder: (_) { - return LoadingPrimaryButton( - isLoading: - restoreFromBackupViewModel.state is IsExecutingState, - onPressed: () => onImportHandler(context), - text: S.of(context).import, - color: Theme.of(context).accentTextTheme!.bodyText1!.color!, - textColor: Colors.white); - })) - ])), - ])); + return Center( + child: ConstrainedBox( + constraints: BoxConstraints(maxWidth: ResponsiveLayoutUtil.kDesktopMaxWidthConstraint), + child: Padding( + padding: EdgeInsets.only(bottom: 24, left: 24, right: 24), + child: Column(children: [ + Expanded( + child: Container( + child: Center( + child: TextFormField( + obscureText: true, + enableSuggestions: false, + autocorrect: false, + decoration: InputDecoration( + hintText: S.of(context).enter_backup_password), + keyboardType: TextInputType.visiblePassword, + controller: textEditingController, + style: TextStyle(fontSize: 26, color: Colors.black))), + ), + ), + Container( + child: Row(children: [ + Expanded( + child: PrimaryButton( + onPressed: () => presentFilePicker(), + text: S.of(context).select_backup_file, + color: Colors.grey, + textColor: Colors.white)), + SizedBox(width: 20), + Expanded(child: Observer(builder: (_) { + return LoadingPrimaryButton( + isLoading: + restoreFromBackupViewModel.state is IsExecutingState, + onPressed: () => onImportHandler(context), + text: S.of(context).import, + color: Theme.of(context).accentTextTheme!.bodyText1!.color!, + textColor: Colors.white); + })) + ])), + ])), + ), + ); } Future presentFilePicker() async { diff --git a/lib/src/screens/restore/restore_options_page.dart b/lib/src/screens/restore/restore_options_page.dart index a49fd3cc7..a7eb03778 100644 --- a/lib/src/screens/restore/restore_options_page.dart +++ b/lib/src/screens/restore/restore_options_page.dart @@ -1,3 +1,4 @@ +import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:flutter/material.dart'; import 'package:cake_wallet/routes.dart'; import 'package:flutter/cupertino.dart'; @@ -18,31 +19,33 @@ class RestoreOptionsPage extends BasePage { @override Widget body(BuildContext context) { - return Container( - width: double.infinity, - height: double.infinity, - padding: EdgeInsets.all(24), - child: SingleChildScrollView( - child: Column( - children: [ - RestoreButton( - onPressed: () => - Navigator.pushNamed(context, Routes.restoreWalletOptionsFromWelcome), - image: imageSeedKeys, - title: S.of(context).restore_title_from_seed_keys, - description: - S.of(context).restore_description_from_seed_keys), - Padding( - padding: EdgeInsets.only(top: 24), - child: RestoreButton( + return Center( + child: Container( + width: ResponsiveLayoutUtil.kDesktopMaxWidthConstraint, + height: double.infinity, + padding: EdgeInsets.symmetric(vertical: 24), + child: SingleChildScrollView( + child: Column( + children: [ + RestoreButton( onPressed: () => - Navigator.pushNamed(context, Routes.restoreFromBackup), - image: imageBackup, - title: S.of(context).restore_title_from_backup, - description: S.of(context).restore_description_from_backup), - ) - ], - ), - )); + Navigator.pushNamed(context, Routes.restoreWalletOptionsFromWelcome), + image: imageSeedKeys, + title: S.of(context).restore_title_from_seed_keys, + description: + S.of(context).restore_description_from_seed_keys), + Padding( + padding: EdgeInsets.only(top: 24), + child: RestoreButton( + onPressed: () => + Navigator.pushNamed(context, Routes.restoreFromBackup), + image: imageBackup, + title: S.of(context).restore_title_from_backup, + description: S.of(context).restore_description_from_backup), + ) + ], + ), + )), + ); } } diff --git a/lib/src/screens/restore/wallet_restore_page.dart b/lib/src/screens/restore/wallet_restore_page.dart index ce9af985e..7288d624b 100644 --- a/lib/src/screens/restore/wallet_restore_page.dart +++ b/lib/src/screens/restore/wallet_restore_page.dart @@ -1,5 +1,6 @@ import 'package:cake_wallet/src/widgets/keyboard_done_button.dart'; import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart'; +import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; @@ -23,10 +24,8 @@ import 'package:cake_wallet/core/seed_validator.dart'; class WalletRestorePage extends BasePage { WalletRestorePage(this.walletRestoreViewModel) - : walletRestoreFromSeedFormKey = - GlobalKey(), - walletRestoreFromKeysFormKey = - GlobalKey(), + : walletRestoreFromSeedFormKey = GlobalKey(), + walletRestoreFromKeysFormKey = GlobalKey(), _pages = [], _blockHeightFocusNode = FocusNode(), _controller = PageController(initialPage: 0) { @@ -36,9 +35,8 @@ class WalletRestorePage extends BasePage { _pages.add(WalletRestoreFromSeedForm( displayBlockHeightSelector: walletRestoreViewModel.hasBlockchainHeightLanguageSelector, - displayLanguageSelector: - walletRestoreViewModel.hasSeedLanguageSelector, - type: walletRestoreViewModel.type!, + displayLanguageSelector: walletRestoreViewModel.hasSeedLanguageSelector, + type: walletRestoreViewModel.type, key: walletRestoreFromSeedFormKey, blockHeightFocusNode: _blockHeightFocusNode, onHeightOrDateEntered: (value) { @@ -48,26 +46,22 @@ class WalletRestorePage extends BasePage { }, onSeedChange: (String seed) { if (walletRestoreViewModel.hasBlockchainHeightLanguageSelector) { - final hasHeight = walletRestoreFromSeedFormKey - .currentState!.blockchainHeightKey.currentState!.restoreHeightController - .text - .isNotEmpty; + final hasHeight = walletRestoreFromSeedFormKey.currentState!.blockchainHeightKey + .currentState!.restoreHeightController.text.isNotEmpty; if (hasHeight) { walletRestoreViewModel.isButtonEnabled = _isValidSeed(); } } else { - walletRestoreViewModel.isButtonEnabled = _isValidSeed(); + walletRestoreViewModel.isButtonEnabled = _isValidSeed(); } }, onLanguageChange: (_) { if (walletRestoreViewModel.hasBlockchainHeightLanguageSelector) { - final hasHeight = walletRestoreFromSeedFormKey - .currentState!.blockchainHeightKey.currentState!.restoreHeightController - .text - .isNotEmpty; + final hasHeight = walletRestoreFromSeedFormKey.currentState!.blockchainHeightKey + .currentState!.restoreHeightController.text.isNotEmpty; if (hasHeight) { - walletRestoreViewModel.isButtonEnabled = _isValidSeed(); + walletRestoreViewModel.isButtonEnabled = _isValidSeed(); } } else { walletRestoreViewModel.isButtonEnabled = _isValidSeed(); @@ -78,8 +72,7 @@ class WalletRestorePage extends BasePage { _pages.add(WalletRestoreFromKeysFrom( key: walletRestoreFromKeysFormKey, walletRestoreViewModel: walletRestoreViewModel, - onHeightOrDateEntered: (value) => - walletRestoreViewModel.isButtonEnabled = value)); + onHeightOrDateEntered: (value) => walletRestoreViewModel.isButtonEnabled = value)); break; default: break; @@ -97,8 +90,7 @@ class WalletRestorePage extends BasePage { fontSize: 18.0, fontWeight: FontWeight.bold, fontFamily: 'Lato', - color: titleColor ?? - Theme.of(context).primaryTextTheme!.headline6!.color!), + color: titleColor ?? Theme.of(context).primaryTextTheme.headline6!.color!), )); final WalletRestoreViewModel walletRestoreViewModel; @@ -129,134 +121,134 @@ class WalletRestorePage extends BasePage { reaction((_) => walletRestoreViewModel.mode, (WalletRestoreMode mode) { walletRestoreViewModel.isButtonEnabled = false; - walletRestoreFromSeedFormKey.currentState!.blockchainHeightKey.currentState - !.restoreHeightController.text = ''; - walletRestoreFromSeedFormKey.currentState!.blockchainHeightKey.currentState - !.dateController.text = ''; + walletRestoreFromSeedFormKey + .currentState!.blockchainHeightKey.currentState!.restoreHeightController.text = ''; + walletRestoreFromSeedFormKey + .currentState!.blockchainHeightKey.currentState!.dateController.text = ''; walletRestoreFromSeedFormKey.currentState!.nameTextEditingController.text = ''; - walletRestoreFromKeysFormKey.currentState!.blockchainHeightKey.currentState - !.restoreHeightController.text = ''; - walletRestoreFromKeysFormKey.currentState!.blockchainHeightKey.currentState - !.dateController.text = ''; + walletRestoreFromKeysFormKey + .currentState!.blockchainHeightKey.currentState!.restoreHeightController.text = ''; + walletRestoreFromKeysFormKey + .currentState!.blockchainHeightKey.currentState!.dateController.text = ''; walletRestoreFromKeysFormKey.currentState!.nameTextEditingController.text = ''; }); return KeyboardActions( - config: KeyboardActionsConfig( - keyboardActionsPlatform: KeyboardActionsPlatform.IOS, - keyboardBarColor: Theme.of(context).accentTextTheme!.bodyText1! - .backgroundColor!, - nextFocus: false, - actions: [ - KeyboardActionsItem( - focusNode: _blockHeightFocusNode, - toolbarButtons: [(_) => KeyboardDoneButton()], - ) - ]), - child: Container( - height: 0, - color: Theme.of(context).backgroundColor, - child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [ - Expanded( - child: PageView.builder( - onPageChanged: (page) { - walletRestoreViewModel.mode = - page == 0 ? WalletRestoreMode.seed : WalletRestoreMode.keys; - }, - controller: _controller, - itemCount: _pages.length, - itemBuilder: (_, index) => - SingleChildScrollView(child: _pages[index]))), - if (_pages.length > 1) - Padding( - padding: EdgeInsets.only(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).hintColor.withOpacity(0.5), - activeDotColor: Theme.of(context).hintColor), - )), - Padding( - padding: EdgeInsets.only(top: 20, bottom: 24, left: 24, right: 24), - child: Observer( - builder: (context) { - return LoadingPrimaryButton( - onPressed: _confirmForm, - text: S.of(context).restore_recover, - color: - Theme.of(context).accentTextTheme!.subtitle2!.decorationColor!, - textColor: - Theme.of(context).accentTextTheme!.headline5!.decorationColor!, - isLoading: walletRestoreViewModel.state is IsExecutingState, - isDisabled: !walletRestoreViewModel.isButtonEnabled, - ); - }, - )) - ]))); + config: KeyboardActionsConfig( + keyboardActionsPlatform: KeyboardActionsPlatform.IOS, + keyboardBarColor: Theme.of(context).accentTextTheme.bodyText1!.backgroundColor!, + nextFocus: false, + actions: [ + KeyboardActionsItem( + focusNode: _blockHeightFocusNode, + toolbarButtons: [(_) => KeyboardDoneButton()], + ) + ], + ), + child: Container( + height: 0, + color: Theme.of(context).backgroundColor, + child: Center( + child: ConstrainedBox( + constraints: BoxConstraints(maxWidth: ResponsiveLayoutUtil.kDesktopMaxWidthConstraint), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Expanded( + child: PageView.builder( + onPageChanged: (page) { + walletRestoreViewModel.mode = + page == 0 ? WalletRestoreMode.seed : WalletRestoreMode.keys; + }, + controller: _controller, + itemCount: _pages.length, + itemBuilder: (_, index) => SingleChildScrollView(child: _pages[index]), + ), + ), + if (_pages.length > 1) + Padding( + padding: EdgeInsets.only(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).hintColor.withOpacity(0.5), + activeDotColor: Theme.of(context).hintColor, + ), + ), + ), + Padding( + padding: EdgeInsets.only(top: 20, bottom: 24, left: 24, right: 24), + child: Observer( + builder: (context) { + return LoadingPrimaryButton( + onPressed: _confirmForm, + text: S.of(context).restore_recover, + color: Theme.of(context).accentTextTheme.subtitle2!.decorationColor!, + textColor: Theme.of(context).accentTextTheme.headline5!.decorationColor!, + isLoading: walletRestoreViewModel.state is IsExecutingState, + isDisabled: !walletRestoreViewModel.isButtonEnabled, + ); + }, + ), + ) + ], + ), + ), + ), + ), + ); } bool _isValidSeed() { - final seedWords = walletRestoreFromSeedFormKey - .currentState - !.seedWidgetStateKey - .currentState - !.text - .split(' '); + final seedWords = + walletRestoreFromSeedFormKey.currentState!.seedWidgetStateKey.currentState!.text.split(' '); - if ((walletRestoreViewModel.type == WalletType.monero || walletRestoreViewModel.type == WalletType.haven) && + if ((walletRestoreViewModel.type == WalletType.monero || + walletRestoreViewModel.type == WalletType.haven) && seedWords.length != WalletRestoreViewModelBase.moneroSeedMnemonicLength) { return false; } - + if ((walletRestoreViewModel.type == WalletType.bitcoin || - walletRestoreViewModel.type == WalletType.litecoin) && - (seedWords.length != WalletRestoreViewModelBase.electrumSeedMnemonicLength && - seedWords.length != WalletRestoreViewModelBase.electrumShortSeedMnemonicLength)) { + walletRestoreViewModel.type == WalletType.litecoin) && + (seedWords.length != WalletRestoreViewModelBase.electrumSeedMnemonicLength && + seedWords.length != WalletRestoreViewModelBase.electrumShortSeedMnemonicLength)) { return false; } - final words = walletRestoreFromSeedFormKey - .currentState - !.seedWidgetStateKey - .currentState - !.words - .toSet(); - return seedWords - .toSet() - .difference(words) - .toSet() - .isEmpty; + final words = + walletRestoreFromSeedFormKey.currentState!.seedWidgetStateKey.currentState!.words.toSet(); + return seedWords.toSet().difference(words).toSet().isEmpty; } Map _credentials() { final credentials = {}; if (walletRestoreViewModel.mode == WalletRestoreMode.seed) { - credentials['seed'] = walletRestoreFromSeedFormKey - .currentState!.seedWidgetStateKey.currentState!.text; + credentials['seed'] = + walletRestoreFromSeedFormKey.currentState!.seedWidgetStateKey.currentState!.text; if (walletRestoreViewModel.hasBlockchainHeightLanguageSelector) { - credentials['height'] = walletRestoreFromSeedFormKey - .currentState!.blockchainHeightKey.currentState!.height; + credentials['height'] = + walletRestoreFromSeedFormKey.currentState!.blockchainHeightKey.currentState!.height; } - credentials['name'] = walletRestoreFromSeedFormKey.currentState!.nameTextEditingController.text; + credentials['name'] = + walletRestoreFromSeedFormKey.currentState!.nameTextEditingController.text; } else { - credentials['address'] = - walletRestoreFromKeysFormKey.currentState!.addressController.text; - credentials['viewKey'] = - walletRestoreFromKeysFormKey.currentState!.viewKeyController.text; - credentials['spendKey'] = - walletRestoreFromKeysFormKey.currentState!.spendKeyController.text; - credentials['height'] = walletRestoreFromKeysFormKey - .currentState!.blockchainHeightKey.currentState!.height; - credentials['name'] = walletRestoreFromKeysFormKey.currentState!.nameTextEditingController.text; + credentials['address'] = walletRestoreFromKeysFormKey.currentState!.addressController.text; + credentials['viewKey'] = walletRestoreFromKeysFormKey.currentState!.viewKeyController.text; + credentials['spendKey'] = walletRestoreFromKeysFormKey.currentState!.spendKeyController.text; + credentials['height'] = + walletRestoreFromKeysFormKey.currentState!.blockchainHeightKey.currentState!.height; + credentials['name'] = + walletRestoreFromKeysFormKey.currentState!.nameTextEditingController.text; } return credentials; @@ -272,10 +264,8 @@ class WalletRestorePage extends BasePage { : walletRestoreFromKeysFormKey.currentState!.formKey; final name = walletRestoreViewModel.mode == WalletRestoreMode.seed - ? walletRestoreFromSeedFormKey - .currentState!.nameTextEditingController.value.text - : walletRestoreFromKeysFormKey - .currentState!.nameTextEditingController.value.text; + ? walletRestoreFromSeedFormKey.currentState!.nameTextEditingController.value.text + : walletRestoreFromKeysFormKey.currentState!.nameTextEditingController.value.text; if (!formKey.currentState!.validate()) { return; @@ -301,5 +291,3 @@ class WalletRestorePage extends BasePage { }); } } - - diff --git a/lib/src/screens/root/root.dart b/lib/src/screens/root/root.dart index 2526d15f3..fe7ea26a8 100644 --- a/lib/src/screens/root/root.dart +++ b/lib/src/screens/root/root.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'package:cake_wallet/core/auth_service.dart'; +import 'package:cake_wallet/utils/device_info.dart'; import 'package:cake_wallet/utils/payment_request.dart'; import 'package:flutter/material.dart'; import 'package:cake_wallet/routes.dart'; @@ -54,7 +55,9 @@ class RootState extends State with WidgetsBindingObserver { WidgetsBinding.instance.addObserver(this); super.initState(); - initUniLinks(); + if (DeviceInfo.instance.isMobile) { + initUniLinks(); + } } @override diff --git a/lib/src/screens/seed/pre_seed_page.dart b/lib/src/screens/seed/pre_seed_page.dart index c4ecbbf9a..86d88c96e 100644 --- a/lib/src/screens/seed/pre_seed_page.dart +++ b/lib/src/screens/seed/pre_seed_page.dart @@ -1,3 +1,4 @@ +import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/themes/theme_base.dart'; @@ -33,44 +34,48 @@ class PreSeedPage extends BasePage { return WillPopScope( onWillPop: () async => false, child: Container( + alignment: Alignment.center, padding: EdgeInsets.all(24), - child: Column( - children: [ - Flexible( - flex: 2, - child: AspectRatio( - aspectRatio: 1, - child: FittedBox(child: image, fit: BoxFit.contain))), - Flexible( - flex: 3, - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Padding( - padding: EdgeInsets.only(top: 70, left: 16, right: 16), - child: Text( - S - .of(context) - .pre_seed_description(wordsCount.toString()), - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.normal, - color: Theme.of(context) - .primaryTextTheme! - .caption! - .color!), + child: ConstrainedBox( + constraints: BoxConstraints(maxWidth: ResponsiveLayoutUtil.kDesktopMaxWidthConstraint), + child: Column( + children: [ + Flexible( + flex: 2, + child: AspectRatio( + aspectRatio: 1, + child: FittedBox(child: image, fit: BoxFit.contain))), + Flexible( + flex: 3, + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: EdgeInsets.only(top: 70, left: 16, right: 16), + child: Text( + S + .of(context) + .pre_seed_description(wordsCount.toString()), + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.normal, + color: Theme.of(context) + .primaryTextTheme! + .caption! + .color!), + ), ), - ), - PrimaryButton( - onPressed: () => Navigator.of(context) - .popAndPushNamed(Routes.seed, arguments: true), - text: S.of(context).pre_seed_button_text, - color: Theme.of(context).accentTextTheme!.bodyText1!.color!, - textColor: Colors.white) - ], - )) - ], + PrimaryButton( + onPressed: () => Navigator.of(context) + .popAndPushNamed(Routes.seed, arguments: true), + text: S.of(context).pre_seed_button_text, + color: Theme.of(context).accentTextTheme!.bodyText1!.color!, + textColor: Colors.white) + ], + )) + ], + ), ), )); } diff --git a/lib/src/screens/seed/wallet_seed_page.dart b/lib/src/screens/seed/wallet_seed_page.dart index 64895db36..034ab832c 100644 --- a/lib/src/screens/seed/wallet_seed_page.dart +++ b/lib/src/screens/seed/wallet_seed_page.dart @@ -2,6 +2,7 @@ import 'package:cake_wallet/palette.dart'; import 'package:cake_wallet/themes/theme_base.dart'; import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart'; import 'package:cake_wallet/utils/share_util.dart'; +import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:cake_wallet/utils/show_bar.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:flutter/material.dart'; @@ -52,7 +53,7 @@ class WalletSeedPage extends BasePage { @override Widget? leading(BuildContext context) => - isNewWalletCreated ? Offstage() : super.leading(context); + isNewWalletCreated ? null: super.leading(context); @override Widget trailing(BuildContext context) { @@ -85,114 +86,119 @@ class WalletSeedPage extends BasePage { return WillPopScope(onWillPop: () async => false, child: Container( padding: EdgeInsets.all(24), - child: Column( - children: [ - Flexible( - flex: 2, - child: AspectRatio( - aspectRatio: 1, - child: FittedBox(child: image, fit: BoxFit.fill))), - Flexible( - flex: 3, - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Padding( - padding: EdgeInsets.only(top: 33), - child: Observer(builder: (_) { - return Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Text( - walletSeedViewModel.name, - style: TextStyle( - fontSize: 20, - fontWeight: FontWeight.w600, - color: Theme.of(context) - .primaryTextTheme! - .headline6! - .color!), - ), - Padding( - padding: - EdgeInsets.only(top: 20, left: 16, right: 16), - child: Text( - walletSeedViewModel.seed, - textAlign: TextAlign.center, + alignment: Alignment.center, + child: ConstrainedBox( + constraints: + BoxConstraints(maxWidth: ResponsiveLayoutUtil.kDesktopMaxWidthConstraint), + child: Column( + children: [ + Flexible( + flex: 2, + child: AspectRatio( + aspectRatio: 1, + child: FittedBox(child: image, fit: BoxFit.fill))), + Flexible( + flex: 3, + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: EdgeInsets.only(top: 33), + child: Observer(builder: (_) { + return Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + walletSeedViewModel.name, style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.normal, + fontSize: 20, + fontWeight: FontWeight.w600, color: Theme.of(context) .primaryTextTheme! - .caption! + .headline6! .color!), ), - ) - ], - ); - }), - ), - Column( - children: [ - isNewWalletCreated - ? Padding( - padding: EdgeInsets.only( - bottom: 52, left: 43, right: 43), + Padding( + padding: + EdgeInsets.only(top: 20, left: 16, right: 16), child: Text( - S.of(context).seed_reminder, + walletSeedViewModel.seed, textAlign: TextAlign.center, style: TextStyle( - fontSize: 12, + fontSize: 14, fontWeight: FontWeight.normal, color: Theme.of(context) .primaryTextTheme! - .overline! + .caption! .color!), ), ) - : Offstage(), - Row( - mainAxisSize: MainAxisSize.max, - children: [ - Flexible( - child: Container( - padding: EdgeInsets.only(right: 8.0), - child: PrimaryButton( - onPressed: () { - ShareUtil.share( - text: walletSeedViewModel.seed, - context: context, - ); - }, - text: S.of(context).save, - color: Colors.green, - textColor: Colors.white), - )), - Flexible( - child: Container( - padding: EdgeInsets.only(left: 8.0), - child: Builder( - builder: (context) => PrimaryButton( - onPressed: () { - Clipboard.setData(ClipboardData( - text: walletSeedViewModel.seed)); - showBar(context, - S.of(context).copied_to_clipboard); - }, - text: S.of(context).copy, - color: Theme.of(context) - .accentTextTheme! - .bodyText2! - .color!, - textColor: Colors.white)), - )) - ], - ) - ], - ) - ], - )) - ], + ], + ); + }), + ), + Column( + children: [ + isNewWalletCreated + ? Padding( + padding: EdgeInsets.only( + bottom: 52, left: 43, right: 43), + child: Text( + S.of(context).seed_reminder, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.normal, + color: Theme.of(context) + .primaryTextTheme! + .overline! + .color!), + ), + ) + : Offstage(), + Row( + mainAxisSize: MainAxisSize.max, + children: [ + Flexible( + child: Container( + padding: EdgeInsets.only(right: 8.0), + child: PrimaryButton( + onPressed: () { + ShareUtil.share( + text: walletSeedViewModel.seed, + context: context, + ); + }, + text: S.of(context).save, + color: Colors.green, + textColor: Colors.white), + )), + Flexible( + child: Container( + padding: EdgeInsets.only(left: 8.0), + child: Builder( + builder: (context) => PrimaryButton( + onPressed: () { + Clipboard.setData(ClipboardData( + text: walletSeedViewModel.seed)); + showBar(context, + S.of(context).copied_to_clipboard); + }, + text: S.of(context).copy, + color: Theme.of(context) + .accentTextTheme! + .bodyText2! + .color!, + textColor: Colors.white)), + )) + ], + ) + ], + ) + ], + )) + ], + ), ))); } } diff --git a/lib/src/screens/send/send_page.dart b/lib/src/screens/send/send_page.dart index 881536944..c30c46565 100644 --- a/lib/src/screens/send/send_page.dart +++ b/lib/src/screens/send/send_page.dart @@ -1,10 +1,12 @@ import 'package:cake_wallet/entities/fiat_currency.dart'; import 'package:cake_wallet/src/screens/dashboard/widgets/sync_indicator_icon.dart'; import 'package:cake_wallet/src/screens/send/widgets/send_card.dart'; +import 'package:cake_wallet/src/widgets/add_template_button.dart'; import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart'; import 'package:cake_wallet/src/widgets/picker.dart'; import 'package:cake_wallet/src/widgets/template_tile.dart'; import 'package:cake_wallet/utils/payment_request.dart'; +import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:cake_wallet/view_model/send/output.dart'; import 'package:flutter/material.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; @@ -19,7 +21,6 @@ import 'package:cake_wallet/src/widgets/trail_button.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cake_wallet/view_model/send/send_view_model_state.dart'; import 'package:cake_wallet/generated/i18n.dart'; -import 'package:dotted_border/dotted_border.dart'; import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; import 'package:cake_wallet/src/screens/send/widgets/confirm_sending_alert.dart'; import 'package:smooth_page_indicator/smooth_page_indicator.dart'; @@ -50,9 +51,21 @@ class SendPage extends BasePage { @override bool get extendBodyBehindAppBar => true; + @override + bool get canUseCloseIcon => true; + @override AppBarStyle get appBarStyle => AppBarStyle.transparent; + double _sendCardHeight(BuildContext context) { + final double initialHeight = sendViewModel.isElectrumWallet ? 490 : 465; + + if (!ResponsiveLayoutUtil.instance.isMobile(context)) { + return initialHeight - 66; + } + return initialHeight; + } + @override void onClose(BuildContext context) { sendViewModel.onClose(); @@ -104,178 +117,137 @@ class SendPage extends BasePage { key: _formKey, child: ScrollableWithBottomSection( contentPadding: EdgeInsets.only(bottom: 24), - content: Column( - children: [ - Container( - height: sendViewModel.isElectrumWallet ? 490 : 465, - child: Observer( - builder: (_) { - return PageView.builder( - scrollDirection: Axis.horizontal, - controller: controller, - itemCount: sendViewModel.outputs.length, - itemBuilder: (context, index) { - final output = sendViewModel.outputs[index]; - - return SendCard( - key: output.key, - output: output, - sendViewModel: sendViewModel, - initialPaymentRequest: initialPaymentRequest, - ); - }); - }, - )), - Padding( - padding: - EdgeInsets.only(top: 10, left: 24, right: 24, bottom: 10), - child: Container( - height: 10, - child: Observer( - builder: (_) { - final count = sendViewModel.outputs.length; - - return count > 1 - ? SmoothPageIndicator( - controller: controller, - count: count, - effect: ScrollingDotsEffect( - spacing: 6.0, - radius: 6.0, - dotWidth: 6.0, - dotHeight: 6.0, - dotColor: Theme.of(context) - .primaryTextTheme! - .headline3! - .backgroundColor!, - activeDotColor: Theme.of(context) - .primaryTextTheme! - .headline2! - .backgroundColor!), - ) - : Offstage(); - }, - ), - ), - ), - if (sendViewModel.hasMultiRecipient) - Container( - height: 40, - width: double.infinity, - padding: EdgeInsets.only(left: 24), - child: SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: Observer( - builder: (_) { - final templates = sendViewModel.templates; - final itemCount = templates.length; - - return Row( - children: [ - GestureDetector( - onTap: () => Navigator.of(context) - .pushNamed(Routes.sendTemplate), - child: Container( - padding: EdgeInsets.only(left: 1, right: 10), - child: DottedBorder( - borderType: BorderType.RRect, - dashPattern: [6, 4], - color: Theme.of(context) - .primaryTextTheme! - .headline2! - .decorationColor!, - strokeWidth: 2, - radius: Radius.circular(20), - child: Container( - height: 34, - padding: EdgeInsets.only(left: 10, right: 10), - alignment: Alignment.center, - decoration: BoxDecoration( - borderRadius: - BorderRadius.all(Radius.circular(20)), - color: Colors.transparent, - ), - child: templates.length >= 1 - ? Icon( - Icons.add, - color: Theme.of(context) - .primaryTextTheme! - .headline2! - .color!, - ) - : Text( - S.of(context).new_template, - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w600, - color: Theme.of(context) - .primaryTextTheme! - .headline2! - .color!, - ), - ), - ), - ), - ), - ), - ListView.builder( + content: FocusTraversalGroup( + policy: OrderedTraversalPolicy(), + child: Column( + children: [ + Container( + height: _sendCardHeight(context), + child: Observer( + builder: (_) { + return PageView.builder( scrollDirection: Axis.horizontal, - shrinkWrap: true, - physics: NeverScrollableScrollPhysics(), - itemCount: itemCount, + controller: controller, + itemCount: sendViewModel.outputs.length, itemBuilder: (context, index) { - final template = templates[index]; - return TemplateTile( - key: UniqueKey(), - to: template.name, - amount: template.isCurrencySelected ? template.amount : template.amountFiat, - from: template.isCurrencySelected ? template.cryptoCurrency : template.fiatCurrency, - onTap: () async { - final fiatFromTemplate = FiatCurrency.all.singleWhere((element) => element.title == template.fiatCurrency); - final output = _defineCurrentOutput(); - output.address = template.address; - if(template.isCurrencySelected){ - output.setCryptoAmount(template.amount); - }else{ - sendViewModel.setFiatCurrency(fiatFromTemplate); - output.setFiatAmount(template.amountFiat); - } - output.resetParsedAddress(); - await output.fetchParsedAddress(context); - }, - onRemove: () { - showPopUp( - context: context, - builder: (dialogContext) { - return AlertWithTwoActions( - alertTitle: S.of(context).template, - alertContent: S - .of(context) - .confirm_delete_template, - rightButtonText: S.of(context).delete, - leftButtonText: S.of(context).cancel, - actionRightButton: () { - Navigator.of(dialogContext).pop(); - sendViewModel.sendTemplateViewModel - .removeTemplate( - template: template); - }, - actionLeftButton: () => - Navigator.of(dialogContext) - .pop()); - }, - ); - }, + final output = sendViewModel.outputs[index]; + + return SendCard( + key: output.key, + output: output, + sendViewModel: sendViewModel, + initialPaymentRequest: initialPaymentRequest, ); - }, - ), - ], - ); - }, + }); + }, + )), + Padding( + padding: + EdgeInsets.only(top: 10, left: 24, right: 24, bottom: 10), + child: Container( + height: 10, + child: Observer( + builder: (_) { + final count = sendViewModel.outputs.length; + + return count > 1 + ? SmoothPageIndicator( + controller: controller, + count: count, + effect: ScrollingDotsEffect( + spacing: 6.0, + radius: 6.0, + dotWidth: 6.0, + dotHeight: 6.0, + dotColor: Theme.of(context) + .primaryTextTheme.headline3! + .backgroundColor!, + activeDotColor: Theme.of(context) + .primaryTextTheme.headline2! + .backgroundColor!), + ) + : Offstage(); + }, + ), ), ), - ) - ], + if (sendViewModel.hasMultiRecipient) + Container( + height: 40, + width: double.infinity, + padding: EdgeInsets.only(left: 24), + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Observer( + builder: (_) { + final templates = sendViewModel.templates; + final itemCount = templates.length; + + return Row( + children: [ + AddTemplateButton( + onTap: () => Navigator.of(context).pushNamed(Routes.sendTemplate), + currentTemplatesLength: templates.length, + ), + ListView.builder( + scrollDirection: Axis.horizontal, + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount: itemCount, + itemBuilder: (context, index) { + final template = templates[index]; + return TemplateTile( + key: UniqueKey(), + to: template.name, + amount: template.isCurrencySelected ? template.amount : template.amountFiat, + from: template.isCurrencySelected ? template.cryptoCurrency : template.fiatCurrency, + onTap: () async { + final fiatFromTemplate = FiatCurrency.all.singleWhere((element) => element.title == template.fiatCurrency); + final output = _defineCurrentOutput(); + output.address = template.address; + if(template.isCurrencySelected){ + output.setCryptoAmount(template.amount); + }else{ + sendViewModel.setFiatCurrency(fiatFromTemplate); + output.setFiatAmount(template.amountFiat); + } + output.resetParsedAddress(); + await output.fetchParsedAddress(context); + }, + onRemove: () { + showPopUp( + context: context, + builder: (dialogContext) { + return AlertWithTwoActions( + alertTitle: S.of(context).template, + alertContent: S + .of(context) + .confirm_delete_template, + rightButtonText: S.of(context).delete, + leftButtonText: S.of(context).cancel, + actionRightButton: () { + Navigator.of(dialogContext).pop(); + sendViewModel.sendTemplateViewModel + .removeTemplate( + template: template); + }, + actionLeftButton: () => + Navigator.of(dialogContext) + .pop()); + }, + ); + }, + ); + }, + ), + ], + ); + }, + ), + ), + ) + ], + ), ), bottomSectionPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24), @@ -290,8 +262,7 @@ class SendPage extends BasePage { text: 'Change your asset (${sendViewModel.selectedCryptoCurrency})', color: Colors.transparent, textColor: Theme.of(context) - .accentTextTheme! - .headline3! + .accentTextTheme.headline3! .decorationColor!, ) ) @@ -309,13 +280,11 @@ class SendPage extends BasePage { text: S.of(context).add_receiver, color: Colors.transparent, textColor: Theme.of(context) - .accentTextTheme! - .headline3! + .accentTextTheme.headline3! .decorationColor!, isDottedBorder: true, borderColor: Theme.of(context) - .primaryTextTheme! - .headline3! + .primaryTextTheme.headline3! .decorationColor!, )), Observer( @@ -335,7 +304,7 @@ class SendPage extends BasePage { item.address.isEmpty || item.cryptoAmount.isEmpty) .toList(); - if (notValidItems?.isNotEmpty ?? false) { + if (notValidItems.isNotEmpty ?? false) { showErrorValidationAlert(context); return; } @@ -344,7 +313,7 @@ class SendPage extends BasePage { }, text: S.of(context).send, - color: Theme.of(context).accentTextTheme!.bodyText1!.color!, + color: Theme.of(context).accentTextTheme.bodyText1!.color!, textColor: Colors.white, isLoading: sendViewModel.state is IsExecutingState || sendViewModel.state is TransactionCommitting, diff --git a/lib/src/screens/send/widgets/send_card.dart b/lib/src/screens/send/widgets/send_card.dart index 082067d95..cdae9a8df 100644 --- a/lib/src/screens/send/widgets/send_card.dart +++ b/lib/src/screens/send/widgets/send_card.dart @@ -1,6 +1,7 @@ import 'package:cake_wallet/entities/priority_for_wallet_type.dart'; import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; import 'package:cake_wallet/utils/payment_request.dart'; +import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:cw_core/transaction_priority.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/src/widgets/keyboard_done_button.dart'; @@ -119,7 +120,7 @@ class SendCardState extends State color: Colors.transparent, )), Container( - decoration: BoxDecoration( + decoration: ResponsiveLayoutUtil.instance.isMobile(context) ? BoxDecoration( borderRadius: BorderRadius.only( bottomLeft: Radius.circular(24), bottomRight: Radius.circular(24)), @@ -130,9 +131,14 @@ class SendCardState extends State .subtitle1! .decorationColor!, ], begin: Alignment.topLeft, end: Alignment.bottomRight), - ), + ) : null, child: Padding( - padding: EdgeInsets.fromLTRB(24, 100, 24, 32), + padding: EdgeInsets.fromLTRB( + 24, + ResponsiveLayoutUtil.instance.isMobile(context) ? 100 : 55, + 24, + ResponsiveLayoutUtil.instance.isMobile(context) ? 32 : 0, + ), child: SingleChildScrollView( child: Observer(builder: (_) => Column( mainAxisSize: MainAxisSize.min, diff --git a/lib/src/screens/settings/desktop_settings/desktop_settings_page.dart b/lib/src/screens/settings/desktop_settings/desktop_settings_page.dart new file mode 100644 index 000000000..eee54eb45 --- /dev/null +++ b/lib/src/screens/settings/desktop_settings/desktop_settings_page.dart @@ -0,0 +1,105 @@ +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/routes.dart'; +import 'package:cake_wallet/src/widgets/setting_action_button.dart'; +import 'package:cake_wallet/src/widgets/setting_actions.dart'; +import 'package:cake_wallet/typography.dart'; +import 'package:flutter/material.dart'; +import 'package:cake_wallet/router.dart' as Router; + +final _settingsNavigatorKey = GlobalKey(); + +class DesktopSettingsPage extends StatefulWidget { + const DesktopSettingsPage({super.key}); + + @override + State createState() => _DesktopSettingsPageState(); +} + +class _DesktopSettingsPageState extends State { + final int itemCount = SettingActions.desktopSettings.length; + + int? currentPage; + + void _onItemChange(int index) { + setState(() { + currentPage = index; + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Container( + height: MediaQuery.of(context).size.height, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.all(24), + child: Text( + S.current.settings, + style: textXLarge(), + ), + ), + Expanded( + child: Row( + children: [ + Expanded( + flex: 1, + child: ListView.separated( + padding: EdgeInsets.only(top: 0), + itemBuilder: (_, index) { + final item = SettingActions.desktopSettings[index]; + final isLastTile = index == itemCount - 1; + return SettingActionButton( + isLastTile: isLastTile, + selectionActive: currentPage != null, + isSelected: currentPage == index, + isArrowVisible: true, + onTap: () { + if (currentPage != index) { + final settingContext = + _settingsNavigatorKey.currentState?.context ?? context; + item.onTap.call(settingContext); + _onItemChange(index); + } + }, + image: item.image, + title: item.name, + ); + }, + separatorBuilder: (_, index) => Container( + height: 1, + color: Theme.of(context).primaryTextTheme.caption!.decorationColor!, + ), + itemCount: itemCount, + ), + ), + Flexible( + flex: 2, + child: ConstrainedBox( + constraints: BoxConstraints(maxWidth: 500), + child: Navigator( + key: _settingsNavigatorKey, + initialRoute: Routes.empty_no_route, + onGenerateRoute: (settings) => Router.createRoute(settings), + onGenerateInitialRoutes: + (NavigatorState navigator, String initialRouteName) { + return [ + navigator + .widget.onGenerateRoute!(RouteSettings(name: initialRouteName))! + ]; + }, + ), + ), + ) + ], + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/src/screens/settings/display_settings_page.dart b/lib/src/screens/settings/display_settings_page.dart index 39123a7eb..4f932b189 100644 --- a/lib/src/screens/settings/display_settings_page.dart +++ b/lib/src/screens/settings/display_settings_page.dart @@ -7,9 +7,9 @@ import 'package:cake_wallet/src/screens/settings/widgets/settings_picker_cell.da import 'package:cake_wallet/src/screens/settings/widgets/settings_switcher_cell.dart'; import 'package:cake_wallet/themes/theme_base.dart'; import 'package:cake_wallet/themes/theme_list.dart'; +import 'package:cake_wallet/utils/device_info.dart'; import 'package:cake_wallet/view_model/settings/choices_list_item.dart'; import 'package:cake_wallet/view_model/settings/display_settings_view_model.dart'; -import 'package:cake_wallet/wallet_type_utils.dart'; import 'package:flutter/material.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; @@ -65,14 +65,15 @@ class DisplaySettingsPage extends BasePage { return LanguageService.list[code]?.toLowerCase().contains(searchText) ?? false; }, ), - SettingsChoicesCell( - ChoicesListItem( - title: S.current.color_theme, - items: ThemeList.all, - selectedItem: _displaySettingsViewModel.theme, - onItemSelected: (ThemeBase theme) => _displaySettingsViewModel.setTheme(theme), + if (DeviceInfo.instance.isMobile) + SettingsChoicesCell( + ChoicesListItem( + title: S.current.color_theme, + items: ThemeList.all, + selectedItem: _displaySettingsViewModel.theme, + onItemSelected: (ThemeBase theme) => _displaySettingsViewModel.setTheme(theme), + ), ), - ), ], ), ); diff --git a/lib/src/screens/support/support_page.dart b/lib/src/screens/support/support_page.dart index 5edb723bc..801f81775 100644 --- a/lib/src/screens/support/support_page.dart +++ b/lib/src/screens/support/support_page.dart @@ -1,6 +1,7 @@ import 'package:cake_wallet/src/screens/settings/widgets/settings_cell_with_arrow.dart'; import 'package:cake_wallet/src/screens/settings/widgets/settings_link_provider_cell.dart'; import 'package:cake_wallet/src/widgets/standard_list.dart'; +import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:cake_wallet/view_model/settings/link_list_item.dart'; import 'package:cake_wallet/view_model/settings/regular_list_item.dart'; import 'package:cake_wallet/view_model/support_view_model.dart'; @@ -18,32 +19,34 @@ class SupportPage extends BasePage { @override Widget body(BuildContext context) { - final iconColor = - Theme.of(context).accentTextTheme!.headline1!.backgroundColor!; + final iconColor = Theme.of(context).accentTextTheme!.headline1!.backgroundColor!; // FIX-ME: Added `context` it was not used here before, maby bug ? - return SectionStandardList( - context: context, - sectionCount: 1, - itemCounter: (int _) => supportViewModel.items.length, - itemBuilder: (_, __, index) { - final item = supportViewModel.items[index]; + return Center( + child: ConstrainedBox( + constraints: BoxConstraints(maxWidth: 500), + child: SectionStandardList( + context: context, + sectionCount: 1, + itemCounter: (int _) => supportViewModel.items.length, + itemBuilder: (_, __, index) { + final item = supportViewModel.items[index]; - if (item is RegularListItem) { - return SettingsCellWithArrow( - title: item.title, handler: item.handler); - } + if (item is RegularListItem) { + return SettingsCellWithArrow(title: item.title, handler: item.handler); + } - if (item is LinkListItem) { - return SettingsLinkProviderCell( - title: item.title, - icon: item.icon, - iconColor: item.hasIconColor ? iconColor : null, - link: item.link, - linkTitle: item.linkTitle); - } + if (item is LinkListItem) { + return SettingsLinkProviderCell( + title: item.title, + icon: item.icon, + iconColor: item.hasIconColor ? iconColor : null, + link: item.link, + linkTitle: item.linkTitle); + } - return Container(); - }); + return Container(); + }), + ), + ); } - -} \ No newline at end of file +} diff --git a/lib/src/screens/wallet_list/wallet_list_page.dart b/lib/src/screens/wallet_list/wallet_list_page.dart index d76631b3f..a1e8c51b7 100644 --- a/lib/src/screens/wallet_list/wallet_list_page.dart +++ b/lib/src/screens/wallet_list/wallet_list_page.dart @@ -1,5 +1,6 @@ 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/device_info.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'; @@ -23,8 +24,7 @@ class WalletListPage extends BasePage { final WalletListViewModel walletListViewModel; @override - Widget body(BuildContext context) => - WalletListBody(walletListViewModel: walletListViewModel); + Widget body(BuildContext context) => WalletListBody(walletListViewModel: walletListViewModel); } class WalletListBody extends StatefulWidget { @@ -37,28 +37,21 @@ class WalletListBody extends StatefulWidget { } class WalletListBodyState 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 nonWalletTypeIcon = - Image.asset('assets/images/close.png', height: 24, width: 24); - final havenIcon = - Image.asset('assets/images/haven_logo.png', height: 24, width: 24); + 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 nonWalletTypeIcon = Image.asset('assets/images/close.png', height: 24, width: 24); + final havenIcon = Image.asset('assets/images/haven_logo.png', height: 24, width: 24); final scrollController = ScrollController(); final double tileHeight = 60; Flushbar? _progressBar; @override Widget build(BuildContext context) { - final newWalletImage = Image.asset('assets/images/new_wallet.png', - height: 12, width: 12, color: Colors.white); + final newWalletImage = + Image.asset('assets/images/new_wallet.png', height: 12, width: 12, color: Colors.white); final restoreWalletImage = Image.asset('assets/images/restore_wallet.png', - height: 12, - width: 12, - color: Theme.of(context).primaryTextTheme!.headline6!.color!); + height: 12, width: 12, color: Theme.of(context).primaryTextTheme.headline6!.color!); return Container( padding: EdgeInsets.only(top: 16), @@ -69,16 +62,13 @@ class WalletListBodyState extends State { builder: (_) => ListView.separated( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), - separatorBuilder: (_, index) => Divider( - color: Theme.of(context).backgroundColor, height: 32), + separatorBuilder: (_, index) => + Divider(color: Theme.of(context).backgroundColor, height: 32), itemCount: widget.walletListViewModel.wallets.length, itemBuilder: (__, index) { final wallet = widget.walletListViewModel.wallets[index]; final currentColor = wallet.isCurrent - ? Theme.of(context) - .accentTextTheme! - .subtitle2! - .decorationColor! + ? Theme.of(context).accentTextTheme.subtitle2!.decorationColor! : Theme.of(context).backgroundColor; final row = GestureDetector( onTap: () async { @@ -90,19 +80,15 @@ class WalletListBodyState extends State { context: context, builder: (dialogContext) { return AlertWithTwoActions( - alertTitle: S - .of(context) - .change_wallet_alert_title, - alertContent: S - .of(context) - .change_wallet_alert_content( - wallet.name), + alertTitle: S.of(context).change_wallet_alert_title, + alertContent: + S.of(context).change_wallet_alert_content(wallet.name), leftButtonText: S.of(context).cancel, rightButtonText: S.of(context).change, actionLeftButton: () => - Navigator.of(context).pop(false), + Navigator.of(dialogContext).pop(false), actionRightButton: () => - Navigator.of(context).pop(true)); + Navigator.of(dialogContext).pop(true)); }) ?? false; @@ -131,12 +117,11 @@ class WalletListBodyState extends State { color: Theme.of(context).backgroundColor, alignment: Alignment.centerLeft, child: Row( - crossAxisAlignment: - CrossAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, children: [ wallet.isEnabled - ? _imageFor(type: wallet.type) - : nonWalletTypeIcon, + ? _imageFor(type: wallet.type) + : nonWalletTypeIcon, SizedBox(width: 10), Text( wallet.name, @@ -144,8 +129,7 @@ class WalletListBodyState extends State { fontSize: 22, fontWeight: FontWeight.w500, color: Theme.of(context) - .primaryTextTheme! - .headline6! + .primaryTextTheme.headline6! .color!), ) ], @@ -163,43 +147,40 @@ class WalletListBodyState extends State { startActionPane: _actionPane(wallet), endActionPane: _actionPane(wallet), child: row, - ); + ); }), ), ), - bottomSectionPadding: - EdgeInsets.only(bottom: 24, right: 24, left: 24), + bottomSectionPadding: EdgeInsets.only(bottom: 24, right: 24, left: 24), bottomSection: Column(children: [ PrimaryImageButton( onPressed: () { - if (isSingleCoin) { - Navigator.of(context).pushNamed(Routes.newWallet, arguments: widget.walletListViewModel.currentWalletType); - } else { - Navigator.of(context).pushNamed(Routes.newWalletType); - } - }, + if (isSingleCoin) { + Navigator.of(context).pushNamed(Routes.newWallet, + arguments: widget.walletListViewModel.currentWalletType); + } else { + Navigator.of(context).pushNamed(Routes.newWalletType); + } + }, image: newWalletImage, text: S.of(context).wallet_list_create_new_wallet, - color: Theme.of(context).accentTextTheme!.bodyText1!.color!, + color: Theme.of(context).accentTextTheme.bodyText1!.color!, textColor: Colors.white, ), SizedBox(height: 10.0), PrimaryImageButton( onPressed: () { - if (isSingleCoin) { - Navigator - .of(context) - .pushNamed( - Routes.restoreWallet, - arguments: widget.walletListViewModel.currentWalletType); - } else { - Navigator.of(context).pushNamed(Routes.restoreWalletType); - } - }, + if (isSingleCoin) { + Navigator.of(context).pushNamed(Routes.restoreWallet, + arguments: widget.walletListViewModel.currentWalletType); + } else { + Navigator.of(context).pushNamed(Routes.restoreWalletType); + } + }, image: restoreWalletImage, text: S.of(context).wallet_list_restore_wallet, - color: Theme.of(context).accentTextTheme!.caption!.color!, - textColor: Theme.of(context).primaryTextTheme!.headline6!.color!) + color: Theme.of(context).accentTextTheme.caption!.color!, + textColor: Theme.of(context).primaryTextTheme.headline6!.color!) ])), ); } @@ -232,9 +213,13 @@ class WalletListBodyState extends State { await widget.walletListViewModel.loadWallet(wallet); auth.hideProgressText(); auth.close(); - WidgetsBinding.instance.addPostFrameCallback((_) { - Navigator.of(context).pop(); - }); + // only pop the wallets route in mobile as it will go back to dashboard page + // in desktop platforms the navigation tree is different + if (DeviceInfo.instance.isMobile) { + WidgetsBinding.instance.addPostFrameCallback((_) { + Navigator.of(context).pop(); + }); + } } catch (e) { auth.changeProcessText( S.of(context).wallet_list_failed_to_load(wallet.name, e.toString())); @@ -245,7 +230,11 @@ class WalletListBodyState extends State { changeProcessText(S.of(context).wallet_list_loading_wallet(wallet.name)); await widget.walletListViewModel.loadWallet(wallet); hideProgressText(); - Navigator.of(context).pop(); + // only pop the wallets route in mobile as it will go back to dashboard page + // in desktop platforms the navigation tree is different + if (DeviceInfo.instance.isMobile) { + Navigator.of(context).pop(); + } } catch (e) { changeProcessText(S.of(context).wallet_list_failed_to_load(wallet.name, e.toString())); } @@ -290,6 +279,7 @@ class WalletListBodyState extends State { ? auth.changeProcessText(S.of(context).wallet_list_removing_wallet(wallet.name)) : changeProcessText(S.of(context).wallet_list_removing_wallet(wallet.name)); await widget.walletListViewModel.remove(wallet); + hideProgressText(); } catch (e) { auth != null ? auth.changeProcessText( @@ -309,8 +299,10 @@ class WalletListBodyState extends State { } void hideProgressText() { - _progressBar?.dismiss(); - _progressBar = null; + Future.delayed(Duration(milliseconds: 50), () { + _progressBar?.dismiss(); + _progressBar = null; + }); } ActionPane _actionPane(WalletListItem wallet) => ActionPane( diff --git a/lib/src/screens/welcome/welcome_page.dart b/lib/src/screens/welcome/welcome_page.dart index a50d8ba8c..86e1cbcf1 100644 --- a/lib/src/screens/welcome/welcome_page.dart +++ b/lib/src/screens/welcome/welcome_page.dart @@ -1,4 +1,5 @@ import 'package:cake_wallet/themes/theme_base.dart'; +import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:flutter/material.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/src/widgets/primary_button.dart'; @@ -19,7 +20,7 @@ class WelcomePage extends BasePage { if (isHaven) { return S.of(context).haven_app; } - + return S.of(context).cake_wallet; } @@ -31,172 +32,137 @@ class WelcomePage extends BasePage { if (isHaven) { return S.of(context).haven_app_wallet_text; } - + return S.of(context).first_wallet_text; } @override Widget build(BuildContext context) { return Scaffold( - backgroundColor: Theme - .of(context) - .backgroundColor, + backgroundColor: Theme.of(context).backgroundColor, resizeToAvoidBottomInset: false, body: body(context)); } @override Widget body(BuildContext context) { - final welcomeImage = currentTheme.type == ThemeType.dark - ? welcomeImageDark : welcomeImageLight; + final welcomeImage = currentTheme.type == ThemeType.dark ? welcomeImageDark : welcomeImageLight; final newWalletImage = Image.asset('assets/images/new_wallet.png', height: 12, width: 12, - color: Theme - .of(context) - .accentTextTheme! - .headline5! - .decorationColor!); + color: Theme.of(context).accentTextTheme!.headline5!.decorationColor!); final restoreWalletImage = Image.asset('assets/images/restore_wallet.png', - height: 12, - width: 12, - color: Theme.of(context) - .primaryTextTheme! - .headline6! - .color!); + height: 12, width: 12, color: Theme.of(context).primaryTextTheme!.headline6!.color!); - return WillPopScope(onWillPop: () async => false, child: Container( - padding: EdgeInsets.only(top: 64, bottom: 24, left: 24, right: 24), - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Flexible( - flex: 2, - child: AspectRatio( - aspectRatio: aspectRatioImage, - child: FittedBox(child: welcomeImage, fit: BoxFit.fill) - ) - ), - Flexible( - flex: 3, + return WillPopScope( + onWillPop: () async => false, + child: Container( + padding: EdgeInsets.only(top: 64, bottom: 24, left: 24, right: 24), + child: Center( + child: ConstrainedBox( + constraints: + BoxConstraints(maxWidth: ResponsiveLayoutUtil.kDesktopMaxWidthConstraint), child: Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Column( - children: [ - Padding( - padding: EdgeInsets.only(top: 24), - child: Text( - S - .of(context) - .welcome, - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.w500, - color: Theme - .of(context) - .accentTextTheme! - .headline2! - .color!, + Flexible( + flex: 2, + child: AspectRatio( + aspectRatio: aspectRatioImage, + child: FittedBox(child: welcomeImage, fit: BoxFit.fill))), + Flexible( + flex: 3, + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + children: [ + Padding( + padding: EdgeInsets.only(top: 24), + child: Text( + S.of(context).welcome, + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w500, + color: Theme.of(context).accentTextTheme!.headline2!.color!, + ), + textAlign: TextAlign.center, + ), + ), + Padding( + padding: EdgeInsets.only(top: 5), + child: Text( + appTitle(context), + style: TextStyle( + fontSize: 36, + fontWeight: FontWeight.bold, + color: Theme.of(context).primaryTextTheme!.headline6!.color!, + ), + textAlign: TextAlign.center, + ), + ), + Padding( + padding: EdgeInsets.only(top: 5), + child: Text( + appDescription(context), + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Theme.of(context).accentTextTheme!.headline2!.color!, + ), + textAlign: TextAlign.center, + ), + ), + ], ), - textAlign: TextAlign.center, - ), - ), - Padding( - padding: EdgeInsets.only(top: 5), - child: Text( - appTitle(context), - style: TextStyle( - fontSize: 36, - fontWeight: FontWeight.bold, - color: Theme.of(context) - .primaryTextTheme! - .headline6! - .color!, - ), - textAlign: TextAlign.center, - ), - ), - Padding( - padding: EdgeInsets.only(top: 5), - child: Text( - appDescription(context), - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - color: Theme - .of(context) - .accentTextTheme! - .headline2! - .color!, - ), - textAlign: TextAlign.center, - ), - ), - ], - ), - Column( - children: [ - Text( - S - .of(context) - .please_make_selection, - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.normal, - color: Theme.of(context) - .accentTextTheme! - .headline2! - .color!, - ), - textAlign: TextAlign.center, - ), - Padding( - padding: EdgeInsets.only(top: 24), - child: PrimaryImageButton( - onPressed: () => - Navigator.pushNamed(context, - Routes.newWalletFromWelcome), - image: newWalletImage, - text: S.of(context).create_new, - color: Theme.of(context) - .accentTextTheme! - .subtitle2! - .decorationColor!, - textColor: Theme - .of(context) - .accentTextTheme! - .headline5! - .decorationColor!, - ), - ), - Padding( - padding: EdgeInsets.only(top: 10), - child: PrimaryImageButton( - onPressed: () { - Navigator.pushNamed(context, Routes.restoreOptions); - }, - image: restoreWalletImage, - text: S - .of(context) - .restore_wallet, - color: Theme.of(context) - .accentTextTheme! - .caption! - .color!, - textColor: Theme.of(context) - .primaryTextTheme! - .headline6! - .color!), - ) - ], - ) + Column( + children: [ + Text( + S.of(context).please_make_selection, + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.normal, + color: Theme.of(context).accentTextTheme!.headline2!.color!, + ), + textAlign: TextAlign.center, + ), + Padding( + padding: EdgeInsets.only(top: 24), + child: PrimaryImageButton( + onPressed: () => + Navigator.pushNamed(context, Routes.newWalletFromWelcome), + image: newWalletImage, + text: S.of(context).create_new, + color: Theme.of(context) + .accentTextTheme! + .subtitle2! + .decorationColor!, + textColor: Theme.of(context) + .accentTextTheme! + .headline5! + .decorationColor!, + ), + ), + Padding( + padding: EdgeInsets.only(top: 10), + child: PrimaryImageButton( + onPressed: () { + Navigator.pushNamed(context, Routes.restoreOptions); + }, + image: restoreWalletImage, + text: S.of(context).restore_wallet, + color: Theme.of(context).accentTextTheme!.caption!.color!, + textColor: + Theme.of(context).primaryTextTheme!.headline6!.color!), + ) + ], + ) + ], + )) ], - ) - ) - ], - ) - )); + ), + ), + ))); } } diff --git a/lib/src/widgets/add_template_button.dart b/lib/src/widgets/add_template_button.dart new file mode 100644 index 000000000..249c493a6 --- /dev/null +++ b/lib/src/widgets/add_template_button.dart @@ -0,0 +1,52 @@ +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/utils/responsive_layout_util.dart'; +import 'package:dotted_border/dotted_border.dart'; +import 'package:flutter/material.dart'; + +class AddTemplateButton extends StatelessWidget { + final Function() onTap; + final int currentTemplatesLength; + + const AddTemplateButton({Key? key, required this.onTap, required this.currentTemplatesLength}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, + child: Container( + padding: EdgeInsets.only(left: 1, right: 10), + child: DottedBorder( + borderType: BorderType.RRect, + dashPattern: [6, 4], + color: Theme.of(context).primaryTextTheme.headline3!.decorationColor!, + strokeWidth: 2, + radius: Radius.circular(20), + child: Container( + height: 34, + padding: EdgeInsets.symmetric( + horizontal: ResponsiveLayoutUtil.instance.isMobile(context) ? 10 : 30), + alignment: Alignment.center, + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(20)), + color: Colors.transparent, + ), + child: currentTemplatesLength >= 1 + ? Icon( + Icons.add, + color: Theme.of(context).primaryTextTheme.headline2!.color!, + ) + : Text( + S.of(context).new_template, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: Theme.of(context).primaryTextTheme.headline2!.color!, + ), + ), + ), + ), + ), + ); + } +} diff --git a/lib/src/widgets/address_text_field.dart b/lib/src/widgets/address_text_field.dart index 4acc9a145..059dc51aa 100644 --- a/lib/src/widgets/address_text_field.dart +++ b/lib/src/widgets/address_text_field.dart @@ -1,3 +1,6 @@ +import 'dart:io'; + +import 'package:cake_wallet/utils/device_info.dart'; import 'package:flutter/services.dart'; import 'package:flutter/material.dart'; import 'package:cake_wallet/routes.dart'; @@ -65,7 +68,7 @@ class AddressTextField extends StatelessWidget { style: textStyle ?? TextStyle( fontSize: 16, - color: Theme.of(context).primaryTextTheme!.headline6!.color!), + color: Theme.of(context).primaryTextTheme.headline6!.color!), decoration: InputDecoration( suffixIcon: SizedBox( width: prefixIconWidth * options.length + @@ -102,7 +105,8 @@ class AddressTextField extends StatelessWidget { width: prefixIconWidth * options.length + (spaceBetweenPrefixIcons * options.length), child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisAlignment: DeviceInfo.instance.isMobile + ? MainAxisAlignment.spaceBetween : MainAxisAlignment.end, children: [ SizedBox(width: 5), if (this.options.contains(AddressTextFieldOption.paste)) ...[ @@ -117,8 +121,7 @@ class AddressTextField extends StatelessWidget { decoration: BoxDecoration( color: buttonColor ?? Theme.of(context) - .accentTextTheme! - .headline6! + .accentTextTheme.headline6! .color!, borderRadius: BorderRadius.all(Radius.circular(6))), @@ -126,13 +129,13 @@ class AddressTextField extends StatelessWidget { 'assets/images/paste_ios.png', color: iconColor ?? Theme.of(context) - .primaryTextTheme! - .headline4! + .primaryTextTheme.headline4! .decorationColor!, )), )), ], - if (this.options.contains(AddressTextFieldOption.qrCode)) ...[ + if (this.options.contains(AddressTextFieldOption.qrCode) && DeviceInfo.instance.isMobile) + ...[ Container( width: prefixIconWidth, height: prefixIconHeight, @@ -144,8 +147,7 @@ class AddressTextField extends StatelessWidget { decoration: BoxDecoration( color: buttonColor ?? Theme.of(context) - .accentTextTheme! - .headline6! + .accentTextTheme.headline6! .color!, borderRadius: BorderRadius.all(Radius.circular(6))), @@ -153,12 +155,11 @@ class AddressTextField extends StatelessWidget { 'assets/images/qr_code_icon.png', color: iconColor ?? Theme.of(context) - .primaryTextTheme! - .headline4! + .primaryTextTheme.headline4! .decorationColor!, )), )) - ], + ] else SizedBox(width: 5), if (this .options .contains(AddressTextFieldOption.addressBook)) ...[ @@ -173,8 +174,7 @@ class AddressTextField extends StatelessWidget { decoration: BoxDecoration( color: buttonColor ?? Theme.of(context) - .accentTextTheme! - .headline6! + .accentTextTheme.headline6! .color!, borderRadius: BorderRadius.all(Radius.circular(6))), @@ -182,8 +182,7 @@ class AddressTextField extends StatelessWidget { 'assets/images/open_book.png', color: iconColor ?? Theme.of(context) - .primaryTextTheme! - .headline4! + .primaryTextTheme.headline4! .decorationColor!, )), )) @@ -211,7 +210,7 @@ class AddressTextField extends StatelessWidget { } Future _presetAddressBookPicker(BuildContext context) async { - final contact = await Navigator.of(context, rootNavigator: true) + final contact = await Navigator.of(context) .pushNamed(Routes.pickerAddressBook,arguments: selectedCurrency); if (contact is ContactBase && contact.address != null) { diff --git a/lib/src/widgets/alert_background.dart b/lib/src/widgets/alert_background.dart index 0b4dab470..1b72597af 100644 --- a/lib/src/widgets/alert_background.dart +++ b/lib/src/widgets/alert_background.dart @@ -1,5 +1,5 @@ import 'dart:ui'; -import 'package:flutter/cupertino.dart'; +import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:flutter/material.dart'; import 'package:cake_wallet/palette.dart'; @@ -21,10 +21,15 @@ class AlertBackground extends StatelessWidget { filter: ImageFilter.blur(sigmaX: 3.0, sigmaY: 3.0), child: Container( decoration: BoxDecoration(color: PaletteDark.darkNightBlue.withOpacity(0.75)), - child: child, + child: Center( + child: Container( + width: ResponsiveLayoutUtil.kDesktopMaxWidthConstraint, + child: child, + ), + ), ), ), ), ); } -} \ No newline at end of file +} diff --git a/lib/src/widgets/alert_close_button.dart b/lib/src/widgets/alert_close_button.dart index 35a5cd45c..e8e20f125 100644 --- a/lib/src/widgets/alert_close_button.dart +++ b/lib/src/widgets/alert_close_button.dart @@ -13,9 +13,7 @@ class AlertCloseButton extends StatelessWidget { @override Widget build(BuildContext context) { - return Positioned( - bottom: 60, - child: GestureDetector( + return GestureDetector( onTap: () => Navigator.of(context).pop(), child: Container( height: 42, @@ -28,7 +26,6 @@ class AlertCloseButton extends StatelessWidget { child: image ?? closeButton, ), ), - ) ); } } \ No newline at end of file diff --git a/lib/src/widgets/base_text_form_field.dart b/lib/src/widgets/base_text_form_field.dart index 19d43e9c2..ff2102843 100644 --- a/lib/src/widgets/base_text_form_field.dart +++ b/lib/src/widgets/base_text_form_field.dart @@ -27,6 +27,7 @@ class BaseTextFormField extends StatelessWidget { this.maxLength, this.focusNode, this.initialValue, + this.onSubmit, this.borderWidth = 1.0}); final TextEditingController? controller; @@ -54,6 +55,7 @@ class BaseTextFormField extends StatelessWidget { final bool? enableInteractiveSelection; final String? initialValue; final double borderWidth; + final void Function(String)? onSubmit; @override Widget build(BuildContext context) { @@ -71,11 +73,12 @@ class BaseTextFormField extends StatelessWidget { inputFormatters: inputFormatters, enabled: enabled, maxLength: maxLength, + onFieldSubmitted: onSubmit, style: textStyle ?? TextStyle( fontSize: 16.0, color: - textColor ?? Theme.of(context).primaryTextTheme!.headline6!.color!), + textColor ?? Theme.of(context).primaryTextTheme.headline6!.color!), decoration: InputDecoration( prefix: prefix, prefixIcon: prefixIcon, @@ -89,17 +92,17 @@ class BaseTextFormField extends StatelessWidget { focusedBorder: UnderlineInputBorder( borderSide: BorderSide( color: borderColor ?? - Theme.of(context).primaryTextTheme!.headline6!.backgroundColor!, + Theme.of(context).primaryTextTheme.headline6!.backgroundColor!, width: borderWidth)), disabledBorder: UnderlineInputBorder( borderSide: BorderSide( color: borderColor ?? - Theme.of(context).primaryTextTheme!.headline6!.backgroundColor!, + Theme.of(context).primaryTextTheme.headline6!.backgroundColor!, width: borderWidth)), enabledBorder: UnderlineInputBorder( borderSide: BorderSide( color: borderColor ?? - Theme.of(context).primaryTextTheme!.headline6!.backgroundColor!, + Theme.of(context).primaryTextTheme.headline6!.backgroundColor!, width: borderWidth))), validator: validator, ); diff --git a/lib/src/widgets/check_box_picker.dart b/lib/src/widgets/check_box_picker.dart index 80461e26d..a59dda905 100644 --- a/lib/src/widgets/check_box_picker.dart +++ b/lib/src/widgets/check_box_picker.dart @@ -1,4 +1,5 @@ import 'package:cake_wallet/palette.dart'; +import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:flutter/material.dart'; import 'package:cake_wallet/src/widgets/alert_background.dart'; import 'package:cake_wallet/src/widgets/alert_close_button.dart'; @@ -32,63 +33,62 @@ class CheckBoxPickerState extends State { @override Widget build(BuildContext context) { return AlertBackground( - child: Stack( - alignment: Alignment.center, - children: [ - Column( - mainAxisSize: MainAxisSize.min, - children: [ - if (widget.title.isNotEmpty) - Container( - padding: EdgeInsets.symmetric(horizontal: 24), - child: Text( - widget.title, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 18, - fontFamily: 'Lato', - fontWeight: FontWeight.bold, - decoration: TextDecoration.none, - color: Colors.white, + child: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + if (widget.title.isNotEmpty) + Container( + padding: EdgeInsets.symmetric(horizontal: 24), + child: Text( + widget.title, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 18, + fontFamily: 'Lato', + fontWeight: FontWeight.bold, + decoration: TextDecoration.none, + color: Colors.white, + ), ), ), - ), - Padding( - padding: EdgeInsets.only(left: 24, right: 24, top: 24), - child: ClipRRect( - borderRadius: BorderRadius.all(Radius.circular(30)), - child: Container( - color: Theme.of(context).accentTextTheme.headline6!.color!, - child: ConstrainedBox( - constraints: BoxConstraints( - maxHeight: MediaQuery.of(context).size.height * 0.65, - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Flexible( - child: Stack( - alignment: Alignment.center, - children: [ - (items.length) > 3 - ? Scrollbar( - controller: controller, - child: itemsList(), - ) - : itemsList(), - ], + Padding( + padding: EdgeInsets.only(left: 24, right: 24, top: 24), + child: ClipRRect( + borderRadius: BorderRadius.all(Radius.circular(30)), + child: Container( + color: Theme.of(context).accentTextTheme.headline6!.color!, + child: ConstrainedBox( + constraints: BoxConstraints( + maxHeight: MediaQuery.of(context).size.height * 0.65, + maxWidth: ResponsiveLayoutUtil.kPopupWidth, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Flexible( + child: Stack( + alignment: Alignment.center, + children: [ + items.length > 3 + ? Scrollbar( + controller: controller, + child: itemsList(), + ) + : itemsList(), + ], + ), ), - ), - ], + ], + ), ), ), ), ), - ) - ], - ), - AlertCloseButton(), - ], + SizedBox(height: ResponsiveLayoutUtil.kPopupSpaceHeight), + AlertCloseButton(), + ], + ), ), ); } diff --git a/lib/src/widgets/market_place_item.dart b/lib/src/widgets/market_place_item.dart index 8049a6346..438391c97 100644 --- a/lib/src/widgets/market_place_item.dart +++ b/lib/src/widgets/market_place_item.dart @@ -17,6 +17,9 @@ class MarketPlaceItem extends StatelessWidget { Widget build(BuildContext context) { return InkWell( onTap: onTap, + hoverColor: Colors.transparent, + splashColor: Colors.transparent, + highlightColor: Colors.transparent, child: Stack( children: [ Container( diff --git a/lib/src/widgets/nav_bar.dart b/lib/src/widgets/nav_bar.dart index f6d933c8b..aabe8d9c8 100644 --- a/lib/src/widgets/nav_bar.dart +++ b/lib/src/widgets/nav_bar.dart @@ -1,13 +1,7 @@ import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; class NavBar extends StatelessWidget implements ObstructingPreferredSizeWidget { - factory NavBar( - {Widget? leading, - Widget? middle, - Widget? trailing, - Color? backgroundColor}) { - + factory NavBar({Widget? leading, Widget? middle, Widget? trailing, Color? backgroundColor}) { return NavBar._internal( leading: leading, middle: middle, @@ -17,11 +11,7 @@ class NavBar extends StatelessWidget implements ObstructingPreferredSizeWidget { } factory NavBar.withShadow( - {Widget? leading, - Widget? middle, - Widget? trailing, - Color? backgroundColor}) { - + {Widget? leading, Widget? middle, Widget? trailing, Color? backgroundColor}) { return NavBar._internal( leading: leading, middle: middle, @@ -29,13 +19,15 @@ class NavBar extends StatelessWidget implements ObstructingPreferredSizeWidget { height: 80, backgroundColor: backgroundColor, decoration: BoxDecoration( - color: backgroundColor, - boxShadow: [ - BoxShadow( - color: Color.fromRGBO(132, 141, 198, 0.11), - blurRadius: 8, - offset: Offset(0, 2)) - ]), + color: backgroundColor, + boxShadow: [ + BoxShadow( + color: Color.fromRGBO(132, 141, 198, 0.11), + blurRadius: 8, + offset: Offset(0, 2), + ), + ], + ), ); } @@ -59,14 +51,17 @@ class NavBar extends StatelessWidget implements ObstructingPreferredSizeWidget { @override Widget build(BuildContext context) { + if (leading == null && middle == null && trailing == null) { + return const SizedBox(); + } + final pad = height - _originalHeight; final paddingTop = pad / 2; final _paddingBottom = (pad / 2); 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/lib/src/widgets/picker.dart b/lib/src/widgets/picker.dart index f26ff3ee2..ccf922d41 100644 --- a/lib/src/widgets/picker.dart +++ b/lib/src/widgets/picker.dart @@ -1,5 +1,6 @@ // ignore_for_file: deprecated_member_use +import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:flutter/material.dart'; import 'package:cake_wallet/src/widgets/alert_background.dart'; import 'package:cake_wallet/src/widgets/alert_close_button.dart'; @@ -70,108 +71,106 @@ class _PickerState extends State> { @override Widget build(BuildContext context) { return AlertBackground( - child: Stack( - alignment: Alignment.center, + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, children: [ - Column( - mainAxisSize: MainAxisSize.min, - children: [ - if (widget.title?.isNotEmpty ?? false) - Container( - padding: EdgeInsets.symmetric(horizontal: 24), - child: Text( - widget.title!, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 18, - fontFamily: 'Lato', - fontWeight: FontWeight.bold, - decoration: TextDecoration.none, - color: Colors.white, - ), - ), + if (widget.title?.isNotEmpty ?? false) + Container( + padding: EdgeInsets.symmetric(horizontal: 24), + child: Text( + widget.title!, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 18, + fontFamily: 'Lato', + fontWeight: FontWeight.bold, + decoration: TextDecoration.none, + color: Colors.white, ), - Padding( - padding: EdgeInsets.only(left: 24, right: 24, top: 24), - child: ClipRRect( - borderRadius: BorderRadius.all(Radius.circular(30)), - child: Container( - color: Theme.of(context).accentTextTheme.headline6!.color!, - child: ConstrainedBox( - constraints: BoxConstraints( - maxHeight: MediaQuery.of(context).size.height * 0.65, - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - if (widget.hintText != null) - Padding( - padding: const EdgeInsets.all(16), - child: TextFormField( - controller: searchController, - style: TextStyle(color: Theme.of(context).primaryTextTheme.headline6!.color!), - decoration: InputDecoration( - hintText: widget.hintText, - prefixIcon: Image.asset("assets/images/search_icon.png"), - filled: true, - fillColor: Theme.of(context).accentTextTheme.headline3!.color!, - alignLabelWithHint: false, - contentPadding: const EdgeInsets.symmetric(vertical: 4, horizontal: 16), - enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(14), - borderSide: const BorderSide( - color: Colors.transparent, - )), - focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(14), - borderSide: const BorderSide( - color: Colors.transparent, - )), - ), + ), + ), + Padding( + padding: EdgeInsets.only(left: 24, right: 24, top: 24), + child: ClipRRect( + borderRadius: BorderRadius.all(Radius.circular(30)), + child: Container( + color: Theme.of(context).accentTextTheme.headline6!.color!, + child: ConstrainedBox( + constraints: BoxConstraints( + maxHeight: MediaQuery.of(context).size.height * 0.65, + maxWidth: ResponsiveLayoutUtil.kPopupWidth, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + if (widget.hintText != null) + Padding( + padding: const EdgeInsets.all(16), + child: TextFormField( + controller: searchController, + style: TextStyle(color: Theme.of(context).primaryTextTheme.headline6!.color!), + decoration: InputDecoration( + hintText: widget.hintText, + prefixIcon: Image.asset("assets/images/search_icon.png"), + filled: true, + fillColor: Theme.of(context).accentTextTheme.headline3!.color!, + alignLabelWithHint: false, + contentPadding: const EdgeInsets.symmetric(vertical: 4, horizontal: 16), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(14), + borderSide: const BorderSide( + color: Colors.transparent, + )), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(14), + borderSide: const BorderSide( + color: Colors.transparent, + )), ), ), - Divider( - color: Theme.of(context).accentTextTheme.headline6!.backgroundColor!, - height: 1, ), - if (widget.selectedAtIndex != -1) buildSelectedItem(), - Flexible( - child: Stack( - alignment: Alignment.center, - children: [ - items.length > 3 ? Scrollbar( - controller: controller, - child: itemsList(), - ) : itemsList(), - (widget.description?.isNotEmpty ?? false) - ? Positioned( - bottom: 24, - left: 24, - right: 24, - child: Text( - widget.description!, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w500, - fontFamily: 'Lato', - decoration: TextDecoration.none, - color: Theme.of(context).primaryTextTheme.headline6!.color!, - ), + Divider( + color: Theme.of(context).accentTextTheme.headline6!.backgroundColor!, + height: 1, + ), + if (widget.selectedAtIndex != -1) buildSelectedItem(), + Flexible( + child: Stack( + alignment: Alignment.center, + children: [ + items.length > 3 ? Scrollbar( + controller: controller, + child: itemsList(), + ) : itemsList(), + (widget.description?.isNotEmpty ?? false) + ? Positioned( + bottom: 24, + left: 24, + right: 24, + child: Text( + widget.description!, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w500, + fontFamily: 'Lato', + decoration: TextDecoration.none, + color: Theme.of(context).primaryTextTheme.headline6!.color!, ), - ) - : Offstage(), - ], - ), + ), + ) + : Offstage(), + ], ), - ], - ), - ), + ), + ], ), ), - ) - ], + ), + ), ), + SizedBox(height: ResponsiveLayoutUtil.kPopupSpaceHeight), AlertCloseButton(), ], ), diff --git a/lib/src/widgets/primary_button.dart b/lib/src/widgets/primary_button.dart index a563319c5..c27169894 100644 --- a/lib/src/widgets/primary_button.dart +++ b/lib/src/widgets/primary_button.dart @@ -1,3 +1,4 @@ +import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:dotted_border/dotted_border.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; @@ -24,28 +25,31 @@ class PrimaryButton extends StatelessWidget { @override Widget build(BuildContext context) { - final content = SizedBox( - width: double.infinity, - height: 52.0, - child: TextButton( - onPressed: isDisabled - ? (onDisabledPressed != null ? onDisabledPressed : null) : onPressed, - style: ButtonStyle(backgroundColor: MaterialStateProperty.all(isDisabled ? color.withOpacity(0.5) : color), - shape: MaterialStateProperty.all( - RoundedRectangleBorder( - borderRadius: BorderRadius.circular(26.0), + final content = ConstrainedBox( + constraints: BoxConstraints(maxWidth: ResponsiveLayoutUtil.kDesktopMaxWidthConstraint), + child: SizedBox( + width: double.infinity, + height: 52.0, + child: TextButton( + onPressed: isDisabled + ? (onDisabledPressed != null ? onDisabledPressed : null) : onPressed, + style: ButtonStyle(backgroundColor: MaterialStateProperty.all(isDisabled ? color.withOpacity(0.5) : color), + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(26.0), + ), ), - ), - overlayColor: MaterialStateProperty.all(Colors.transparent)), - child: Text(text, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 15.0, - fontWeight: FontWeight.w600, - color: isDisabled - ? textColor.withOpacity(0.5) - : textColor)), - )); + overlayColor: MaterialStateProperty.all(Colors.transparent)), + child: Text(text, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 15.0, + fontWeight: FontWeight.w600, + color: isDisabled + ? textColor.withOpacity(0.5) + : textColor)), + )), + ); return isDottedBorder ? DottedBorder( @@ -77,29 +81,32 @@ class LoadingPrimaryButton extends StatelessWidget { @override Widget build(BuildContext context) { - return SizedBox( - width: double.infinity, - height: 52.0, - child: TextButton( - onPressed: (isLoading || isDisabled) ? null : onPressed, - style: ButtonStyle(backgroundColor: MaterialStateProperty.all(isDisabled ? color.withOpacity(0.5) : color), - shape: MaterialStateProperty.all( - RoundedRectangleBorder( - borderRadius: BorderRadius.circular(26.0), - ), - )), - - child: isLoading - ? CupertinoActivityIndicator(animating: true) - : Text(text, - style: TextStyle( - fontSize: 15.0, - fontWeight: FontWeight.w600, - color: isDisabled - ? textColor.withOpacity(0.5) - : textColor + return ConstrainedBox( + constraints: BoxConstraints(maxWidth: ResponsiveLayoutUtil.kDesktopMaxWidthConstraint), + child: SizedBox( + width: double.infinity, + height: 52.0, + child: TextButton( + onPressed: (isLoading || isDisabled) ? null : onPressed, + style: ButtonStyle(backgroundColor: MaterialStateProperty.all(isDisabled ? color.withOpacity(0.5) : color), + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(26.0), + ), )), - )); + + child: isLoading + ? CupertinoActivityIndicator(animating: true) + : Text(text, + style: TextStyle( + fontSize: 15.0, + fontWeight: FontWeight.w600, + color: isDisabled + ? textColor.withOpacity(0.5) + : textColor + )), + )), + ); } } @@ -130,45 +137,48 @@ class PrimaryIconButton extends StatelessWidget { @override Widget build(BuildContext context) { - return SizedBox( - width: double.infinity, - height: 52.0, - child: TextButton( - onPressed: onPressed, - style: ButtonStyle(backgroundColor: MaterialStateProperty.all(color), - shape: MaterialStateProperty.all( - RoundedRectangleBorder( - borderRadius: BorderRadius.circular(radius), - ), - )), - child: Stack( - children: [ - Row( - mainAxisAlignment: mainAxisAlignment, - children: [ - Container( - width: 26.0, - height: 52.0, - decoration: BoxDecoration( - shape: BoxShape.circle, color: iconBackgroundColor), - child: Center( - child: Icon(iconData, color: iconColor, size: 22.0) - ), + return ConstrainedBox( + constraints: BoxConstraints(maxWidth: ResponsiveLayoutUtil.kDesktopMaxWidthConstraint), + child: SizedBox( + width: double.infinity, + height: 52.0, + child: TextButton( + onPressed: onPressed, + style: ButtonStyle(backgroundColor: MaterialStateProperty.all(color), + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(radius), ), - ], - ), - Container( - height: 52.0, - child: Center( - child: Text(text, - style: TextStyle( - fontSize: 16.0, - color: textColor)), + )), + child: Stack( + children: [ + Row( + mainAxisAlignment: mainAxisAlignment, + children: [ + Container( + width: 26.0, + height: 52.0, + decoration: BoxDecoration( + shape: BoxShape.circle, color: iconBackgroundColor), + child: Center( + child: Icon(iconData, color: iconColor, size: 22.0) + ), + ), + ], ), - ) - ], - ), - )); + Container( + height: 52.0, + child: Center( + child: Text(text, + style: TextStyle( + fontSize: 16.0, + color: textColor)), + ), + ) + ], + ), + )), + ); } } @@ -190,34 +200,37 @@ class PrimaryImageButton extends StatelessWidget { @override Widget build(BuildContext context) { - return SizedBox( - width: double.infinity, - height: 52.0, - child: TextButton( - onPressed: onPressed, - style: ButtonStyle(backgroundColor: MaterialStateProperty.all(color), - shape: MaterialStateProperty.all( - RoundedRectangleBorder( - borderRadius: BorderRadius.circular(26.0), - ), - )), - child:Center( - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - image, - SizedBox(width: 15), - Text( - text, - style: TextStyle( - fontSize: 15, - fontWeight: FontWeight.w600, - color: textColor + return ConstrainedBox( + constraints: BoxConstraints(maxWidth: ResponsiveLayoutUtil.kDesktopMaxWidthConstraint), + child: SizedBox( + width: double.infinity, + height: 52.0, + child: TextButton( + onPressed: onPressed, + style: ButtonStyle(backgroundColor: MaterialStateProperty.all(color), + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(26.0), ), - ) - ], - ), - ) - )); + )), + child:Center( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + image, + SizedBox(width: 15), + Text( + text, + style: TextStyle( + fontSize: 15, + fontWeight: FontWeight.w600, + color: textColor + ), + ) + ], + ), + ) + )), + ); } } diff --git a/lib/src/widgets/setting_action_button.dart b/lib/src/widgets/setting_action_button.dart new file mode 100644 index 000000000..ef2d4e1bd --- /dev/null +++ b/lib/src/widgets/setting_action_button.dart @@ -0,0 +1,81 @@ +import 'package:cake_wallet/palette.dart'; +import 'package:flutter/material.dart'; + +class SettingActionButton extends StatelessWidget { + final bool isLastTile; + final bool isSelected; + final bool isArrowVisible; + final bool selectionActive; + final VoidCallback onTap; + final String image; + final String title; + final double fromBottomEdge; + final double fromTopEdge; + final double tileHeight; + const SettingActionButton({ + super.key, + this.isLastTile = false, + this.isSelected = false, + this.selectionActive = true, + this.isArrowVisible = false, + required this.onTap, + required this.image, + required this.title, + this.tileHeight = 60, + this.fromTopEdge = 50, + this.fromBottomEdge = 25, + }); + + @override + Widget build(BuildContext context) { + Color? color = isSelected + ? Theme.of(context).textTheme.headline3!.color + : selectionActive + ? Palette.darkBlue + : Theme.of(context).textTheme.headline3!.color; + return InkWell( + onTap: onTap, + hoverColor: Colors.transparent, + child: Container( + height: tileHeight, + padding: isLastTile + ? EdgeInsets.only( + left: 24, + right: 24, + top: fromBottomEdge, + ) + : EdgeInsets.only(left: 24, right: 24), + alignment: isLastTile ? Alignment.topLeft : null, + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Image.asset( + image, + height: 16, + width: 16, + color: Palette.darkBlue, + ), + SizedBox(width: 16), + Expanded( + child: Text( + title, + style: TextStyle( + color: color, + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + ), + if (isArrowVisible) + Icon( + Icons.arrow_forward_ios, + color: color, + size: 16, + ) + ], + ), + ), + ); + } +} diff --git a/lib/src/widgets/setting_actions.dart b/lib/src/widgets/setting_actions.dart new file mode 100644 index 000000000..4dd16670a --- /dev/null +++ b/lib/src/widgets/setting_actions.dart @@ -0,0 +1,109 @@ +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/routes.dart'; +import 'package:flutter/material.dart'; + +class SettingActions { + final String name; + final String image; + final void Function(BuildContext) onTap; + + SettingActions._({ + required this.name, + required this.image, + required this.onTap, + }); + + static List all = [ + connectionSettingAction, + walletSettingAction, + addressBookSettingAction, + securityBackupSettingAction, + privacySettingAction, + displaySettingAction, + otherSettingAction, + supportSettingAction, + ]; + + static List desktopSettings = [ + connectionSettingAction, + walletSettingAction, + addressBookSettingAction, + securityBackupSettingAction, + privacySettingAction, + displaySettingAction, + otherSettingAction, + supportSettingAction, + ]; + + static SettingActions connectionSettingAction = SettingActions._( + name: S.current.connection_sync, + image: 'assets/images/nodes_menu.png', + onTap: (BuildContext context) { + Navigator.pop(context); + Navigator.of(context).pushNamed(Routes.connectionSync); + }, + ); + + static SettingActions walletSettingAction = SettingActions._( + name: S.current.wallets, + image: 'assets/images/wallet_menu.png', + onTap: (BuildContext context) { + Navigator.pop(context); + Navigator.of(context).pushNamed(Routes.walletList); + }, + ); + + static SettingActions addressBookSettingAction = SettingActions._( + name: S.current.address_book_menu, + image: 'assets/images/open_book_menu.png', + onTap: (BuildContext context) { + Navigator.pop(context); + Navigator.of(context).pushNamed(Routes.addressBook); + }, + ); + + static SettingActions securityBackupSettingAction = SettingActions._( + name: S.current.security_and_backup, + image: 'assets/images/key_menu.png', + onTap: (BuildContext context) { + Navigator.pop(context); + Navigator.of(context).pushNamed(Routes.securityBackupPage); + }, + ); + + static SettingActions privacySettingAction = SettingActions._( + name: S.current.privacy, + image: 'assets/images/privacy_menu.png', + onTap: (BuildContext context) { + Navigator.pop(context); + Navigator.of(context).pushNamed(Routes.privacyPage); + }, + ); + + static SettingActions displaySettingAction = SettingActions._( + name: S.current.display_settings, + image: 'assets/images/eye_menu.png', + onTap: (BuildContext context) { + Navigator.pop(context); + Navigator.of(context).pushNamed(Routes.displaySettingsPage); + }, + ); + + static SettingActions otherSettingAction = SettingActions._( + name: S.current.other_settings, + image: 'assets/images/settings_menu.png', + onTap: (BuildContext context) { + Navigator.pop(context); + Navigator.of(context).pushNamed(Routes.otherSettingsPage); + }, + ); + + static SettingActions supportSettingAction = SettingActions._( + name: S.current.settings_support, + image: 'assets/images/question_mark.png', + onTap: (BuildContext context) { + Navigator.pop(context); + Navigator.of(context).pushNamed(Routes.support); + }, + ); +} diff --git a/lib/store/settings_store.dart b/lib/store/settings_store.dart index b6e5a7549..02eb51da7 100644 --- a/lib/store/settings_store.dart +++ b/lib/store/settings_store.dart @@ -241,8 +241,8 @@ abstract class SettingsStoreBase with Store { {required Box nodeSource, required bool isBitcoinBuyEnabled, FiatCurrency initialFiatCurrency = FiatCurrency.usd, - BalanceDisplayMode initialBalanceDisplayMode = - BalanceDisplayMode.availableBalance}) async { + BalanceDisplayMode initialBalanceDisplayMode = BalanceDisplayMode.availableBalance, + ThemeBase? initialTheme}) async { final sharedPreferences = await getIt.getAsync(); final currentFiatCurrency = FiatCurrency.deserialize(raw: @@ -292,7 +292,7 @@ abstract class SettingsStoreBase with Store { (sharedPreferences.getBool(PreferencesKey.isDarkThemeLegacy) ?? false) ? ThemeType.dark.index : ThemeType.bright.index; - final savedTheme = ThemeList.deserialize( + final savedTheme = initialTheme ?? ThemeList.deserialize( raw: sharedPreferences.getInt(PreferencesKey.currentTheme) ?? legacyTheme); final actionListDisplayMode = ObservableList(); diff --git a/lib/utils/device_info.dart b/lib/utils/device_info.dart new file mode 100644 index 000000000..144ea3fa4 --- /dev/null +++ b/lib/utils/device_info.dart @@ -0,0 +1,11 @@ +import 'dart:io'; + +class DeviceInfo { + DeviceInfo._(); + + static DeviceInfo get instance => DeviceInfo._(); + + bool get isMobile => Platform.isAndroid || Platform.isIOS; + + bool get isDesktop => Platform.isMacOS || Platform.isWindows || Platform.isLinux; +} \ No newline at end of file diff --git a/lib/utils/exception_handler.dart b/lib/utils/exception_handler.dart index 002534ea1..d3689e7e0 100644 --- a/lib/utils/exception_handler.dart +++ b/lib/utils/exception_handler.dart @@ -69,6 +69,7 @@ class ExceptionHandler { static void onError(FlutterErrorDetails errorDetails) async { if (kDebugMode) { FlutterError.presentError(errorDetails); + debugPrint(errorDetails.toString()); return; } diff --git a/lib/utils/responsive_layout_util.dart b/lib/utils/responsive_layout_util.dart new file mode 100644 index 000000000..8ae76ca21 --- /dev/null +++ b/lib/utils/responsive_layout_util.dart @@ -0,0 +1,34 @@ +import 'package:flutter/material.dart'; + +class ResponsiveLayoutUtil { + static const double _kMobileThreshold = 900; + static const double kDesktopMaxWidthConstraint = 400; + static const double kPopupWidth = 400; + static const double kPopupSpaceHeight = 100; + + + const ResponsiveLayoutUtil._(); + + static final instance = ResponsiveLayoutUtil._(); + + bool isMobile(BuildContext context) { + final MediaQueryData mediaQueryData = MediaQuery.of(context); + return mediaQueryData.size.width < _kMobileThreshold; + } + + /// Returns dynamic size. + /// + /// If screen size is mobile, it returns 66% ([scale]) of the [originalValue]. + double getDynamicSize( + BuildContext context, + double originalValue, { + double? mobileSize, + double? scale, + }) { + scale ??= 2 / 3; + mobileSize ??= originalValue * scale; + final value = isMobile(context) ? mobileSize : originalValue; + + return value.roundToDouble(); + } +} diff --git a/lib/view_model/dashboard/dashboard_view_model.dart b/lib/view_model/dashboard/dashboard_view_model.dart index 5384ee743..b23f430f9 100644 --- a/lib/view_model/dashboard/dashboard_view_model.dart +++ b/lib/view_model/dashboard/dashboard_view_model.dart @@ -1,13 +1,9 @@ import 'package:cake_wallet/entities/exchange_api_mode.dart'; -import 'package:cake_wallet/entities/fiat_api_mode.dart'; import 'package:cake_wallet/store/anonpay/anonpay_transactions_store.dart'; import 'package:cake_wallet/view_model/dashboard/anonpay_transaction_list_item.dart'; import 'package:cake_wallet/wallet_type_utils.dart'; import 'package:cw_core/transaction_history.dart'; import 'package:cw_core/balance.dart'; -import 'package:cake_wallet/buy/order.dart'; -import 'package:cake_wallet/entities/transaction_history.dart'; -import 'package:cake_wallet/exchange/trade_state.dart'; import 'package:cake_wallet/entities/balance_display_mode.dart'; import 'package:cw_core/transaction_info.dart'; import 'package:cake_wallet/exchange/exchange_provider_description.dart'; @@ -21,10 +17,6 @@ import 'package:cake_wallet/view_model/dashboard/order_list_item.dart'; import 'package:cake_wallet/view_model/dashboard/trade_list_item.dart'; import 'package:cake_wallet/view_model/dashboard/transaction_list_item.dart'; import 'package:cake_wallet/view_model/dashboard/action_list_item.dart'; -import 'package:cake_wallet/view_model/dashboard/action_list_display_mode.dart'; -import 'package:crypto/crypto.dart'; -import 'package:flutter/services.dart'; -import 'package:hive/hive.dart'; import 'package:mobx/mobx.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/sync_status.dart'; diff --git a/lib/view_model/dashboard/desktop_sidebar_view_model.dart b/lib/view_model/dashboard/desktop_sidebar_view_model.dart new file mode 100644 index 000000000..d0320c05f --- /dev/null +++ b/lib/view_model/dashboard/desktop_sidebar_view_model.dart @@ -0,0 +1,34 @@ +import 'package:mobx/mobx.dart'; + +part 'desktop_sidebar_view_model.g.dart'; + +enum SidebarItem { + dashboard, + support, + settings, + transactions; +} + +class DesktopSidebarViewModel = DesktopSidebarViewModelBase with _$DesktopSidebarViewModel; + +abstract class DesktopSidebarViewModelBase with Store { + DesktopSidebarViewModelBase(); + + @observable + SidebarItem currentPage = SidebarItem.dashboard; + + @action + void onPageChange(SidebarItem item) { + if (currentPage == item) { + resetSidebar(); + + return; + } + currentPage = item; + } + + @action + void resetSidebar() { + currentPage = SidebarItem.dashboard; + } +} diff --git a/lib/view_model/node_list/node_list_view_model.dart b/lib/view_model/node_list/node_list_view_model.dart index deb1f29cf..3663d48ac 100644 --- a/lib/view_model/node_list/node_list_view_model.dart +++ b/lib/view_model/node_list/node_list_view_model.dart @@ -1,4 +1,6 @@ import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/store/app_store.dart'; +import 'package:cake_wallet/utils/mobx.dart'; import 'package:hive/hive.dart'; import 'package:mobx/mobx.dart'; import 'package:cw_core/wallet_base.dart'; @@ -7,25 +9,28 @@ import 'package:cw_core/node.dart'; import 'package:cake_wallet/entities/node_list.dart'; import 'package:cake_wallet/entities/default_settings_migration.dart'; import 'package:cw_core/wallet_type.dart'; -import 'package:cake_wallet/utils/mobx.dart'; part 'node_list_view_model.g.dart'; class NodeListViewModel = NodeListViewModelBase with _$NodeListViewModel; abstract class NodeListViewModelBase with Store { - NodeListViewModelBase(this._nodeSource, this.wallet, this.settingsStore) - : nodes = ObservableList() { - _nodeSource.bindToList(nodes, - filter: (Node val) => val?.type == wallet.type, initialFire: true); + NodeListViewModelBase(this._nodeSource, this._appStore) + : nodes = ObservableList(), + settingsStore = _appStore.settingsStore { + _bindNodes(); + + reaction((_) => _appStore.wallet, (WalletBase? _wallet) { + _bindNodes(); + }); } @computed Node get currentNode { - final node = settingsStore.nodes[wallet.type]; + final node = settingsStore.nodes[_appStore.wallet!.type]; if (node == null) { - throw Exception('No node for wallet type: ${wallet.type}'); + throw Exception('No node for wallet type: ${_appStore.wallet!.type}'); } return node; @@ -33,19 +38,19 @@ abstract class NodeListViewModelBase with Store { String getAlertContent(String uri) => S.current.change_current_node(uri) + - '${uri.endsWith('.onion') || uri.contains('.onion:') ? '\n' + S.current.orbot_running_alert : ''}'; + '${uri.endsWith('.onion') || uri.contains('.onion:') ? '\n' + S.current.orbot_running_alert : ''}'; final ObservableList nodes; final SettingsStore settingsStore; - final WalletBase wallet; final Box _nodeSource; + final AppStore _appStore; Future reset() async { await resetToDefault(_nodeSource); Node node; - switch (wallet.type) { + switch (_appStore.wallet!.type) { case WalletType.bitcoin: node = getBitcoinDefaultElectrumServer(nodes: _nodeSource)!; break; @@ -59,7 +64,7 @@ abstract class NodeListViewModelBase with Store { node = getHavenDefaultNode(nodes: _nodeSource)!; break; default: - throw Exception('Unexpected wallet type: ${wallet.type}'); + throw Exception('Unexpected wallet type: ${_appStore.wallet!.type}'); } await setAsCurrent(node); @@ -68,6 +73,15 @@ abstract class NodeListViewModelBase with Store { @action Future delete(Node node) async => node.delete(); - Future setAsCurrent(Node node) async => - settingsStore.nodes[wallet.type] = node; + Future setAsCurrent(Node node) async => settingsStore.nodes[_appStore.wallet!.type] = node; + + @action + void _bindNodes() { + nodes.clear(); + _nodeSource.bindToList( + nodes, + filter: (val) => val.type == _appStore.wallet!.type, + initialFire: true, + ); + } } diff --git a/lib/view_model/wallet_keys_view_model.dart b/lib/view_model/wallet_keys_view_model.dart index 023713bd8..e20089915 100644 --- a/lib/view_model/wallet_keys_view_model.dart +++ b/lib/view_model/wallet_keys_view_model.dart @@ -1,3 +1,4 @@ +import 'package:cake_wallet/store/app_store.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:mobx/mobx.dart'; import 'package:cake_wallet/generated/i18n.dart'; @@ -12,70 +13,83 @@ part 'wallet_keys_view_model.g.dart'; class WalletKeysViewModel = WalletKeysViewModelBase with _$WalletKeysViewModel; abstract class WalletKeysViewModelBase with Store { - WalletKeysViewModelBase(WalletBase wallet) - : title = wallet.type == WalletType.bitcoin || wallet.type == WalletType.litecoin + WalletKeysViewModelBase(this._appStore) + : title = _appStore.wallet!.type == WalletType.bitcoin || + _appStore.wallet!.type == WalletType.litecoin ? S.current.wallet_seed : S.current.wallet_keys, - _wallet = wallet, - _restoreHeight = wallet.walletInfo.restoreHeight, + _restoreHeight = _appStore.wallet!.walletInfo.restoreHeight, items = ObservableList() { - if (wallet.type == WalletType.monero) { - final keys = monero!.getKeys(wallet); - items.addAll([ - if (keys['publicSpendKey'] != null) - StandartListItem(title: S.current.spend_key_public, value: keys['publicSpendKey']!), - if (keys['privateSpendKey'] != null) - StandartListItem(title: S.current.spend_key_private, value: keys['privateSpendKey']!), - if (keys['publicViewKey'] != null) - StandartListItem(title: S.current.view_key_public, value: keys['publicViewKey']!), - if (keys['privateViewKey'] != null) - StandartListItem(title: S.current.view_key_private, value: keys['privateViewKey']!), - StandartListItem(title: S.current.wallet_seed, value: wallet.seed), - ]); - } + _populateItems(); - if (wallet.type == WalletType.haven) { - final keys = haven!.getKeys(wallet); - items.addAll([ - if (keys['publicSpendKey'] != null) - StandartListItem(title: S.current.spend_key_public, value: keys['publicSpendKey']!), - if (keys['privateSpendKey'] != null) - StandartListItem(title: S.current.spend_key_private, value: keys['privateSpendKey']!), - if (keys['publicViewKey'] != null) - StandartListItem(title: S.current.view_key_public, value: keys['publicViewKey']!), - if (keys['privateViewKey'] != null) - StandartListItem(title: S.current.view_key_private, value: keys['privateViewKey']!), - StandartListItem(title: S.current.wallet_seed, value: wallet.seed), - ]); - } - - if (wallet.type == WalletType.bitcoin || wallet.type == WalletType.litecoin) { - items.addAll([ - StandartListItem(title: S.current.wallet_seed, value: wallet.seed), - ]); - } + reaction((_) => _appStore.wallet, (WalletBase? _wallet) { + _populateItems(); + }); } final ObservableList items; final String title; - final WalletBase _wallet; + final AppStore _appStore; final int _restoreHeight; + void _populateItems() { + items.clear(); + + if (_appStore.wallet!.type == WalletType.monero) { + final keys = monero!.getKeys(_appStore.wallet!); + + items.addAll([ + if (keys['publicSpendKey'] != null) + StandartListItem(title: S.current.spend_key_public, value: keys['publicSpendKey']!), + if (keys['privateSpendKey'] != null) + StandartListItem(title: S.current.spend_key_private, value: keys['privateSpendKey']!), + if (keys['publicViewKey'] != null) + StandartListItem(title: S.current.view_key_public, value: keys['publicViewKey']!), + if (keys['privateViewKey'] != null) + StandartListItem(title: S.current.view_key_private, value: keys['privateViewKey']!), + StandartListItem(title: S.current.wallet_seed, value: _appStore.wallet!.seed), + ]); + } + + if (_appStore.wallet!.type == WalletType.haven) { + final keys = haven!.getKeys(_appStore.wallet!); + + items.addAll([ + if (keys['publicSpendKey'] != null) + StandartListItem(title: S.current.spend_key_public, value: keys['publicSpendKey']!), + if (keys['privateSpendKey'] != null) + StandartListItem(title: S.current.spend_key_private, value: keys['privateSpendKey']!), + if (keys['publicViewKey'] != null) + StandartListItem(title: S.current.view_key_public, value: keys['publicViewKey']!), + if (keys['privateViewKey'] != null) + StandartListItem(title: S.current.view_key_private, value: keys['privateViewKey']!), + StandartListItem(title: S.current.wallet_seed, value: _appStore.wallet!.seed), + ]); + } + + if (_appStore.wallet!.type == WalletType.bitcoin || + _appStore.wallet!.type == WalletType.litecoin) { + items.addAll([ + StandartListItem(title: S.current.wallet_seed, value: _appStore.wallet!.seed), + ]); + } + } + Future currentHeight() async { - if (_wallet.type == WalletType.haven) { + if (_appStore.wallet!.type == WalletType.haven) { return await haven!.getCurrentHeight(); } - if (_wallet.type == WalletType.monero) { + if (_appStore.wallet!.type == WalletType.monero) { return monero_wallet.getCurrentHeight(); } return null; } String get _path { - switch (_wallet.type) { + switch (_appStore.wallet!.type) { case WalletType.monero: return 'monero_wallet:'; case WalletType.bitcoin: @@ -85,7 +99,7 @@ abstract class WalletKeysViewModelBase with Store { case WalletType.haven: return 'haven_wallet:'; default: - throw Exception('Unexpected wallet type: ${_wallet.toString()}'); + throw Exception('Unexpected wallet type: ${_appStore.wallet!.toString()}'); } } @@ -103,7 +117,7 @@ abstract class WalletKeysViewModelBase with Store { Future> get _queryParams async { final restoreHeightResult = await restoreHeight; return { - 'seed': _wallet.seed, + 'seed': _appStore.wallet!.seed, if (restoreHeightResult != null) ...{'height': restoreHeightResult} }; } diff --git a/lib/view_model/wallet_list/wallet_list_item.dart b/lib/view_model/wallet_list/wallet_list_item.dart index af30b9bea..a644c07b3 100644 --- a/lib/view_model/wallet_list/wallet_list_item.dart +++ b/lib/view_model/wallet_list/wallet_list_item.dart @@ -1,13 +1,13 @@ -import 'package:flutter/foundation.dart'; import 'package:cw_core/wallet_type.dart'; class WalletListItem { - const WalletListItem( - {required this.name, - required this.type, - required this.key, - this.isCurrent = false, - this.isEnabled = true}); + const WalletListItem({ + required this.name, + required this.type, + required this.key, + this.isCurrent = false, + this.isEnabled = true, + }); final String name; final WalletType type; diff --git a/lib/view_model/wallet_list/wallet_list_view_model.dart b/lib/view_model/wallet_list/wallet_list_view_model.dart index 6d63675ba..be8d928aa 100644 --- a/lib/view_model/wallet_list/wallet_list_view_model.dart +++ b/lib/view_model/wallet_list/wallet_list_view_model.dart @@ -22,6 +22,7 @@ abstract class WalletListViewModelBase with Store { this._authService, ) : wallets = ObservableList() { _updateList(); + reaction((_) => _appStore.wallet, (_) => _updateList()); } @observable diff --git a/macos/.gitignore b/macos/.gitignore new file mode 100644 index 000000000..746adbb6b --- /dev/null +++ b/macos/.gitignore @@ -0,0 +1,7 @@ +# Flutter-related +**/Flutter/ephemeral/ +**/Pods/ + +# Xcode-related +**/dgph +**/xcuserdata/ diff --git a/macos/CakeWallet/secRandom.swift b/macos/CakeWallet/secRandom.swift new file mode 100644 index 000000000..c9b2e3593 --- /dev/null +++ b/macos/CakeWallet/secRandom.swift @@ -0,0 +1,12 @@ +import Foundation + +func secRandom(count: Int) -> Data? { + var bytes = [Int8](repeating: 0, count: count) + let status = SecRandomCopyBytes(kSecRandomDefault, bytes.count, &bytes) + + if status == errSecSuccess { + return Data(bytes: bytes, count: bytes.count) + } + + return nil +} diff --git a/macos/Flutter/Flutter-Debug.xcconfig b/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 000000000..4b81f9b2d --- /dev/null +++ b/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/macos/Flutter/Flutter-Release.xcconfig b/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 000000000..5caa9d157 --- /dev/null +++ b/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift new file mode 100644 index 000000000..feebda3f2 --- /dev/null +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -0,0 +1,36 @@ +// +// Generated file. Do not edit. +// + +import FlutterMacOS +import Foundation + +import connectivity_macos +import cw_monero +import device_info_plus +import devicelocale +import flutter_secure_storage_macos +import package_info +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")) + CwMoneroPlugin.register(with: registry.registrar(forPlugin: "CwMoneroPlugin")) + DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) + DevicelocalePlugin.register(with: registry.registrar(forPlugin: "DevicelocalePlugin")) + FlutterSecureStorageMacosPlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStorageMacosPlugin")) + FLTPackageInfoPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlugin")) + PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) + PlatformDeviceIdMacosPlugin.register(with: registry.registrar(forPlugin: "PlatformDeviceIdMacosPlugin")) + PlatformDeviceIdMacosPlugin.register(with: registry.registrar(forPlugin: "PlatformDeviceIdMacosPlugin")) + 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 b/macos/Podfile new file mode 100644 index 000000000..0c76ccf54 --- /dev/null +++ b/macos/Podfile @@ -0,0 +1,40 @@ +platform :osx, '12.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_macos_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_macos_build_settings(target) + end +end diff --git a/macos/Podfile.lock b/macos/Podfile.lock new file mode 100644 index 000000000..41861493e --- /dev/null +++ b/macos/Podfile.lock @@ -0,0 +1,118 @@ +PODS: + - connectivity_macos (0.0.1): + - FlutterMacOS + - Reachability + - cw_monero (0.0.1): + - cw_monero/Boost (= 0.0.1) + - cw_monero/Monero (= 0.0.1) + - cw_monero/OpenSSL (= 0.0.1) + - cw_monero/Sodium (= 0.0.1) + - cw_monero/Unbound (= 0.0.1) + - FlutterMacOS + - cw_monero/Boost (0.0.1): + - FlutterMacOS + - cw_monero/Monero (0.0.1): + - FlutterMacOS + - cw_monero/OpenSSL (0.0.1): + - FlutterMacOS + - cw_monero/Sodium (0.0.1): + - FlutterMacOS + - cw_monero/Unbound (0.0.1): + - FlutterMacOS + - device_info_plus (0.0.1): + - FlutterMacOS + - devicelocale (0.0.1): + - FlutterMacOS + - flutter_secure_storage_macos (3.3.1): + - FlutterMacOS + - FlutterMacOS (1.0.0) + - package_info (0.0.1): + - FlutterMacOS + - path_provider_foundation (0.0.1): + - Flutter + - FlutterMacOS + - platform_device_id (0.0.1): + - FlutterMacOS + - platform_device_id_macos (0.0.1): + - FlutterMacOS + - Reachability (3.2) + - share_plus_macos (0.0.1): + - FlutterMacOS + - shared_preferences_foundation (0.0.1): + - Flutter + - 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`) + - cw_monero (from `Flutter/ephemeral/.symlinks/plugins/cw_monero/macos`) + - device_info_plus (from `Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos`) + - devicelocale (from `Flutter/ephemeral/.symlinks/plugins/devicelocale/macos`) + - 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_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: + - Reachability + +EXTERNAL SOURCES: + connectivity_macos: + :path: Flutter/ephemeral/.symlinks/plugins/connectivity_macos/macos + cw_monero: + :path: Flutter/ephemeral/.symlinks/plugins/cw_monero/macos + device_info_plus: + :path: Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos + devicelocale: + :path: Flutter/ephemeral/.symlinks/plugins/devicelocale/macos + flutter_secure_storage_macos: + :path: Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos + FlutterMacOS: + :path: Flutter/ephemeral + package_info: + :path: Flutter/ephemeral/.symlinks/plugins/package_info/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: + :path: Flutter/ephemeral/.symlinks/plugins/platform_device_id_macos/macos + share_plus_macos: + :path: Flutter/ephemeral/.symlinks/plugins/share_plus_macos/macos + shared_preferences_foundation: + :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 + cw_monero: f8b7f104508efba2591548e76b5c058d05cba3f0 + device_info_plus: 5401765fde0b8d062a2f8eb65510fb17e77cf07f + devicelocale: 9f0f36ac651cabae2c33f32dcff4f32b61c38225 + flutter_secure_storage_macos: 6ceee8fbc7f484553ad17f79361b556259df89aa + FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 + package_info: 6eba2fd8d3371dda2d85c8db6fe97488f24b74b2 + path_provider_foundation: c68054786f1b4f3343858c1e1d0caaded73f0be9 + platform_device_id: 3e414428f45df149bbbfb623e2c0ca27c545b763 + platform_device_id_macos: f763bb55f088be804d61b96eb4710b8ab6598e94 + Reachability: 33e18b67625424e47b6cde6d202dce689ad7af96 + share_plus_macos: 853ee48e7dce06b633998ca0735d482dd671ade4 + shared_preferences_foundation: 986fc17f3d3251412d18b0265f9c64113a8c2472 + url_launcher_macos: 5335912b679c073563f29d89d33d10d459f95451 + wakelock_macos: bc3f2a9bd8d2e6c89fee1e1822e7ddac3bd004a9 + +PODFILE CHECKSUM: 505596d150d38022472859d890f709281982e016 + +COCOAPODS: 1.11.3 diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj new file mode 100644 index 000000000..06558bf57 --- /dev/null +++ b/macos/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,663 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXAggregateTarget section */ + 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; + buildPhases = ( + 33CC111E2044C6BF0003C045 /* ShellScript */, + ); + dependencies = ( + ); + name = "Flutter Assemble"; + productName = FLX; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; + 4171CB1F5A4EA2E4DC33F52F /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B38D1DBC56DBD386923BC063 /* Pods_Runner.framework */; }; + 9F565D5929954F53009A75FB /* secRandom.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F565D5729954F53009A75FB /* secRandom.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC111A2044C6BA0003C045; + remoteInfo = FLX; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 33CC110E2044A8840003C045 /* Bundle Framework */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Bundle Framework"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 094BF982245FD1012D60A103 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 0C090639294D3AAC00954DC9 /* libiconv.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libiconv.tbd; path = usr/lib/libiconv.tbd; sourceTree = SDKROOT; }; + 2A820A13B0719E9E0CD6686F /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; + 33CC10ED2044A3C60003C045 /* Cake Wallet.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Cake Wallet.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; + 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; + 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; + 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; + 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 9646C67C7114830A5ACFF5DF /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; + 9F565D5729954F53009A75FB /* secRandom.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = secRandom.swift; path = CakeWallet/secRandom.swift; sourceTree = ""; }; + 9F565D5829954F53009A75FB /* decrypt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = decrypt.swift; path = CakeWallet/decrypt.swift; sourceTree = ""; }; + B38D1DBC56DBD386923BC063 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 4171CB1F5A4EA2E4DC33F52F /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 33BA886A226E78AF003329D5 /* Configs */ = { + isa = PBXGroup; + children = ( + 33E5194F232828860026EE4D /* AppInfo.xcconfig */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, + ); + path = Configs; + sourceTree = ""; + }; + 33CC10E42044A3C60003C045 = { + isa = PBXGroup; + children = ( + 9F565D5829954F53009A75FB /* decrypt.swift */, + 9F565D5729954F53009A75FB /* secRandom.swift */, + 33FAB671232836740065AC1E /* Runner */, + 33CEB47122A05771004F2AC0 /* Flutter */, + 33CC10EE2044A3C60003C045 /* Products */, + D73912EC22F37F3D000D13A0 /* Frameworks */, + 9B6E7CA3983216A9E173F00F /* Pods */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* Cake Wallet.app */, + ); + name = Products; + sourceTree = ""; + }; + 33CC11242044D66E0003C045 /* Resources */ = { + isa = PBXGroup; + children = ( + 33CC10F22044A3C60003C045 /* Assets.xcassets */, + 33CC10F42044A3C60003C045 /* MainMenu.xib */, + 33CC10F72044A3C60003C045 /* Info.plist */, + ); + name = Resources; + path = ..; + sourceTree = ""; + }; + 33CEB47122A05771004F2AC0 /* Flutter */ = { + isa = PBXGroup; + children = ( + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, + ); + path = Flutter; + sourceTree = ""; + }; + 33FAB671232836740065AC1E /* Runner */ = { + isa = PBXGroup; + children = ( + 33CC10F02044A3C60003C045 /* AppDelegate.swift */, + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, + 33E51913231747F40026EE4D /* DebugProfile.entitlements */, + 33E51914231749380026EE4D /* Release.entitlements */, + 33CC11242044D66E0003C045 /* Resources */, + 33BA886A226E78AF003329D5 /* Configs */, + ); + path = Runner; + sourceTree = ""; + }; + 9B6E7CA3983216A9E173F00F /* Pods */ = { + isa = PBXGroup; + children = ( + 9646C67C7114830A5ACFF5DF /* Pods-Runner.debug.xcconfig */, + 2A820A13B0719E9E0CD6686F /* Pods-Runner.release.xcconfig */, + 094BF982245FD1012D60A103 /* Pods-Runner.profile.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; + D73912EC22F37F3D000D13A0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 0C090639294D3AAC00954DC9 /* libiconv.tbd */, + B38D1DBC56DBD386923BC063 /* Pods_Runner.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 33CC10EC2044A3C60003C045 /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 93B711AB4B96E7C8C5C5B844 /* [CP] Check Pods Manifest.lock */, + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + 5592D00118C2EA3C5E0B5FDF /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* Cake Wallet.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 33CC10E52044A3C60003C045 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 1300; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 33CC10EC2044A3C60003C045 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + SystemCapabilities = { + com.apple.Sandbox = { + enabled = 1; + }; + }; + }; + 33CC111A2044C6BA0003C045 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 33CC10E42044A3C60003C045; + productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 33CC10EC2044A3C60003C045 /* Runner */, + 33CC111A2044C6BA0003C045 /* Flutter Assemble */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 33CC10EB2044A3C60003C045 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3399D490228B24CF009A79C7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; + }; + 33CC111E2044C6BF0003C045 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + Flutter/ephemeral/FlutterInputs.xcfilelist, + ); + inputPaths = ( + Flutter/ephemeral/tripwire, + ); + outputFileListPaths = ( + Flutter/ephemeral/FlutterOutputs.xcfilelist, + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; + }; + 5592D00118C2EA3C5E0B5FDF /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 93B711AB4B96E7C8C5C5B844 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 33CC10E92044A3C60003C045 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 9F565D5929954F53009A75FB /* secRandom.swift in Sources */, + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; + targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 33CC10F52044A3C60003C045 /* Base */, + ); + name = MainMenu.xib; + path = Runner; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 338D0CE9231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Profile; + }; + 338D0CEA231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ARCHS = arm64; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 10; + DEVELOPMENT_TEAM = 32J6BB6VUS; + INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "Cake Wallet"; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.finance"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 12; + MARKETING_VERSION = 1.0.1; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Profile; + }; + 338D0CEB231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + 33CC10F92044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 33CC10FA2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 33CC10FC2044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ARCHS = arm64; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 10; + DEVELOPMENT_TEAM = 32J6BB6VUS; + INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "Cake Wallet"; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.finance"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 12; + MARKETING_VERSION = 1.0.1; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 33CC10FD2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ARCHS = arm64; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 10; + DEVELOPMENT_TEAM = 32J6BB6VUS; + INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "Cake Wallet"; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.finance"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 12; + MARKETING_VERSION = 1.0.1; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 33CC111C2044C6BA0003C045 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 33CC111D2044C6BA0003C045 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10F92044A3C60003C045 /* Debug */, + 33CC10FA2044A3C60003C045 /* Release */, + 338D0CE9231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10FC2044A3C60003C045 /* Debug */, + 33CC10FD2044A3C60003C045 /* Release */, + 338D0CEA231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC111C2044C6BA0003C045 /* Debug */, + 33CC111D2044C6BA0003C045 /* Release */, + 338D0CEB231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/macos/Runner.xcodeproj/project_base.pbxproj b/macos/Runner.xcodeproj/project_base.pbxproj new file mode 100644 index 000000000..fb3e76252 --- /dev/null +++ b/macos/Runner.xcodeproj/project_base.pbxproj @@ -0,0 +1,636 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 51; + objects = { + +/* Begin PBXAggregateTarget section */ + 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; + buildPhases = ( + 33CC111E2044C6BF0003C045 /* ShellScript */, + ); + dependencies = ( + ); + name = "Flutter Assemble"; + productName = FLX; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 328F945957E1041662291EC5 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C84AA35EA80D710889C68D81 /* Pods_Runner.framework */; }; + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC111A2044C6BA0003C045; + remoteInfo = FLX; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 33CC110E2044A8840003C045 /* Bundle Framework */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Bundle Framework"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 0C090639294D3AAC00954DC9 /* libiconv.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libiconv.tbd; path = usr/lib/libiconv.tbd; sourceTree = SDKROOT; }; + 135D3AD0276D31F62BBEDDBF /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; + 33CC10ED2044A3C60003C045 /* cake_wallet_new.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = cake_wallet_new.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; + 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; + 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; + 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; + 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 359F2F22842E234537DED5E3 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; + C84AA35EA80D710889C68D81 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + FF499CFF131B036E3C5638D0 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 328F945957E1041662291EC5 /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 33BA886A226E78AF003329D5 /* Configs */ = { + isa = PBXGroup; + children = ( + 33E5194F232828860026EE4D /* AppInfo.xcconfig */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, + ); + path = Configs; + sourceTree = ""; + }; + 33CC10E42044A3C60003C045 = { + isa = PBXGroup; + children = ( + 33FAB671232836740065AC1E /* Runner */, + 33CEB47122A05771004F2AC0 /* Flutter */, + 33CC10EE2044A3C60003C045 /* Products */, + D73912EC22F37F3D000D13A0 /* Frameworks */, + 9B6E7CA3983216A9E173F00F /* Pods */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* cake_wallet_new.app */, + ); + name = Products; + sourceTree = ""; + }; + 33CC11242044D66E0003C045 /* Resources */ = { + isa = PBXGroup; + children = ( + 33CC10F22044A3C60003C045 /* Assets.xcassets */, + 33CC10F42044A3C60003C045 /* MainMenu.xib */, + 33CC10F72044A3C60003C045 /* Info.plist */, + ); + name = Resources; + path = ..; + sourceTree = ""; + }; + 33CEB47122A05771004F2AC0 /* Flutter */ = { + isa = PBXGroup; + children = ( + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, + ); + path = Flutter; + sourceTree = ""; + }; + 33FAB671232836740065AC1E /* Runner */ = { + isa = PBXGroup; + children = ( + 33CC10F02044A3C60003C045 /* AppDelegate.swift */, + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, + 33E51913231747F40026EE4D /* DebugProfile.entitlements */, + 33E51914231749380026EE4D /* Release.entitlements */, + 33CC11242044D66E0003C045 /* Resources */, + 33BA886A226E78AF003329D5 /* Configs */, + ); + path = Runner; + sourceTree = ""; + }; + 9B6E7CA3983216A9E173F00F /* Pods */ = { + isa = PBXGroup; + children = ( + 359F2F22842E234537DED5E3 /* Pods-Runner.debug.xcconfig */, + 135D3AD0276D31F62BBEDDBF /* Pods-Runner.release.xcconfig */, + FF499CFF131B036E3C5638D0 /* Pods-Runner.profile.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; + D73912EC22F37F3D000D13A0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 0C090639294D3AAC00954DC9 /* libiconv.tbd */, + C84AA35EA80D710889C68D81 /* Pods_Runner.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 33CC10EC2044A3C60003C045 /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 8AB41FC42599228A92F51A44 /* [CP] Check Pods Manifest.lock */, + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + F015812745AAC61FF550BB30 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* cake_wallet_new.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 33CC10E52044A3C60003C045 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 1300; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 33CC10EC2044A3C60003C045 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.Sandbox = { + enabled = 1; + }; + }; + }; + 33CC111A2044C6BA0003C045 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 33CC10E42044A3C60003C045; + productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 33CC10EC2044A3C60003C045 /* Runner */, + 33CC111A2044C6BA0003C045 /* Flutter Assemble */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 33CC10EB2044A3C60003C045 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3399D490228B24CF009A79C7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; + }; + 33CC111E2044C6BF0003C045 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + Flutter/ephemeral/FlutterInputs.xcfilelist, + ); + inputPaths = ( + Flutter/ephemeral/tripwire, + ); + outputFileListPaths = ( + Flutter/ephemeral/FlutterOutputs.xcfilelist, + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; + }; + 8AB41FC42599228A92F51A44 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + F015812745AAC61FF550BB30 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 33CC10E92044A3C60003C045 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; + targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 33CC10F52044A3C60003C045 /* Base */, + ); + name = MainMenu.xib; + path = Runner; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 338D0CE9231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Profile; + }; + 338D0CEA231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ARCHS = ; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Profile; + }; + 338D0CEB231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + 33CC10F92044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 33CC10FA2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 33CC10FC2044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ARCHS = ; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 33CC10FD2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ARCHS = ; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 33CC111C2044C6BA0003C045 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 33CC111D2044C6BA0003C045 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10F92044A3C60003C045 /* Debug */, + 33CC10FA2044A3C60003C045 /* Release */, + 338D0CE9231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10FC2044A3C60003C045 /* Debug */, + 33CC10FD2044A3C60003C045 /* Release */, + 338D0CEA231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC111C2044C6BA0003C045 /* Debug */, + 33CC111D2044C6BA0003C045 /* Release */, + 338D0CEB231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 000000000..8536e9a81 --- /dev/null +++ b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/macos/Runner.xcworkspace/contents.xcworkspacedata b/macos/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..21a3cc14c --- /dev/null +++ b/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/macos/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/macos/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 000000000..f9b0d7c5e --- /dev/null +++ b/macos/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/macos/Runner/AppDelegate.swift b/macos/Runner/AppDelegate.swift new file mode 100644 index 000000000..0c8973175 --- /dev/null +++ b/macos/Runner/AppDelegate.swift @@ -0,0 +1,33 @@ +import Cocoa +import FlutterMacOS +import IOKit.pwr_mgt + +@NSApplicationMain +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } + + override func applicationDidFinishLaunching(_ notification: Notification) { + let controller : FlutterViewController = mainFlutterWindow?.contentViewController as! FlutterViewController + + let utilsChannel = FlutterMethodChannel( + name: "com.cake_wallet/native_utils", + binaryMessenger: controller.engine.binaryMessenger) + utilsChannel.setMethodCallHandler({ [weak self] (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in + switch call.method { + case "sec_random": + guard let args = call.arguments as? Dictionary, + let count = args["count"] as? Int else { + result(nil) + return + } + + result(secRandom(count: count)) + + default: + result(FlutterMethodNotImplemented) + } + }) + } +} diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000..96d3fee1a --- /dev/null +++ b/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "info": { + "version": 1, + "author": "xcode" + }, + "images": [ + { + "size": "16x16", + "idiom": "mac", + "filename": "app_icon_16.png", + "scale": "1x" + }, + { + "size": "16x16", + "idiom": "mac", + "filename": "app_icon_32.png", + "scale": "2x" + }, + { + "size": "32x32", + "idiom": "mac", + "filename": "app_icon_32.png", + "scale": "1x" + }, + { + "size": "32x32", + "idiom": "mac", + "filename": "app_icon_64.png", + "scale": "2x" + }, + { + "size": "128x128", + "idiom": "mac", + "filename": "app_icon_128.png", + "scale": "1x" + }, + { + "size": "128x128", + "idiom": "mac", + "filename": "app_icon_256.png", + "scale": "2x" + }, + { + "size": "256x256", + "idiom": "mac", + "filename": "app_icon_256.png", + "scale": "1x" + }, + { + "size": "256x256", + "idiom": "mac", + "filename": "app_icon_512.png", + "scale": "2x" + }, + { + "size": "512x512", + "idiom": "mac", + "filename": "app_icon_512.png", + "scale": "1x" + }, + { + "size": "512x512", + "idiom": "mac", + "filename": "app_icon_1024.png", + "scale": "2x" + } + ] +} \ No newline at end of file diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png new file mode 100644 index 0000000000000000000000000000000000000000..73101354a94f8a498125cce07b600e77716908b9 GIT binary patch literal 112502 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%0D;JkNN+MqBocLt_iy%RW+p&eoMc~h|;6P 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

4(}nfD_-v~LRXwmlm^$IQyF@ye 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

&LH;XvN0f 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 literal 0 HcmV?d00001 diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png new file mode 100644 index 0000000000000000000000000000000000000000..9ceee3c5eb2df653ceaa19d223a25a5f281fa64a GIT binary patch literal 7683 zcmV+e9{k~nP)92J`YMwWD#1KDo@fwllfrW|w(f!vIOFOwK2K((qIGV=sT zisz7(qV5ta9Tk88+JKoAU|d5FjoUSZjZ*{EadJdWTRZ6J)rJ- zG;07!_FVHIfVNI`IJO>5$4GdM3&6pROAxf@ED~eXUAA&UfST3@$9|ZY_^YtM05}+E{`zgLt(0SlWe7KIDq7cByyaA?ex{SJx1F&=1Sw8#%^%20r zR)E=mtb%jz3SVAucp4IjvpdcxJ7d)nfT?|-t(z-_ z|6X9iKZUvd`{LE}3}t7oIs#ND2Ywo0dy$T8^3xS z+I%_#mBN=7RP*ng!h8~bTqG~TXHX3R226K{`|x$B6u!JDQ2X;p?Z@#`^NYYg0lIkG zVzAFH`lZYJvpx)PctXNoFo1XAGw@3Q3tL@$z3Oj@WzBhgj^f}7by6-J$;i%1MpA6H z@Qj6RHM&+XT9|t`;)rhzR-pFhd6UhL;$8R*{1Je)@=f>s38vPX)Q`_0C-WuZo}9d!twOs3uLBbBa^sb5ADa@b0LV2hxCgb`%%LZu(Ym%wjQ0(q(^_$Z!r+MdNokC_bgQV{M-DJ;D-S9Tj*iv ztSwM)`+hE7L>$10?Z4p6o*=x6P|i4E)|C(`R}O?pt?5d6QXcT^Mizd# zu@iqU-wDsL?(iJF07jN=As-RcqV|hJ`2=X!vp!mO{}!rO{Tx!_g7L?a2?*MGoz%W+ zpB^6r{4qBGM>Ypyz>LA@F?*)N^S8yM|6MwXs1)su>JT z3doz9oJln}0ZGx(corH);n_`uUI{~VL_Tt|WIyA<)g;X7F#@CJA4m7GzR+gVj)f=` zzWUUDaVU=fR<=N&@jpRzs-Ht_L;x0kIS7w0Cds^;3B6vcs%YQc3+;OKM{Ngp^4^Qu zC&bHaVO#KjZ3m!{D<`}qCo>)|pPa&_gL`rL#9=%M&MK7*Z^;a8nC^#1_wHiUf8nzRwMvkT$nN#YWOz>R?S)=pNNod@D zGzr}d?@dxc!Tq{=HBqpygi1@n1$tI zX5y7*2%kYY1YnaiU59)J)zp3tx9P(#?mrf967oyEvthd$7`x<0G-&@drK+aHG6;m) zqlMcHI=(^o(Z1L``zPGLlqfaDjgv2N|KcQNkLW2YltX}ao{dq%)W z68_s#gs)G896EaldW~C)DpYGZUWr)^&g~~-`SGDRuyG3hTbDr8gtC;5%+x5X8uJ;V!lZj$ANR4vj7K(2F8(`X|lN7qy@LU!dAcxV^swrk} zyMQ%SJP_pnK)vND9c3ec2|3g*U0Chsv$k=#cWNaLtUD+5j`n>V@$HrXa?EP;SdRAt zBcczLDLdU2mk&p%yExKNHUc#6U`BEBC%i7llAiJy8)nT%wiM0Z%FPVZemhFxp5_R@ z2)cT;;IsW07W8e0kc*nLEJ!hwjR38?dls&|#TPi7_`?TJ??{fDjjXjWX@egOOdNSE zLs=l8h@?Id)DKfW>4BuU(!*9YKyd=F2A0OIJ;A5?kB_+k{}l%%USqwEQ&-P~g@Ze< zWl#nz9o;cu-At?)u>@IiG$X4JiW9)xP75_{-IZ9DlHhRUxA|0SBwy|1-4N~i&4s25 zzW@Ss^qzyOClBD%w)^TXOmQeqfL5+9g(G43!JD|7II%TQ;x%In;H$-JQLwUSd4V2= z&0K?<$Gaopg}Te~4vG_?QHKsxNd-JrKn@p={wj5Wgn<*?Va}`e;Rjarx}o2f?%1** zK;1FCi`2@r44V|mo341_D_F_*Qyu+?!$jTv4?w@2++nWP>cX3 zmQ`VF_7N}fKS%%W1|6jMU;G;L{N27_tA>e4Bq)i)>nsiydZL1AU?_lNH37n8biUT4 zY(Fs<7jhj++m7-$|F-n>)hVf4P4VYfC=;oWMS_rB%C`JHlXM5h_ zf41W8y-SjvzuL~)aB}Hi@-$Uc9HJ0=jIHP(WC7KDubNs2iz+?*?q?x@t^_T?P7Sou zA_A12{u44EhC1TuS(zlNv3gCtQO9{QB16^A00jx);GpDk{D-uZa72W}O1#$Iqa{Ti zmSX4W+xxxD$LPA^W}p`dcXgEXwWFER9$={@mhN48y5uRExJJb-s zg5EMNS5hj#f|N#Q3F^NHbxFUsG7#XRl+ImklCh_Sxbyd#%5pkMs*M!Dh2G`B`f^GD z3)@;sF2{d}x)+Lk39V%YR+zECNQOE3OAF83-WAnt!0IRqCN>TqsU}oUk@ObKa_!Q2)94 zoKPag%>t|8-lPStr;3^ZdkQ<77>N_$0F^U_6Qg-|*fvcqFBhxw9>5Jal zgKV|#!+hivo;D`+-_2ZHc>WYl=F$J%OW1Zt%C9Bak?Vy6dzt^xv!*zKqR9kZd?S;m z1<WH$@~9gWiqWQ^UG$_<-Jq`r#Xmnl!b>aNQYc4)GvLg_UdVi zW|d9aZU3tR6OctL(9;n$1D}x=xE@jP>SmOEM>FxF92Ba5xgul^nW<_Y9Ps`GVB$$n zrh?llzclR2*RZNC@mgAn+^X_#62eq_aWmjWwfZXAad!GM3Euzd*BlZG+vZNu?_RY; zZ|Fm5W|JAnrG(wg#KT9XL3Ycl{u$!reGIsjC*Gd`$q8(q-DDohQt;udY}w#W@t(3Y;#^W(1WYVj z$;yVKeEua{KD)lC8E8r#JM-Mz84FOYg!<1Qm4EQ{e@^1zcn)c~91L`en*o6^8o2v3 zAKfK{kN?Kz3$=q>VDFNv{wacr`2flB39#a&J3s&nJ9~*&lA?(O3HRh70K*;<#wq0H1TfK+;6T2Q`hOjB53eLQ zd8iVK5g_q}f=te>xAK&%F#E{RE5dhDGZZ60TucT{sbve{ zS_82XW>wuee+KR&?M74;U zrTA;_iZtVhdTLU71c!SWLphrkceh7V&2J&R} zPaF}TzLuC%R#Hv}V>zTsXpp6l9{Xbpx|vH$|6Ywh3~9*^5OS5bZD8I9iV@(+opAaJ zOlK;;L5K}BY1y?C?wva(c8zI;yXXASw)X;gvbsxZ`968&jf|yNOsd9V&9Kx>dMqHRY_wo(}eSqjU$ViJS+^t*LATQ0cC;S#4lQkn) z?P2DTGXXX#Wh<%#$-}nZ;cOCzS6Lh^^`r}T@B4WFtmpd3mTmIh%r_Jxz)SL8Uq^?a zj+32oi&Kc&A9vFsNelBgkl89A{RS4;yMwakCMw%05++z%OH3BKIeqkwH$i( zzTXyO^3Q`HhZYDULw-AjLvs^x0ump=p z&PpMQcShVla|!Kw@)HRN&A`BM*tP70#E*66uLtn>d@CBX{-3PJvqqtRUUFz=B7P?u zLUxV?{4(-joja_Pr%rY8`*st7^5qgfeX7t)TX1amLpg3%85AeLy~|hVyJgXP@zXV2 z*WL^6eLup{osYz>WfCcVpZfzA?(ZjYkZ1|)p=e;Cv$z>xsy#!F$xWC`AlRMbC{aTMstT9TO!D43$Ut zcTcZ@-_iqRDgaL?+kk|7frrtlA6pw(avy%#p@R`WuENz59un8>O(znr{$?f??QH;S zrykI}sp1@--#>;mlV)h*5^)S=BS3IqIFjN*U~1W1N#zko4QqD{pZz(0p758%>#^av zSTJ}X79BngbL$Sgl|vE4$6UhVVT17Ek=kbfNkiEP5EDU0@W$V0)3X`XJ-%w603O5F z;mU!txPDS{{2UpYitqb>iut?u!OGSR>emDuUPhe8vJw68EL5Y{3oQGXv{T!5p>5Aa zO36za*g_QJ*X_dhUT%01A$iPT>=m?*!D>1vz6l6-7Oc6sL+~?B~7`OZiJ{htC+B$4_ znW~7uAul%_XZ(DyamFU3X`I6^5oIR;+nMA1?q4xvjwks2*a9?cKM508M_|LGpQSog z?8C1fyAij}24d8lUtz8ilfY&)oyPA!j>l>LN18D0TV5zfZ6DcwoMQF|Ff^7tm_vCG zzxMN@CL~Lpj;nD?O+E*_|D-+>%Vi^8!@k_o{TE;gT&`3f(eTtxWolel*1Aa0(0iMMjv zdAmjinE&rCSUPwKpY-K2idGy(P?>hCyJJXs1PHy9gTP%2;rSKo>sE#1rG*wA^RVP( zBdi!RhCFZC;Z_O>ENIV5I((sT>5CfXY`r5J)OB)(jeQ+x>zTpIx;g|}9Acv~keii& zn8+wZ-g}6bklIAXWspVE2+QNDEz)yTy$Ok{wZ@(YSU*`Tg@|mcXzD zU!ceE_0ZChG$chEeZKjPh-ZT3U#=FCQw>lq0b(Qa@b5YwjGT8Is*J-8jGZuM=|!~i zSO?#k^YJ7^#a%UViT3oSiYV&Xa@ter4V<$E21kH(t=>rTS$ zi#@1o&lYo1Wn+vET->H%^_juAvTrUqXMZ5JMid6DsOg{?N8LIs|`7&-jHoB?Sa7+^Ywgq@K7Z)rr$GY{8OHnMQT{q`-cW45;>%H3dhC{P zkf-_~5N`Zfzxhz%QP7~1Lqd;4eDrhr>ot&;nX%*<{ z*N2T${dZ^U6uYDNXhYR5S@{5RUtp2J$ zzTFlG3sP;BcpnIadm>JscLw^&pW^7Y>&nVlwT{`{^C?(3xCfRT4OJ#EH{vmOfIDXVei9be{OvmO;ekU);GbAD(np!Y&D0Z`-3xk+ zp9Z*L;h`fiHgAhcpFkEkJh`z63qPM&>dxs3Kz*@!_>Cm^baTgi|6QnEzc(sLAPxDr zergVu4qe1YZ=re{XQD#W;p66wnSU*VOXumRB!MXMa?@~Z<7jN2?Jsrj;&+hKQx^Kqf!KEa=AEp@uU|_?fPwoiCfobx}4$M-cu>cvq02vCHaY#{htDjnNkFn1f8 zcXmM6p#$O6rk`-dag-dtrxI|&rSo1yoWbpLdvW^Dy?A&#vD68yYDW1bKyjo}%zS=t z1RYCA_)E~JLoKxRaEE=XPhf4|5qbs=g;PA0KRuicEKQ2LgV<*maPQn{1P7ia+x`~m zYTph>E_@gSC=M1%UD*4Qjvdggx&vlbfI|y&I5%mI+6|hbegkJ@X*;5ptsQjqYOxsv zLS#OM}XpB;}Mx) zVk6^mao;IAb4u8_XlMiIWda6fTBvSnLRx}tg=+{MLp{`}p$)BE;ALVCkdc9`)Hg^@ zOhA0pTj7Ef>Ga7mBrI81PVr$AKqANwdrPEX(??&&vA*?0gkDTkcd68c3P^xT6h|ci xDmW?$P{C12fC`RE0#tBR5}<;kk^mJP{|BQE8ayxB)W84$002ovPDHLkV1fio*lGX( literal 0 HcmV?d00001 diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png new file mode 100644 index 0000000000000000000000000000000000000000..ef46cd8054c4211cd2b16f739ec84a3e517bf404 GIT binary patch literal 678 zcmV;X0$KfuP);X!g=p`Nk3|iSm(KOl&lxK#*bui=vUJivklJ zT41;gOolx$um^@MOK_zbTIdQ))72h#x4RXqZ5R5&56=0Vb3Po-@9^-y@c01j1{j8W znUikXy#rb0N+fPcLsYB~4*M+L4fLa@rJ1cZR!DOT!Xv_=X}p27f+BWC{N<6c*dW{G ztK8!iI9Iq2wvV3z1tdkHroA1~j4VH+-40AoO&~gMF~S53eT*L#0oPAw=ne8Kl>WnR zmjgy^H7t+JSh7Zp>B-Mnl&}nYOBJ|q@DMBg5-1=k0{c&%{=*ntRUW}=NfNH~z5+pz zKKi=XjGDZB*uT$*KH^%b1aV7O`o<%c7x*zZSD;j%G1(ZRKuDm&#vmtso4j*Od)CvS zJ{2lR5Q+R`@JTOHvvZT931Wuro?$(OS(--4auTN0>>O|k7>~zKD2js1>9{@V!L1}7 zE`RXgl9LbrGeXNXC(rE zJFD9|aQ;9(o_5|36nJy-XfeKhwIN5L_DQ_onYSSFpB)S=kIfkBdlq^FZ)g{dh7(mV zH#H%%tQ?6l8DfO-u+PrG`m!GnjLmr7)f4&#!FfOM2DPe6&b6TY0wpxtzd#?_&;S4c M07*qoM6N<$g1_N1asU7T literal 0 HcmV?d00001 diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png new file mode 100644 index 0000000000000000000000000000000000000000..6547a1b1b312dcf34c5eb6150f17c5a8e0c42919 GIT binary patch literal 17249 zcmafaWn0_~)AsJ-F2&uUxVy8sLyNn+ySo&Z;>9U&L2-8|PH_qpDQ?Bxmfe^8`2+8# zQXU0g9 zpLCa@e~X!lc@b)Ps7m8FBYU;Z=t1A}li0YdeLpDU`aW)*K%(eZ9 z`@#&A_eJSYS^px8BPI;@%8Y<~Fz``N!^-FAt9>dk2_Q;LJ~94td^~?~h5SyEMTWHH zaskI#M0TiBKhTJP_87wsNeW%I{wVU~Rbw}D8H@sLkw@Eb2$l%?N2Xbhr7!m1v1ehQ zi1K=$))tkW&i!7@^Kag%L^qsA1b$eQ=kMIPM4BiM8zN$7!2AAN_pdbr*;yH;w@RZb;*~5BtcHP0+0NRo!}i z+;%P#PQVsf$D~`L$ml!fi3l}~UOyz4cAq!N#Jl*pH0E1UMTOPf^i)P1>V3R&`3!9v0J= zh9dR!`$Bj2PXzSxidGMR#LcNoi@co^(HTh}RI5G-*?xRDVjCU(^;1x3a=ZLHI$5|JKoz*Nzv!#7VD2|5 zIega#r#Wi)T&YuiM%>#&+mJ*VXMy&FR7oXHADuaWOC;l_~@~CigqD{leZ%I;`w1|UE=7?74-#ae=zaHEeH;qk@li09ibw~U_bEAmw zMEb(s_Cqi7=K(>c9WAQXdj{3a4_YY?oaCoRRqbz4uX)iv-}UL1-jkGQ6Jly;@|UAmpUE2Kh#0lOTCZ0SKDStq9Ob9TGQ~# zOQ1ZpUM%5TWLJs$n&)v5RM2CZ(qiaD_j+O~Ftvznw?Gd5UWyiATUMhe`N@JR=KEI4 z;0czN>lO~#{(^9ztyFQ~28mckDB3qSth-T;MwgS@)ea}xkQp3z9omSlIphI-&hG$c zj1q;J9Mu>Y;Qo&K&f?$3CMyT1rsmMDW_Cced6CO&*Pzz$!Iy02nSv$zOP-@yq#Qsg z^7e8s9mMreClse%C=(LgKrrFU0`I87ry_waAR&lOFYAbteM8ij*AEnF^+zEAGDTBy zL&D>y(|8ZW@1}C(IgC39rd_ofMo@;(tBI_~rKPnPbb_v!9;`3qk{^>v?BKVV1TE`T zX|4u(P>wAE2tuS9^L7Nxk@>*b=Hmmsx1)O+FY9`pXYNk3qVYw&DagDaH~8N1S<1|L z^2kM#;zDcIUcJ1OdJ4D10YdVm{;{|3>y7!F5eTl9ZwpgZcZE-3a)1Y_uzCg$>rf7Z5G+s#eKOpWB{$}U%qlQ0KP){ijE&WD~j}z0~ zaD1SpPQ<>1yls$}vy9s={^Sa#hw3C;IX)ryl~d_SU=s7RQ+*0W5)X3tp!C}~O@TJd z0A8;QLhk-Eb5;S0wG0X-AqiuX6tr0*X~E2kKB(_aa{iZuW(PmOnc6S$;2X?kGivdK z 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 YFD>>OFA+L-4gdfE07*qoM6N<$f{n1t7XSbN literal 0 HcmV?d00001 diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png new file mode 100644 index 0000000000000000000000000000000000000000..157b004934f008ce3a79c67eb80432818eb5255e GIT binary patch literal 42348 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 literal 0 HcmV?d00001 diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png new file mode 100644 index 0000000000000000000000000000000000000000..a46ed4535c63ac6c4de889181f173bb4cd58a43b GIT binary patch literal 3520 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 + + + + + + + + + + + + + + + + + + + + + +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/macos/Runner/Configs/AppInfo.xcconfig b/macos/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 000000000..84f96bff7 --- /dev/null +++ b/macos/Runner/Configs/AppInfo.xcconfig @@ -0,0 +1,14 @@ +// Application-level settings for the Runner target. +// +// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the +// future. If not, the values below would default to using the project name when this becomes a +// 'flutter create' template. + +// The application's name. By default this is also the title of the Flutter window. +PRODUCT_NAME = Cake Wallet + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = com.fotolockr.cakewallet + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright © 2022 com.fotolockr. All rights reserved. diff --git a/macos/Runner/Configs/Debug.xcconfig b/macos/Runner/Configs/Debug.xcconfig new file mode 100644 index 000000000..36b0fd946 --- /dev/null +++ b/macos/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/macos/Runner/Configs/Release.xcconfig b/macos/Runner/Configs/Release.xcconfig new file mode 100644 index 000000000..dff4f4956 --- /dev/null +++ b/macos/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/macos/Runner/Configs/Warnings.xcconfig b/macos/Runner/Configs/Warnings.xcconfig new file mode 100644 index 000000000..42bcbf478 --- /dev/null +++ b/macos/Runner/Configs/Warnings.xcconfig @@ -0,0 +1,13 @@ +WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings +GCC_WARN_UNDECLARED_SELECTOR = YES +CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_PRAGMA_PACK = YES +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_COMMA = YES +GCC_WARN_STRICT_SELECTOR_MATCH = YES +CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +GCC_WARN_SHADOW = YES +CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/macos/Runner/DebugProfileBase.entitlements b/macos/Runner/DebugProfileBase.entitlements new file mode 100644 index 000000000..c1b4345fe --- /dev/null +++ b/macos/Runner/DebugProfileBase.entitlements @@ -0,0 +1,18 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.server + + keychain-access-groups + + $(AppIdentifierPrefix)${BUNDLE_ID} + + com.apple.security.network.client + + + diff --git a/macos/Runner/InfoBase.plist b/macos/Runner/InfoBase.plist new file mode 100644 index 000000000..98d0ea9ee --- /dev/null +++ b/macos/Runner/InfoBase.plist @@ -0,0 +1,34 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + LSApplicationCategoryType + public.app-category.finance + + diff --git a/macos/Runner/MainFlutterWindow.swift b/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 000000000..2722837ec --- /dev/null +++ b/macos/Runner/MainFlutterWindow.swift @@ -0,0 +1,15 @@ +import Cocoa +import FlutterMacOS + +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController.init() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } +} diff --git a/macos/Runner/ReleaseBase.entitlements b/macos/Runner/ReleaseBase.entitlements new file mode 100644 index 000000000..aef6ac342 --- /dev/null +++ b/macos/Runner/ReleaseBase.entitlements @@ -0,0 +1,14 @@ + + + + + com.apple.security.app-sandbox + + keychain-access-groups + + $(AppIdentifierPrefix)${BUNDLE_ID} + + com.apple.security.network.client + + + diff --git a/pubspec_base.yaml b/pubspec_base.yaml index 7825824a9..b357619bd 100644 --- a/pubspec_base.yaml +++ b/pubspec_base.yaml @@ -9,6 +9,7 @@ dependencies: qr_flutter: ^4.0.0 uuid: 3.0.6 shared_preferences: ^2.0.15 + shared_preferences_android: 2.1.0 flutter_secure_storage: git: url: https://github.com/cake-tech/flutter_secure_storage.git @@ -34,7 +35,9 @@ dependencies: local_auth: ^2.1.0 package_info: ^2.0.0 #package_info_plus: ^1.4.2 - devicelocale: ^0.4.3 + devicelocale: + git: + url: https://github.com/OmarHatem28/flutter-devicelocale auto_size_text: ^3.0.0 dotted_border: ^2.0.0+2 smooth_page_indicator: ^1.0.0+2 @@ -61,6 +64,7 @@ dependencies: permission_handler: ^10.0.0 device_display_brightness: ^0.0.6 platform_device_id: ^1.0.1 + wakelock: ^0.6.2 flutter_mailer: ^2.0.2 device_info_plus: 8.1.0 cake_backup: @@ -72,11 +76,11 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - build_runner: ^2.1.11 + build_runner: ^2.3.3 mobx_codegen: ^2.1.1 build_resolvers: ^2.0.9 hive_generator: ^1.1.3 - flutter_launcher_icons: ^0.9.3 + flutter_launcher_icons: ^0.11.0 # check flutter_launcher_icons for usage pedantic: ^1.8.0 # replace https://github.com/dart-lang/lints#migrating-from-packagepedantic @@ -85,6 +89,9 @@ flutter_icons: image_path: "assets/images/app_logo.png" android: true ios: true + macos: + generate: true + image_path: "assets/images/app_logo.png" flutter: uses-material-design: true diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb index e06846872..430f2edcf 100644 --- a/res/values/strings_ar.arb +++ b/res/values/strings_ar.arb @@ -695,5 +695,6 @@ "optional_name": "اسم المستلم الاختياري", "clearnet_link": "رابط Clearnet", "onion_link": "رابط البصل", + "settings": "إعدادات", "sell_monero_com_alert_content": "بيع Monero غير مدعوم حتى الآن" } diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index 6a8f3c89e..3896fb133 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -697,5 +697,6 @@ "optional_name": "Optionaler Empfängername", "clearnet_link": "Clearnet-Link", "onion_link": "Zwiebel-Link", + "settings": "Einstellungen", "sell_monero_com_alert_content": "Der Verkauf von Monero wird noch nicht unterstützt" } diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index b1bf94690..fd3cdbc99 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -697,5 +697,6 @@ "onion_link": "Onion link", "decimal_places_error": "Too many decimal places", "edit_node": "Edit Node", + "settings": "Settings", "sell_monero_com_alert_content": "Selling Monero is not supported yet" } diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index 8e9e4992c..2995008ac 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -697,5 +697,6 @@ "optional_name": "Nombre del destinatario opcional", "clearnet_link": "enlace Clearnet", "onion_link": "Enlace de cebolla", + "settings": "Configuraciones", "sell_monero_com_alert_content": "Aún no se admite la venta de Monero" } diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index 9ba3066c1..4dd8e54cb 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -696,6 +696,7 @@ "optional_description": "Descriptif facultatif", "optional_name": "Nom du destinataire facultatif", "clearnet_link": "Lien Clearnet", + "settings": "Paramètres", "onion_link": "Lien .onion", "sell_monero_com_alert_content": "La vente de Monero n'est pas encore prise en charge" } diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index ea9a673a8..e8280bae7 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -697,5 +697,6 @@ "optional_name": "वैकल्पिक प्राप्तकर्ता नाम", "clearnet_link": "क्लियरनेट लिंक", "onion_link": "प्याज का लिंक", + "settings": "समायोजन", "sell_monero_com_alert_content": "मोनेरो बेचना अभी तक समर्थित नहीं है" } diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index 8a0b4b5ed..973aaca59 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -697,5 +697,6 @@ "optional_name": "Izborno ime primatelja", "clearnet_link": "Clearnet veza", "onion_link": "Poveznica luka", + "settings": "Postavke", "sell_monero_com_alert_content": "Prodaja Monera još nije podržana" } diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index 968fb991d..4b90f8890 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -697,5 +697,6 @@ "optional_name": "Nome del destinatario facoltativo", "clearnet_link": "Collegamento Clearnet", "onion_link": "Collegamento a cipolla", + "settings": "Impostazioni", "sell_monero_com_alert_content": "La vendita di Monero non è ancora supportata" } diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index 3c45e803d..7caa1b170 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -697,5 +697,6 @@ "optional_name": "オプションの受信者名", "clearnet_link": "クリアネット リンク", "onion_link": "オニオンリンク", + "settings": "設定", "sell_monero_com_alert_content": "モネロの販売はまだサポートされていません" } diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index 1535f54b2..327da90db 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -697,5 +697,6 @@ "optional_name": "선택적 수신자 이름", "clearnet_link": "클리어넷 링크", "onion_link": "양파 링크", + "settings": "설정", "sell_monero_com_alert_content": "지원되지 않습니다." } diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index 5020cc966..aaa19d5e7 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -697,5 +697,6 @@ "optional_name": "ရွေးချယ်နိုင်သော လက်ခံသူအမည်", "clearnet_link": "Clearnet လင့်ခ်", "onion_link": "ကြက်သွန်လင့်", + "settings": "ဆက်တင်များ", "sell_monero_com_alert_content": "Monero ရောင်းချခြင်းကို မပံ့ပိုးရသေးပါ။" } diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index 9b19f6e84..875c4e5c0 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -697,5 +697,6 @@ "optional_name": "Optionele naam ontvanger", "clearnet_link": "Clearnet-link", "onion_link": "Ui koppeling", + "settings": "Instellingen", "sell_monero_com_alert_content": "Het verkopen van Monero wordt nog niet ondersteund" } diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index cdc1c916a..b2b1fcfa5 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -697,5 +697,6 @@ "optional_name": "Opcjonalna nazwa odbiorcy", "clearnet_link": "łącze Clearnet", "onion_link": "Łącznik cebulowy", + "settings": "Ustawienia", "sell_monero_com_alert_content": "Sprzedaż Monero nie jest jeszcze obsługiwana" } diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index 86a256dcc..3ac194e7e 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -696,5 +696,6 @@ "optional_name": "Nome do destinatário opcional", "clearnet_link": "link clear net", "onion_link": "ligação de cebola", + "settings": "Configurações", "sell_monero_com_alert_content": "A venda de Monero ainda não é suportada" } diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index 7707e0003..d9d5e1d4f 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -697,5 +697,6 @@ "optional_name": "Необязательное имя получателя", "clearnet_link": "Клирнет ссылка", "onion_link": "Луковая ссылка", + "settings": "Настройки", "sell_monero_com_alert_content": "Продажа Monero пока не поддерживается" } diff --git a/res/values/strings_th.arb b/res/values/strings_th.arb index 01a05204e..4b400caea 100644 --- a/res/values/strings_th.arb +++ b/res/values/strings_th.arb @@ -695,5 +695,6 @@ "optional_name": "ชื่อผู้รับเพิ่มเติม", "clearnet_link": "ลิงค์เคลียร์เน็ต", "onion_link": "ลิงค์หัวหอม", + "settings": "การตั้งค่า", "sell_monero_com_alert_content": "ยังไม่รองรับการขาย Monero" } diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb index 2df815175..b8fd76499 100644 --- a/res/values/strings_tr.arb +++ b/res/values/strings_tr.arb @@ -697,5 +697,6 @@ "optional_name": "İsteğe bağlı alıcı adı", "clearnet_link": "Net bağlantı", "onion_link": "soğan bağlantısı", + "settings": "ayarlar", "sell_monero_com_alert_content": "Monero satışı henüz desteklenmiyor" } diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index ee7c4e507..1b9995893 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -696,5 +696,6 @@ "optional_name": "Додаткове ім'я одержувача", "clearnet_link": "Посилання Clearnet", "onion_link": "Посилання на цибулю", + "settings": "Налаштування", "sell_monero_com_alert_content": "Продаж Monero ще не підтримується" } diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index fcaf66b6a..54c3c14a9 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -696,5 +696,6 @@ "optional_name": "可选收件人姓名", "clearnet_link": "明网链接", "onion_link": "洋葱链接", + "settings": "设置", "sell_monero_com_alert_content": "尚不支持出售门罗币" } diff --git a/scripts/macos/app_config.sh b/scripts/macos/app_config.sh new file mode 100755 index 000000000..231945659 --- /dev/null +++ b/scripts/macos/app_config.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +CAKEWALLET="cakewallet" +DIR=`pwd` + +if [ -z "$APP_MACOS_TYPE" ]; then + echo "Please set APP_MACOS_TYPE" + exit 1 +fi + +cd ../.. # go to root +cp -rf ./macos/Runner/InfoBase.plist ./macos/Runner/Info.plist +/usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier ${APP_MACOS_BUNDLE_ID}" ./macos/Runner/Info.plist +/usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString ${APP_MACOS_VERSION}" ./macos/Runner/Info.plist +/usr/libexec/PlistBuddy -c "Set :CFBundleVersion ${APP_MACOS_BUILD_NUMBER}" ./macos/Runner/Info.plist + +# Fill entitlements Bundle ID +cp -rf ./macos/Runner/DebugProfileBase.entitlements ./macos/Runner/DebugProfile.entitlements +cp -rf ./macos/Runner/ReleaseBase.entitlements ./macos/Runner/Release.entitlements +sed -i '' "s/\${BUNDLE_ID}/${APP_MACOS_BUNDLE_ID}/g" ./macos/Runner/DebugProfile.entitlements +sed -i '' "s/\${BUNDLE_ID}/${APP_MACOS_BUNDLE_ID}/g" ./macos/Runner/Release.entitlements +CONFIG_ARGS="" + +case $APP_MACOS_TYPE in + $CAKEWALLET) + CONFIG_ARGS="--monero --bitcoin";; #--haven +esac + +cp -rf pubspec_description.yaml pubspec.yaml +flutter pub get +flutter pub run tool/generate_pubspec.dart +flutter pub get +flutter packages pub run tool/configure.dart $CONFIG_ARGS +cd $DIR \ No newline at end of file diff --git a/scripts/macos/app_env.sh b/scripts/macos/app_env.sh new file mode 100755 index 000000000..3fceeb94d --- /dev/null +++ b/scripts/macos/app_env.sh @@ -0,0 +1,39 @@ +#!/bin/sh + +APP_MACOS_NAME="" +APP_MACOS_VERSION="" +APP_MACOS_BUILD_VERSION="" +APP_MACOS_BUNDLE_ID="" + +CAKEWALLET="cakewallet" + +TYPES=($CAKEWALLET) +APP_MACOS_TYPE=$CAKEWALLET + +if [ -n "$1" ]; then + APP_MACOS_TYPE=$1 +fi + +CAKEWALLET_NAME="Cake Wallet" +CAKEWALLET_VERSION="1.0.1" +CAKEWALLET_BUILD_NUMBER=11 +CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet" + +if ! [[ " ${TYPES[*]} " =~ " ${APP_MACOS_TYPE} " ]]; then + echo "Wrong app type." + exit 1 +fi + +case $APP_MACOS_TYPE in + $CAKEWALLET) + APP_MACOS_NAME=$CAKEWALLET_NAME + APP_MACOS_VERSION=$CAKEWALLET_VERSION + APP_MACOS_BUILD_NUMBER=$CAKEWALLET_BUILD_NUMBER + APP_MACOS_BUNDLE_ID=$CAKEWALLET_BUNDLE_ID;; +esac + +export APP_MACOS_TYPE +export APP_MACOS_NAME +export APP_MACOS_VERSION +export APP_MACOS_BUILD_NUMBER +export APP_MACOS_BUNDLE_ID diff --git a/scripts/macos/build_all.sh b/scripts/macos/build_all.sh new file mode 100755 index 000000000..4116704bf --- /dev/null +++ b/scripts/macos/build_all.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +./build_monero_all.sh \ No newline at end of file diff --git a/scripts/macos/build_boost_arm64.sh b/scripts/macos/build_boost_arm64.sh new file mode 100755 index 000000000..11f26040f --- /dev/null +++ b/scripts/macos/build_boost_arm64.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +. ./build_boost_common.sh +build_boost_arm64 \ No newline at end of file diff --git a/scripts/macos/build_boost_common.sh b/scripts/macos/build_boost_common.sh new file mode 100755 index 000000000..0c75be2bd --- /dev/null +++ b/scripts/macos/build_boost_common.sh @@ -0,0 +1,210 @@ +#!/bin/sh + +. ./config.sh + +# Boost combined + +BOOST_CXXFLAGS_COMBINED="-arch x86_64 -arch arm64" +BOOST_CFLAGS_COMBINED="-arch x86_64 -arch arm64" +BOOST_LINKFLAGS_COMBINED="-arch x86_64 -arch arm64" + +# Boost arm64 + +BOOST_CXXFLAGS_ARM64="-arch arm64" +BOOST_CFLAGS_ARM64="-arch arm64" +BOOST_LINKFLAGS_ARM64="-arch arm64" + +# Boost x86_64 + +BOOST_CXXFLAGS_X86_64="-arch x86_64" +BOOST_CFLAGS_X86_64="-arch x86_64" +BOOST_LINKFLAGS_X86_64="-arch x86_64" + +# Boost B2 arm64 + +BOOST_B2_CXXFLAGS_ARM_64="-arch arm64" +BOOST_B2_CFLAGS_ARM_64="-arch arm64" +BOOST_B2_LINKFLAGS_ARM_64="-arch arm64" +BOOST_B2_BUILD_DIR_ARM_64=macos-arm64 + +# Boost B2 x86_64 + +BOOST_B2_CXXFLAGS_X86_64="-arch x86_64" +BOOST_B2_CFLAGS_X86_64="-arch x86_64" +BOOST_B2_LINKFLAGS_X86_64="-arch x86_64" +BOOST_B2_BUILD_DIR_X86_64=macos-x86_64 + +build_boost_init_common() { + CXXFLAGS=$1 + CFLAGS=$2 + LINKFLAGS=$3 + BOOST_SRC_DIR=${EXTERNAL_MACOS_SOURCE_DIR}/boost_1_72_0 + BOOST_FILENAME=boost_1_72_0.tar.bz2 + BOOST_VERSION=1.72.0 + BOOST_FILE_PATH=${EXTERNAL_MACOS_SOURCE_DIR}/${BOOST_FILENAME} + BOOST_SHA256="59c9b274bc451cf91a9ba1dd2c7fdcaf5d60b1b3aa83f2c9fa143417cc660722" + + if [ ! -e "$BOOST_FILE_PATH" ]; then + curl -L http://downloads.sourceforge.net/project/boost/boost/${BOOST_VERSION}/${BOOST_FILENAME} > $BOOST_FILE_PATH + fi + + echo $BOOST_SHA256 *$BOOST_FILE_PATH | shasum -a 256 -c - || exit 1 + + cd $EXTERNAL_MACOS_SOURCE_DIR + rm -rf $BOOST_SRC_DIR + tar -xvf $BOOST_FILE_PATH -C $EXTERNAL_MACOS_SOURCE_DIR + cd $BOOST_SRC_DIR + ./bootstrap.sh --with-toolset=clang-darwin cxxflags="${CXXFLAGS}" cflags="${CFLAGS}" linkflags="${LINKFLAGS}" +} + +build_boost_init_arm64() { + CXXFLAGS="-arch arm64" + CFLAGS="-arch arm64" + LINKFLAGS="-arch arm64" + build_boost_init_common "${CXXFLAGS}" "${CFLAGS}" "${LINKFLAGS}" +} + +build_boost_init_x86_64() { + CXXFLAGS="-arch x86_64" + CFLAGS="-arch x86_64" + LINKFLAGS="-arch x86_64" + build_boost_init_common "${CXXFLAGS}" "${CFLAGS}" "${LINKFLAGS}" +} + +build_boost_init_universal() { + CXXFLAGS="-arch x86_64 -arch arm64" + CFLAGS="-arch x86_64 -arch arm64" + LINKFLAGS="-arch x86_64 -arch arm64" + build_boost_init_common "${CXXFLAGS}" "${CFLAGS}" "${LINKFLAGS}" +} + +build_boost_compile_common() { + ARCH=$1 + ABI=$2 + CXXFLAGS=$3 + CFLAGS=$4 + LINKFLAGS=$5 + FLAGS=$6 + BUILD_DIR=$7 + ./b2 toolset=clang-darwin target-os=darwin architecture="${ARCH}" cxxflags="${CXXFLAGS}" cflags="${CFLAGS}" linkflags="${LINKFLAGS}" abi="${ABI}" "${FLAGS}" -a \ + --with-chrono \ + --with-date_time \ + --with-filesystem \ + --with-program_options \ + --with-regex \ + --with-serialization \ + --with-system \ + --with-thread \ + --with-locale \ + --build-dir=$BUILD_DIR \ + --stagedir=${BUILD_DIR}/stage \ + link=static +} + +build_boost_compile_arm64() { + ARCH="arm" + ABI="aapcs" + CXXFLAGS="-arch arm64" + CFLAGS="-arch arm64" + LINKFLAGS="-arch arm64" + FLAGS="" + BUILD_DIR="macos-arm64" + build_boost_compile_common "${ARCH}" "${ABI}" "${CXXFLAGS}" "${CFLAGS}" "${LINKFLAGS}" "${FLAGS}" "${BUILD_DIR}" +} + +build_boost_compile_x86_64() { + ARCH="x86" + ABI="sysv" + CXXFLAGS="-arch x86_64" + CFLAGS="-arch x86_64" + LINKFLAGS="-arch x86_64" + FLAGS="binary-format=mach-o" + BUILD_DIR="macos-x86_64" + build_boost_compile_common "${ARCH}" "${ABI}" "${CXXFLAGS}" "${CFLAGS}" "${LINKFLAGS}" "${FLAGS}" "${BUILD_DIR}" +} + +build_boost_compile_universal() { + ARCHES=(arm x86) + for ARCH in ${ARCHES[@]}; do + ABI="" + CXXFLAGS="" + CFLAGS="" + LINKFLAGS="" + FLAGS="" + BUILD_DIR="" + + case $ARCH in + arm) + ABI="aapcs" + CXXFLAGS="-arch arm64" + CFLAGS="-arch arm64" + LINKFLAGS="-arch arm64" + FLAGS="" + BUILD_DIR="macos-arm64";; + x86) + ABI="sysv" + CXXFLAGS="-arch x86_64" + CFLAGS="-arch x86_64" + LINKFLAGS="-arch x86_64" + FLAGS="binary-format=mach-o" + BUILD_DIR="macos-x86_64" + esac + + build_boost_compile_common "${ARCH}" "${ABI}" "${CXXFLAGS}" "${CFLAGS}" "${LINKFLAGS}" "${FLAGS}" "${BUILD_DIR}" + done +} + +build_boost_install_common() { + ARCH=$1 + LIB_DIR="" + mkdir $EXTERNAL_MACOS_LIB_DIR + mkdir $EXTERNAL_MACOS_INCLUDE_DIR + + case $ARCH in + arm64) LIB_DIR="${BOOST_B2_BUILD_DIR_ARM_64}/stage/lib";; + x86_64) LIB_DIR="${BOOST_B2_BUILD_DIR_X86_64}/stage/lib";; + *) LIB_DIR="lib";; + esac + + cp -r ${LIB_DIR}/*.a ${EXTERNAL_MACOS_LIB_DIR} + cp -r boost ${EXTERNAL_MACOS_INCLUDE_DIR} +} + +build_boost_install_arm64() { + ARCH="arm64" + build_boost_install_common $ARCH +} + +build_boost_install_x86_64() { + ARCH="x86_64" + build_boost_install_common $ARCH +} + +build_boost_install_universal() { + mkdir lib + + for blib in ${BOOST_B2_BUILD_DIR_ARM_64}/stage/lib/*.a; do + lipo -create -arch arm64 $blib -arch x86_64 ${BOOST_B2_BUILD_DIR_X86_64}/stage/lib/$(basename $blib) -output lib/$(basename $blib); + done + + cp -r lib/* ${EXTERNAL_MACOS_LIB_DIR} + cp -r boost ${EXTERNAL_MACOS_INCLUDE_DIR} +} + +build_boost_arm64() { + build_boost_init_arm64 + build_boost_compile_arm64 + build_boost_install_arm64 +} + +build_boost_x86_64() { + build_boost_init_x86_64 + build_boost_compile_x86_64 + build_boost_install_x86_64 +} + +build_boost_universal() { + build_boost_init_universal + build_boost_compile_universal + build_boost_install_universal +} \ No newline at end of file diff --git a/scripts/macos/build_boost_universal.sh b/scripts/macos/build_boost_universal.sh new file mode 100755 index 000000000..0b5945aec --- /dev/null +++ b/scripts/macos/build_boost_universal.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +. ./build_boost_common.sh +build_boost_universal \ No newline at end of file diff --git a/scripts/macos/build_boost_x86_64.sh b/scripts/macos/build_boost_x86_64.sh new file mode 100755 index 000000000..a697b7027 --- /dev/null +++ b/scripts/macos/build_boost_x86_64.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +. ./build_boost_common.sh +build_boost_x86_64 \ No newline at end of file diff --git a/scripts/macos/build_expat.sh b/scripts/macos/build_expat.sh new file mode 100755 index 000000000..0c5857907 --- /dev/null +++ b/scripts/macos/build_expat.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +. ./config.sh + +EXPAT_VERSION=R_2_4_8 +EXPAT_HASH="3bab6c09bbe8bf42d84b81563ddbcf4cca4be838" +EXPAT_SRC_DIR=${EXTERNAL_MACOS_SOURCE_DIR}/libexpat + +git clone https://github.com/libexpat/libexpat.git -b ${EXPAT_VERSION} ${EXPAT_SRC_DIR} +cd $EXPAT_SRC_DIR +test `git rev-parse HEAD` = ${EXPAT_HASH} || exit 1 +cd $EXPAT_SRC_DIR/expat + +./buildconf.sh +./configure --enable-static --disable-shared --prefix=${EXTERNAL_MACOS_DIR} +make +make install \ No newline at end of file diff --git a/scripts/macos/build_haven.sh b/scripts/macos/build_haven.sh new file mode 100755 index 000000000..fb67da442 --- /dev/null +++ b/scripts/macos/build_haven.sh @@ -0,0 +1,50 @@ +#!/bin/sh + +. ./config.sh + +HAVEN_URL="https://github.com/haven-protocol-org/haven-main.git" +HAVEN_DIR_PATH="${EXTERNAL_MACOS_SOURCE_DIR}/haven" +HAVEN_VERSION=tags/v3.0.0 +BUILD_TYPE=release +PREFIX=${EXTERNAL_MACOS_DIR} +DEST_LIB_DIR=${EXTERNAL_MACOS_LIB_DIR}/haven +DEST_INCLUDE_DIR=${EXTERNAL_MACOS_INCLUDE_DIR}/haven +ARCH=`uname -m` + +echo "Cloning haven from - $HAVEN_URL to - $HAVEN_DIR_PATH" +git clone $HAVEN_URL $HAVEN_DIR_PATH +cd $HAVEN_DIR_PATH +git checkout $HAVEN_VERSION +git submodule update --init --force +mkdir -p build +cd .. + +ROOT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +if [ -z $INSTALL_PREFIX ]; then + INSTALL_PREFIX=${ROOT_DIR}/haven +fi + +mkdir -p $DEST_LIB_DIR +mkdir -p $DEST_INCLUDE_DIR + +echo "Building MACOS ${ARCH}" +export CMAKE_INCLUDE_PATH="${PREFIX}/include" +export CMAKE_LIBRARY_PATH="${PREFIX}/lib" +rm -rf haven/build > /dev/null + +mkdir -p haven/build/${BUILD_TYPE} +pushd haven/build/${BUILD_TYPE} +cmake -DARCH=${ARCH} \ + -DCMAKE_BUILD_TYPE=${BUILD_TYPE} \ + -DSTATIC=ON \ + -DBUILD_GUI_DEPS=ON \ + -DINSTALL_VENDORED_LIBUNBOUND=ON \ + -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} \ + -DUSE_DEVICE_TREZOR=OFF \ + ../.. +make -j4 && make install +find . -path ./lib -prune -o -name '*.a' -exec cp '{}' lib \; +cp -r ./lib/* $DEST_LIB_DIR +cp ../../src/wallet/api/wallet2_api.h $DEST_INCLUDE_DIR +popd + diff --git a/scripts/macos/build_monero.sh b/scripts/macos/build_monero.sh new file mode 100755 index 000000000..4dc9a9137 --- /dev/null +++ b/scripts/macos/build_monero.sh @@ -0,0 +1,54 @@ +#!/bin/sh + +. ./config.sh + +MONERO_URL="https://github.com/cake-tech/monero.git" +MONERO_DIR_PATH="${EXTERNAL_MACOS_SOURCE_DIR}/monero" +MONERO_VERSION=release-v0.18.0.0 +BUILD_TYPE=release +PREFIX=${EXTERNAL_MACOS_DIR} +DEST_LIB_DIR=${EXTERNAL_MACOS_LIB_DIR}/monero +DEST_INCLUDE_DIR=${EXTERNAL_MACOS_INCLUDE_DIR}/monero +ARCH=`uname -m` + +echo "Cloning monero from - $MONERO_URL to - $MONERO_DIR_PATH" +git clone $MONERO_URL $MONERO_DIR_PATH +cd $MONERO_DIR_PATH +git checkout $MONERO_VERSION +git submodule update --init --force +mkdir -p build +cd .. + +mkdir -p $DEST_LIB_DIR +mkdir -p $DEST_INCLUDE_DIR + +ROOT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +if [ -z $INSTALL_PREFIX ]; then + INSTALL_PREFIX=${ROOT_DIR}/monero +fi + +echo "Building MACOS ${ARCH}" +export CMAKE_INCLUDE_PATH="${PREFIX}/include" +export CMAKE_LIBRARY_PATH="${PREFIX}/lib" +rm -r monero/build > /dev/null + +if [ "${ARCH}" == "x86_64" ]; then + ARCH="x86-64" +fi + +mkdir -p monero/build/${BUILD_TYPE} +pushd monero/build/${BUILD_TYPE} +cmake -DARCH=${ARCH} \ + -DBUILD_64=ON \ + -DCMAKE_BUILD_TYPE=${BUILD_TYPE} \ + -DSTATIC=ON \ + -DBUILD_GUI_DEPS=ON \ + -DUNBOUND_INCLUDE_DIR=${EXTERNAL_MACOS_INCLUDE_DIR} \ + -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} \ + -DUSE_DEVICE_TREZOR=OFF \ + ../.. +make wallet_api -j4 +find . -path ./lib -prune -o -name '*.a' -exec cp '{}' lib \; +cp -r ./lib/* $DEST_LIB_DIR +cp ../../src/wallet/api/wallet2_api.h $DEST_INCLUDE_DIR +popd diff --git a/scripts/macos/build_monero_all.sh b/scripts/macos/build_monero_all.sh new file mode 100755 index 000000000..f7e55909b --- /dev/null +++ b/scripts/macos/build_monero_all.sh @@ -0,0 +1,20 @@ +#!/bin/sh + +ARCH=`uname -m` + +. ./config.sh + +case $ARCH in + arm64) + ./build_openssl_arm64.sh + ./build_boost_arm64.sh;; + x86_64) + ./build_openssl_x86_64.sh + ./build_boost_x86_64.sh;; +esac + +./build_zmq.sh +./build_expat.sh +./build_unbound.sh +./build_sodium.sh +./build_monero.sh \ No newline at end of file diff --git a/scripts/macos/build_openssl_arm64.sh b/scripts/macos/build_openssl_arm64.sh new file mode 100755 index 000000000..fd8d7b2f5 --- /dev/null +++ b/scripts/macos/build_openssl_arm64.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +. ./build_openssl_common.sh +build_openssl_arm64 \ No newline at end of file diff --git a/scripts/macos/build_openssl_common.sh b/scripts/macos/build_openssl_common.sh new file mode 100755 index 000000000..fd07312fa --- /dev/null +++ b/scripts/macos/build_openssl_common.sh @@ -0,0 +1,116 @@ +#!/bin/sh + +. ./config.sh + +OPEN_SSL_DIR_NAME="OpenSSL" +OPEN_SSL_x86_64_DIR_NAME="${OPEN_SSL_DIR_NAME}-x86_64" +OPEN_SSL_ARM_DIR_NAME="${OPEN_SSL_DIR_NAME}-arm" +OPEN_SSL_X86_64_DIR_PATH="${EXTERNAL_MACOS_SOURCE_DIR}/${OPEN_SSL_x86_64_DIR_NAME}" +OPEN_SSL_ARM_DIR_PATH="${EXTERNAL_MACOS_SOURCE_DIR}/${OPEN_SSL_ARM_DIR_NAME}" + +build_openssl_init_common() { + DIR=$1 + # Use 1.1.1s becasue of https://github.com/openssl/openssl/issues/18720 + OPENSSL_VERSION="1.1.1s" + + echo "============================ OpenSSL ============================" + + cd $EXTERNAL_MACOS_SOURCE_DIR + curl -O https://www.openssl.org/source/openssl-$OPENSSL_VERSION.tar.gz + tar -xvzf openssl-$OPENSSL_VERSION.tar.gz + rm -rf $DIR + rm -rf $OPEN_SSL_DIR_PATH + mv openssl-$OPENSSL_VERSION $DIR + tar -xvzf openssl-$OPENSSL_VERSION.tar.gz + mv openssl-$OPENSSL_VERSION $OPEN_SSL_ARM_DIR_NAME +} + +build_openssl_init_arm64() { + DIR=$OPEN_SSL_ARM_DIR_PATH + build_openssl_init_common ${DIR} +} + +build_openssl_init_x86_64() { + DIR=$OPEN_SSL_X86_64_DIR_PATH + build_openssl_init_common ${DIR} +} + +build_openssl_compile_common() { + ARCH=$1 + DIR="" + XARCH="" + case $ARCH in + arm64) + DIR=$OPEN_SSL_ARM_DIR_PATH + XARCH="darwin64-arm64-cc";; + x86_64) + DIR=$OPEN_SSL_X86_64_DIR_PATH + XARCH="darwin64-x86_64-cc";; + esac + + echo "Build OpenSSL for ${ARCH}" + cd $DIR + ./Configure $XARCH + make + +} + +build_openssl_compile_arm64() { + ARCH=arm64 + build_openssl_compile_common "${ARCH}" +} + +build_openssl_compile_x86_64() { + ARCH=x86_64 + build_openssl_compile_common "${ARCH}" +} + +build_openssl_install_common() { + DIR=$1 + mv ${DIR}/include/* $EXTERNAL_MACOS_INCLUDE_DIR + mv ${DIR}/libcrypto.a ${EXTERNAL_MACOS_LIB_DIR}/libcrypto.a + mv ${DIR}/libssl.a ${EXTERNAL_MACOS_LIB_DIR}/libssl.a +} + +build_openssl_install_arm64() { + build_openssl_install_common "${OPEN_SSL_ARM_DIR_PATH}" +} + +build_openssl_install_x86_64() { + build_openssl_install_common "${OPEN_SSL_X86_64_DIR_PATH}" +} + +build_openssl_install_universal() { + OPEN_SSL_DIR_PATH="${EXTERNAL_MACOS_SOURCE_DIR}/${OPEN_SSL_DIR_NAME}" + mv ${OPEN_SSL_ARM_DIR_PATH}/include/* $OPEN_SSL_DIR_PATH/include + build_openssl_install_common "${OPEN_SSL_DIR_PATH}" +} + +build_openssl_arm64() { + build_openssl_init_arm64 + build_openssl_compile_arm64 + build_openssl_install_arm64 +} + +build_openssl_x86_64() { + build_openssl_init_x86_64 + build_openssl_compile_x86_64 + build_openssl_install_x86_64 +} + +build_openssl_combine() { + OPEN_SSL_DIR_PATH="${EXTERNAL_MACOS_SOURCE_DIR}/${OPEN_SSL_DIR_NAME}" + echo "Create universal bin" + mkdir -p $OPEN_SSL_DIR_PATH/include + lipo -create ${OPEN_SSL_ARM_DIR_PATH}/libcrypto.a ${OPEN_SSL_X86_64_DIR_PATH}/libcrypto.a -output ${OPEN_SSL_DIR_PATH}/libcrypto.a + lipo -create ${OPEN_SSL_ARM_DIR_PATH}/libssl.a ${OPEN_SSL_X86_64_DIR_PATH}/libssl.a -output ${OPEN_SSL_DIR_PATH}/libssl.a +} + +build_openssl_universal() { + build_openssl_init_arm64 + build_openssl_compile_arm64 + build_openssl_init_x86_64 + build_openssl_compile_x86_64 + build_openssl_combine + build_openssl_install_universal +} \ No newline at end of file diff --git a/scripts/macos/build_openssl_universal.sh b/scripts/macos/build_openssl_universal.sh new file mode 100755 index 000000000..ba7fc5e4a --- /dev/null +++ b/scripts/macos/build_openssl_universal.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +. ./build_openssl_common.sh +build_openssl_universal \ No newline at end of file diff --git a/scripts/macos/build_openssl_x86_64.sh b/scripts/macos/build_openssl_x86_64.sh new file mode 100755 index 000000000..6ef326e8a --- /dev/null +++ b/scripts/macos/build_openssl_x86_64.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +. ./build_openssl_common.sh +build_openssl_x86_64 \ No newline at end of file diff --git a/scripts/macos/build_sodium.sh b/scripts/macos/build_sodium.sh new file mode 100755 index 000000000..b50d3c2ee --- /dev/null +++ b/scripts/macos/build_sodium.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +. ./config.sh + +SODIUM_PATH="${EXTERNAL_MACOS_SOURCE_DIR}/libsodium" +SODIUM_URL="https://github.com/jedisct1/libsodium.git" + +echo "============================ SODIUM ============================" + +echo "Cloning SODIUM from - $SODIUM_URL" +git clone $SODIUM_URL $SODIUM_PATH --branch stable +cd $SODIUM_PATH +./dist-build/osx.sh + +mv ${SODIUM_PATH}/libsodium-osx/include/* $EXTERNAL_MACOS_INCLUDE_DIR +mv ${SODIUM_PATH}/libsodium-osx/lib/* $EXTERNAL_MACOS_LIB_DIR \ No newline at end of file diff --git a/scripts/macos/build_unbound.sh b/scripts/macos/build_unbound.sh new file mode 100755 index 000000000..ed115d464 --- /dev/null +++ b/scripts/macos/build_unbound.sh @@ -0,0 +1,23 @@ +#!/bin/sh + +. ./config.sh + +UNBOUND_VERSION=release-1.16.2 +UNBOUND_HASH="cbed768b8ff9bfcf11089a5f1699b7e5707f1ea5" +UNBOUND_URL="https://www.nlnetlabs.nl/downloads/unbound/unbound-${UNBOUND_VERSION}.tar.gz" +UNBOUND_DIR_PATH="${EXTERNAL_MACOS_SOURCE_DIR}/unbound-1.16.2" + +echo "============================ Unbound ============================" +rm -rf ${UNBOUND_DIR_PATH} +git clone https://github.com/NLnetLabs/unbound.git -b ${UNBOUND_VERSION} ${UNBOUND_DIR_PATH} +cd $UNBOUND_DIR_PATH +test `git rev-parse HEAD` = ${UNBOUND_HASH} || exit 1 + +./configure --prefix="${EXTERNAL_MACOS_DIR}" \ + --with-ssl="${EXTERNAL_MACOS_DIR}" \ + --with-libexpat="${EXTERNAL_MACOS_DIR}" \ + --enable-static \ + --disable-shared \ + --disable-flto +make +make install \ No newline at end of file diff --git a/scripts/macos/build_zmq.sh b/scripts/macos/build_zmq.sh new file mode 100755 index 000000000..dd5623f06 --- /dev/null +++ b/scripts/macos/build_zmq.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +. ./config.sh + +ZMQ_PATH="${EXTERNAL_MACOS_SOURCE_DIR}/libzmq" +ZMQ_URL="https://github.com/zeromq/libzmq.git" + +echo "============================ ZMQ ============================" + +echo "Cloning ZMQ from - $ZMQ_URL" +git clone $ZMQ_URL $ZMQ_PATH +cd $ZMQ_PATH +mkdir cmake-build +cd cmake-build +cmake .. -DCMAKE_INSTALL_PREFIX="${EXTERNAL_MACOS_DIR}" +make +make install \ No newline at end of file diff --git a/scripts/macos/cakewallet.sh b/scripts/macos/cakewallet.sh new file mode 100755 index 000000000..5f591327e --- /dev/null +++ b/scripts/macos/cakewallet.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +. ./app_env.sh "cakewallet" +. ./app_config.sh \ No newline at end of file diff --git a/scripts/macos/config.sh b/scripts/macos/config.sh new file mode 100755 index 000000000..493aaa6c3 --- /dev/null +++ b/scripts/macos/config.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +export MACOS_SCRIPTS_DIR=`pwd` +export CW_ROOT=${MACOS_SCRIPTS_DIR}/../.. +export EXTERNAL_DIR=${CW_ROOT}/cw_shared_external/ios/External +export EXTERNAL_MACOS_DIR=${EXTERNAL_DIR}/macos +export EXTERNAL_MACOS_SOURCE_DIR=${EXTERNAL_MACOS_DIR}/sources +export EXTERNAL_MACOS_LIB_DIR=${EXTERNAL_MACOS_DIR}/lib +export EXTERNAL_MACOS_INCLUDE_DIR=${EXTERNAL_MACOS_DIR}/include + +mkdir -p $EXTERNAL_MACOS_LIB_DIR +mkdir -p $EXTERNAL_MACOS_INCLUDE_DIR +mkdir -p $EXTERNAL_MACOS_SOURCE_DIR \ No newline at end of file diff --git a/scripts/macos/gen_arm64.sh b/scripts/macos/gen_arm64.sh new file mode 100755 index 000000000..ca604bb43 --- /dev/null +++ b/scripts/macos/gen_arm64.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +. ./gen_common.sh + +gen "arm64" \ No newline at end of file diff --git a/scripts/macos/gen_common.sh b/scripts/macos/gen_common.sh new file mode 100755 index 000000000..62f4effab --- /dev/null +++ b/scripts/macos/gen_common.sh @@ -0,0 +1,31 @@ +#!/bin/sh + +gen_podspec() { + ARCH=$1 + CW_PLUGIN_DIR="`pwd`/../../cw_monero/macos" + BASE_FILENAME="cw_monero_base.podspec" + BASE_FILE_PATH="${CW_PLUGIN_DIR}/${BASE_FILENAME}" + DEFAULT_FILENAME="cw_monero.podspec" + DEFAULT_FILE_PATH="${CW_PLUGIN_DIR}/${DEFAULT_FILENAME}" + rm -f $DEFAULT_FILE_PATH + cp $BASE_FILE_PATH $DEFAULT_FILE_PATH + sed -i '' "s/#___VALID_ARCHS___#/${ARCH}/g" $DEFAULT_FILE_PATH +} + +gen_project() { + ARCH=$1 + CW_DIR="`pwd`/../../macos/Runner.xcodeproj" + BASE_FILENAME="project_base.pbxproj" + BASE_FILE_PATH="${CW_DIR}/${BASE_FILENAME}" + DEFAULT_FILENAME="project.pbxproj" + DEFAULT_FILE_PATH="${CW_DIR}/${DEFAULT_FILENAME}" + rm -f $DEFAULT_FILE_PATH + cp $BASE_FILE_PATH $DEFAULT_FILE_PATH + sed -i '' "s/ARCHS =.*/ARCHS = ${ARCH};/g" $DEFAULT_FILE_PATH +} + +gen() { + ARCH=$1 + gen_podspec "${ARCH}" + gen_project "${ARCH}" +} \ No newline at end of file diff --git a/scripts/macos/gen_universal.sh b/scripts/macos/gen_universal.sh new file mode 100755 index 000000000..6056053a5 --- /dev/null +++ b/scripts/macos/gen_universal.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +. ./gen_common.sh + +gen "arm64, x86_64" \ No newline at end of file diff --git a/scripts/macos/gen_x86_64.sh b/scripts/macos/gen_x86_64.sh new file mode 100755 index 000000000..c6988d8f0 --- /dev/null +++ b/scripts/macos/gen_x86_64.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +. ./gen_common.sh + +gen "x86_64" \ No newline at end of file diff --git a/scripts/macos/setup.sh b/scripts/macos/setup.sh new file mode 100755 index 000000000..fea2a6c8d --- /dev/null +++ b/scripts/macos/setup.sh @@ -0,0 +1,40 @@ +#!/bin/sh + +. ./config.sh + +cd $EXTERNAL_MACOS_LIB_DIR + + +# LIBRANDOMX_PATH=${EXTERNAL_MACOS_LIB_DIR}/monero/librandomx.a + +# if [ -f "$LIBRANDOMX_PATH" ]; then +# cp $LIBRANDOMX_PATH ./haven +# fi + +libtool -static -o libboost.a ./libboost_*.a +libtool -static -o libmonero.a ./monero/*.a + +# CW_HAVEN_EXTERNAL_LIB=../../../../../cw_haven/macos/External/macos/lib +# CW_HAVEN_EXTERNAL_INCLUDE=../../../../../cw_haven/macos/External/macos/include +CW_MONERO_EXTERNAL_LIB=../../../../../cw_monero/macos/External/macos/lib +CW_MONERO_EXTERNAL_INCLUDE=../../../../../cw_monero/macos/External/macos/include + +# mkdir -p $CW_HAVEN_EXTERNAL_INCLUDE +mkdir -p $CW_MONERO_EXTERNAL_INCLUDE +# mkdir -p $CW_HAVEN_EXTERNAL_LIB +mkdir -p $CW_MONERO_EXTERNAL_LIB + +# ln ./libboost.a ${CW_HAVEN_EXTERNAL_LIB}/libboost.a +# ln ./libcrypto.a ${CW_HAVEN_EXTERNAL_LIB}/libcrypto.a +# ln ./libssl.a ${CW_HAVEN_EXTERNAL_LIB}/libssl.a +# ln ./libsodium.a ${CW_HAVEN_EXTERNAL_LIB}/libsodium.a +# cp ./libhaven.a $CW_HAVEN_EXTERNAL_LIB +# cp ../include/haven/* $CW_HAVEN_EXTERNAL_INCLUDE + +ln ./libboost.a ${CW_MONERO_EXTERNAL_LIB}/libboost.a +ln ./libcrypto.a ${CW_MONERO_EXTERNAL_LIB}/libcrypto.a +ln ./libssl.a ${CW_MONERO_EXTERNAL_LIB}/libssl.a +ln ./libsodium.a ${CW_MONERO_EXTERNAL_LIB}/libsodium.a +ln ./libunbound.a ${CW_MONERO_EXTERNAL_LIB}/libunbound.a +cp ./libmonero.a $CW_MONERO_EXTERNAL_LIB +cp ../include/monero/* $CW_MONERO_EXTERNAL_INCLUDE \ No newline at end of file diff --git a/tool/utils/secret_key.dart b/tool/utils/secret_key.dart index e4e59819f..621ab1cfc 100644 --- a/tool/utils/secret_key.dart +++ b/tool/utils/secret_key.dart @@ -13,6 +13,7 @@ class SecretKey { SecretKey('backupSalt', () => hex.encode(encrypt.Key.fromSecureRandom(8).bytes)), SecretKey('backupKeychainSalt', () => hex.encode(encrypt.Key.fromSecureRandom(12).bytes)), SecretKey('changeNowApiKey', () => ''), + SecretKey('changeNowApiKeyDesktop', () => ''), SecretKey('wyreSecretKey', () => ''), SecretKey('wyreApiKey', () => ''), SecretKey('wyreAccountId', () => ''), @@ -21,6 +22,7 @@ class SecretKey { SecretKey('sideShiftAffiliateId', () => ''), SecretKey('sideShiftApiKey', () => ''), SecretKey('simpleSwapApiKey', () => ''), + SecretKey('simpleSwapApiKeyDesktop', () => ''), SecretKey('anypayToken', () => ''), SecretKey('onramperApiKey', () => ''), SecretKey('ioniaClientId', () => ''),