From 1ab359240720c406f06ce68878bbe1468852af38 Mon Sep 17 00:00:00 2001 From: CryptoFish <13164589+KingGorrin@users.noreply.github.com> Date: Tue, 2 Apr 2024 08:55:42 +0200 Subject: [PATCH] Release v0.2.0 (#102) * Release v0.1.0 (#34) * Escape metadata string variables * Fix linux metadata script * Special char test ~!@#$%^&*()_+`-=:";'\|[]{}<>?,./ * Change metadata script with sed cmd * Special char test ~`!@#$%^&*()_+-={}[]:;"'<>?,./|\ * Fix metadata ps script function on top * Special char test ~`!@#$%^&*()_+-={}[]:;"'<>?,./|\ * Fix metadata scripts using raw strings * Char test `~!@#$%^&*\t()_+-={}[]:";'|\<>?,./ * Fix metadata macos script * Char test `~!@#$%^&*\t()_+-={}[]:";'|\<>?,./ * Char test `~!@#$%^&*\t()_+-={}[]:";'|\<>?,./ * Char test `~!@#$%^&*\t()_+-={}[]:";'|\<>?,./ * Char test `~!@#$%^&*\t()_+-={}[]:";'|\<>?,./ * Char test `~!@#$%^&*\t()_+-={}[]:";'|\<>?,./ * Fix pubspec * Restore pubspec * Fix failing build and release workflow (#7) The build and release workflow uses the latest Flutter SDK version that is available on the stable channel. The current codebase is not compatible with Flutter SDK versions 3.10.0 and newer, so the Flutter version must be constrained in the build and release workflow. * [cicd] Release on tags * [cicd] Fix tag releases lib-builder * Updated native libraries * [cicd] Make lib-builder only trigger manually * [cicd] Fix SYRIUS_VERSION * Updated native libraries * Fix realtime statistics refresh (#9) * Implement P2P swaps for ZTS tokens (#11) * Set state when beneficiary address changes * Fix realtime statistics refresh (2) (#17) * Adjust P2P swap expiration times (#21) * Update P2P swap card descriptions (#20) * Enable P2P swap UI on mainnet (#22) * Add warning banner onto the P2P swap page (#23) * Check for local time discrepancy when entering the P2P swap tab (#25) * Add hash back to auto-receive tx queue on all exceptions (#24) * Enable wakelock and disable auto-lock when incoming swap is active (#19) * Fix auto receive queue logic (#27) * Filter on all owned projects (#10) * Update README.md badges for hypercore-one Signed-off-by: georgezgeorgez <92196048+georgezgeorgez@users.noreply.github.com> * Make P2P swap related duration constants global and improve the expiration times configuration (#29) * Call setState when updating the swap completed text (#30) * Improve the logic of the auto unlock worker (#32) * Prevent the same hash from being added twice into the auto receive tx pool (#31) * Prevent the same hash from being added twice into the auto receive tx pool * Add comment to addHash function * Integrate changes from master (#36) * Feat: Add deep linking - syrius:// * Update main_app_container.dart * fix: MacOS deep link support * Feat: Add UI for Wallet Connect * feat: Enable pairing with dApps through WC * chore: Add namespaces and more listeners * Replace bodyText1 with bodyLarge * fix: Change WC initialization * chore: Update gitignore * chore: Update repo after gitignore update * feat: Add method that activates the pairing * feat: Add feature that captures a part of the screen and listens to the clipboard * feat: Build table that shows the current pairings * feat: Add feature to scan the QR code from the screen * fix: Remove Expanded widget from Pairings card widget tree * feat: Listens to values in the clipboard in order to check for WC URI * fix: Make access check for MacOS before scanning screen * feat: Redirect to WC tab when a WC URI is detected * fix: Rebuild WC URI input field when another URI is detected in the clipboard * Updated native libraries * fix: Recreate the windows folder * feat: Add code that opens the app through deep linking * refactor: Change API call when activating a pairing * feat: Add possibility to deactivate a WC pair * feat: Redirect to WC tab when opening the wallet through WC deep linking * fix: Error initializing the WC uri input field * feat: Improve validating the WC URI * feat: Add handler for znn_net_info WC method * feat: Add handler for znn_sign WC method * refactor: Create function for signing with the key pair from the wallet file * feat: Add handler for WC znn_send method * feat: Change name of znn_net_info method to znn_info and refactor sending a block * fix: Restyle the Topic column from the WC pairings table * feat: Send back an account block to the dApp after executing the znn_send method * feat: Send events to the dApp when the address or chain id were changed * feat: Send error to the dApp when user rejects the transfer * fix: Change the error type send to the dApp * feat: Add confirmation dialogs before making an action sent from the dApp * refactor * feat: Fix dialog when dApp wants to send a tx * fix: Reset the URI value from the change notifier once it's used * fix: Don't redirect to WC when detecting a WC URI if the wallet is locked * fix: Show WC URI detected notification if the user is already on the WC tab * feat: Throw an error when trying to trigger an action through WC and wallet is locked * feat: Reconnect to websocket when WC connection falls * feat: Add methods to retrieve the active sessions and all sessions * feat: Get the reference for getting the ZNN SDK * feat: Change position of the WC Connect button and disable it in case the input is not valid * refactor: Delete unused method * feat: Send notification when user cancels the QR scanning * feat: Keep the WC URI in memory when wallet is locked * feat: Update WalletConnectPairingCard with the needed permissions * feat: Update files * feat: Wrap up WC * fix: Clean up * feat: Get WC project id from environment variable * fix: Rebuild 'Connect' button when the URI input field is also rebuild * feat: Rebuild pubspec.lock * feat: Change the source of the ZNN SDK * build: referenced wallet-connect sdk branch * build: added required macos entitlements * refactor: small refactor * docs: updated README * cicd: retrieve WALLET_CONNECT_PROJECT_ID * cicd: added environment name * cicd: updated flutter-action * cicd: set linux directory permissions * feat: added BigInt support * fix: referenced latest SDK changes * fix: desktop window * fix: windows desktop window * fix: bigInt decimal conversions * chore: updated dependencies * fix: tokenSymbol widget * fix: minor UI fixes * cicd: hardcoded flutter version for compatibility * feat: Change caching the active sessions for a pairing * fix: allow any ZTS in znn_send * feat: added WalletConnect events * fix: remove relayClient reconnect * feat: Add widget for pairing session data * feat: Tweak WC pairing flow * fix: Emit the session event (address and chain id events) to each dApp with an active pair and session * fix: add windows directory * chore: update walletconnect_flutter_v2 to v2.0.4 * fix: BigInt fixes * feat: Regenerate windows folder * feat: Regenerate windows folder * feat: Refresh WC URI input field after pairing * feat: error notification for auto-receive * chore: updated walletconnect_flutter_v2 to v2.0.6 * feat: Build WC uri card * feat: Build WC QR card * feat: Build WC camera card * feat: Put the pairings and sessions widgets side by side * feat: Change button name * fix: flutter windows directory * chore: update uni_links_desktop to v0.1.6 * fix: windows deep link handling * feat: added notification for incoming links * feat: Refresh pairing and session lists when connecting to dApp * feat: Align pairing and session list widgets on top of each other * refactor: Add the getter pairings to the WalletConnectService class * feat: replaced uni_links with app_links * fix: Revert changes to the pairing list widget * fix: Make InfiniteScrollBloc handle case when the data request is not paginated * fix: Don't dispose the pairings and sessions bloc * fix: Also refresh sessions list after deleting a pairing * fix: Check for broken WC URI * fix: added refresh for onPairingCreate * fix: Redirect to WC tab when an WC URI is detected while the wallet is locked * fix: Don't cache results for blocs that don't handle paginated results * fix: Decode WC URI before parsing * feat: Upgrade project to Dart 3.0.1 * feat: Delete button that disconnects the WC session with the dApp * feat: Throw error if trying to send tx from dApp while wallet is locked * cicd: upgraded flutter to v3.10.2 * chore: update texts * feat: Update WC dependency to 2.0.8 * fix: parse WalletConnect URI * fix: Dart lint warnings * feat: bump version to v0.0.7 * fix: Windows deep link * Updated native libraries * feat: Hard code chainId to 1 for WC * fix: WalletConnect improvements * fix: When to mark last page of the infinite scroll bloc * fix: dialog navigator pop * fix: added marquee for token balance * feat: logging to disk * refactor: renamed votePillar to delegateToPillar * fix: WalletConnect issues * feat: programmatically display WalletConnect tab * feat: added deep link actions * feat: update defaultValue for kWcProjectId * chore: updated walletconnect_flutter_v2 to v2.0.12 * chore: update dependencies * feat: added camera QR scanning for MacOS * fix: WakelockPlus Linux support * fix: replaced logger with Logger * chore: added descriptions and code cleanup * fix: wallet_connect_tab_child import * fix: minimum amount to send * fix: refresh WalletConnectSessionsBloc * fix: remainingZnnBudget decimals * fix: onSessionDelete refresh * Integrate changes from master with P2P swaps --------- Co-authored-by: mik3mast3rs Co-authored-by: Alien Coder <118136375+alienc0der@users.noreply.github.com> Co-authored-by: Github Actions Co-authored-by: Interstellar Traveler <140100591+interstellartraveler77@users.noreply.github.com> * Verify the uniqueness of the hashlock when joining a swap (#37) * Add option to delete swap history (#38) * Remove P2P swap warning banner and add a warning modal (#41) * Add deposit recovery feature (#40) * Add width constraints to shown amounts and token symbols (#39) * Add width constraints to shown amounts and token symbols * Decrease P2P swap list item's max text width to prevent overflow * Refactor swap expiration warnings and increase counter HTLC expiration time (#42) Signed-off-by: vilkris <90819192+vilkris4@users.noreply.github.com> * Fix workflow file issues caused by upstream changes (#44) * Remove bridge tab and related widgets (#43) * Add link to P2P swap tutorial (#45) * Update pubspec.lock to latest ZNN SDK version * Update version numbers and Git commit date format (#46) * Fix the extractDecimals extension method to handle zero decimals (#47) * Fix ExchangeRateWidget's rate calculation (#48) * Add documentation for how P2P swaps work (#49) * Add documentation for how P2P swaps work * Add a note of the used hash function to the documentation * fix: enabled transfers for minimum zts amount * Fix typo in bullet point card (#53) * Merge master branch into develop (#54) * Feat: Add deep linking - syrius:// * Update main_app_container.dart * fix: MacOS deep link support * Feat: Add UI for Wallet Connect * feat: Enable pairing with dApps through WC * chore: Add namespaces and more listeners * Replace bodyText1 with bodyLarge * fix: Change WC initialization * chore: Update gitignore * chore: Update repo after gitignore update * feat: Add method that activates the pairing * feat: Add feature that captures a part of the screen and listens to the clipboard * feat: Build table that shows the current pairings * feat: Add feature to scan the QR code from the screen * fix: Remove Expanded widget from Pairings card widget tree * feat: Listens to values in the clipboard in order to check for WC URI * fix: Make access check for MacOS before scanning screen * feat: Redirect to WC tab when a WC URI is detected * fix: Rebuild WC URI input field when another URI is detected in the clipboard * Updated native libraries * fix: Recreate the windows folder * feat: Add code that opens the app through deep linking * refactor: Change API call when activating a pairing * feat: Add possibility to deactivate a WC pair * feat: Redirect to WC tab when opening the wallet through WC deep linking * fix: Error initializing the WC uri input field * feat: Improve validating the WC URI * feat: Add handler for znn_net_info WC method * feat: Add handler for znn_sign WC method * refactor: Create function for signing with the key pair from the wallet file * feat: Add handler for WC znn_send method * feat: Change name of znn_net_info method to znn_info and refactor sending a block * fix: Restyle the Topic column from the WC pairings table * feat: Send back an account block to the dApp after executing the znn_send method * feat: Send events to the dApp when the address or chain id were changed * feat: Send error to the dApp when user rejects the transfer * fix: Change the error type send to the dApp * feat: Add confirmation dialogs before making an action sent from the dApp * refactor * feat: Fix dialog when dApp wants to send a tx * fix: Reset the URI value from the change notifier once it's used * fix: Don't redirect to WC when detecting a WC URI if the wallet is locked * fix: Show WC URI detected notification if the user is already on the WC tab * feat: Throw an error when trying to trigger an action through WC and wallet is locked * feat: Reconnect to websocket when WC connection falls * feat: Add methods to retrieve the active sessions and all sessions * feat: Get the reference for getting the ZNN SDK * feat: Change position of the WC Connect button and disable it in case the input is not valid * refactor: Delete unused method * feat: Send notification when user cancels the QR scanning * feat: Keep the WC URI in memory when wallet is locked * feat: Update WalletConnectPairingCard with the needed permissions * feat: Update files * feat: Wrap up WC * fix: Clean up * feat: Get WC project id from environment variable * fix: Rebuild 'Connect' button when the URI input field is also rebuild * feat: Rebuild pubspec.lock * feat: Change the source of the ZNN SDK * build: referenced wallet-connect sdk branch * build: added required macos entitlements * refactor: small refactor * docs: updated README * cicd: retrieve WALLET_CONNECT_PROJECT_ID * cicd: added environment name * cicd: updated flutter-action * cicd: set linux directory permissions * feat: added BigInt support * fix: referenced latest SDK changes * fix: desktop window * fix: windows desktop window * fix: bigInt decimal conversions * chore: updated dependencies * fix: tokenSymbol widget * fix: minor UI fixes * cicd: hardcoded flutter version for compatibility * feat: Change caching the active sessions for a pairing * fix: allow any ZTS in znn_send * feat: added WalletConnect events * fix: remove relayClient reconnect * feat: Add widget for pairing session data * feat: Tweak WC pairing flow * fix: Emit the session event (address and chain id events) to each dApp with an active pair and session * fix: add windows directory * chore: update walletconnect_flutter_v2 to v2.0.4 * fix: BigInt fixes * feat: Regenerate windows folder * feat: Regenerate windows folder * feat: Refresh WC URI input field after pairing * feat: error notification for auto-receive * chore: updated walletconnect_flutter_v2 to v2.0.6 * feat: Build WC uri card * feat: Build WC QR card * feat: Build WC camera card * feat: Put the pairings and sessions widgets side by side * feat: Change button name * fix: flutter windows directory * chore: update uni_links_desktop to v0.1.6 * fix: windows deep link handling * feat: added notification for incoming links * feat: Refresh pairing and session lists when connecting to dApp * feat: Align pairing and session list widgets on top of each other * refactor: Add the getter pairings to the WalletConnectService class * feat: replaced uni_links with app_links * fix: Revert changes to the pairing list widget * fix: Make InfiniteScrollBloc handle case when the data request is not paginated * fix: Don't dispose the pairings and sessions bloc * fix: Also refresh sessions list after deleting a pairing * fix: Check for broken WC URI * fix: added refresh for onPairingCreate * fix: Redirect to WC tab when an WC URI is detected while the wallet is locked * fix: Don't cache results for blocs that don't handle paginated results * fix: Decode WC URI before parsing * feat: Upgrade project to Dart 3.0.1 * feat: Delete button that disconnects the WC session with the dApp * feat: Throw error if trying to send tx from dApp while wallet is locked * cicd: upgraded flutter to v3.10.2 * chore: update texts * feat: Update WC dependency to 2.0.8 * fix: parse WalletConnect URI * fix: Dart lint warnings * feat: bump version to v0.0.7 * fix: Windows deep link * Updated native libraries * feat: Hard code chainId to 1 for WC * fix: WalletConnect improvements * fix: When to mark last page of the infinite scroll bloc * fix: dialog navigator pop * fix: added marquee for token balance * feat: logging to disk * refactor: renamed votePillar to delegateToPillar * fix: WalletConnect issues * feat: programmatically display WalletConnect tab * feat: added deep link actions * feat: update defaultValue for kWcProjectId * chore: updated walletconnect_flutter_v2 to v2.0.12 * chore: update dependencies * feat: added camera QR scanning for MacOS * fix: WakelockPlus Linux support * fix: replaced logger with Logger * chore: added descriptions and code cleanup * fix: wallet_connect_tab_child import * fix: minimum amount to send * fix: refresh WalletConnectSessionsBloc * fix: remainingZnnBudget decimals * fix: onSessionDelete refresh * v0.0.7 - BigInt support & WalletConnect implementation * BigInt support & WalletConnect implementation * Embedded node update: libznn libraries will mitigate the fork at height 5094735 --------- Co-authored-by: Github Actions * fix: enabled transfers for minimum zts amount (#32) --------- Co-authored-by: mik3mast3rs Co-authored-by: Alien Coder <118136375+alienc0der@users.noreply.github.com> Co-authored-by: Github Actions Co-authored-by: Interstellar Traveler <140100591+interstellartraveler77@users.noreply.github.com> Co-authored-by: Sol Sanctum * Fix local time discrepancy warning with remote node * Fix linter warning * Update README.md badges for zenon network (#55) * Update ZNN SDK dependency to use Zenon Network repository (#56) --------- Signed-off-by: georgezgeorgez <92196048+georgezgeorgez@users.noreply.github.com> Signed-off-by: vilkris <90819192+vilkris4@users.noreply.github.com> Co-authored-by: CryptoFish Co-authored-by: georgezgeorgez <92196048+georgezgeorgez@users.noreply.github.com> Co-authored-by: Github Actions Co-authored-by: CryptoFish <13164589+KingGorrin@users.noreply.github.com> Co-authored-by: Sol Sanctum Co-authored-by: mik3mast3rs Co-authored-by: Alien Coder <118136375+alienc0der@users.noreply.github.com> Co-authored-by: Interstellar Traveler <140100591+interstellartraveler77@users.noreply.github.com> * Update libznn binaries to latest version (#35) * fix: _wcClient initialization * fix: check relay server connection * chore: update walletconnect_flutter_v2 to v2.0.15 * fix: onPairingDelete refresh pairingsBloc * chore: update dependencies Upgrade several dependencies and remove flutter_test * chore: update dependencies * feat: updated WalletConnect integration * feat: added community public nodes * feat: added badge for notifications * refactor: resyncWallet and NodeManagement * feat: separated Plasma generation from sync * fix: fl_charts * fix: duplicate nodes in NodeManagement * feat: open Syrius logs directory * chore: update dependencies * feat: switch auto-receiver on/off * Create SECURITY.md * feat: switch auto-receiver on/off * chore: remove WC_PROJECT_ID environment variable * cicd: removed wallet-connect environment * Fix auto receiver * Sync pubspec * Fix different green colors * Separate plasma from sync * Revert "Add alienc0der's v0.0.7 changes" * Reword `Send Payment` to `Send`. (#56) * Fix wrapping of revocation time (#53) * Fix FormatException when trying to fuse a large amount (#58) * Community public nodes (#49) * Update app_links and ignore incoming links on Linux (#55) * Fix receive amount formatting and QrInputTooLongException (#59) * Update charts (#52) * Position address above amount (#62) * Hardware wallet support for Ledger (#43) * Delay Embedded Node until selected (#63) * Disable autoreceive feature (#47) * Reset words when verify incorrect seed (#66) * Add auto-receive option on the onboard node-management screen (#65) * Use CircularProgressIndicator for sync state * Update znn_sdk_dart ref * Change AZ pillar owner filters (#68) * Load community nodes from assets (#69) * Add manual test cases (#70) * Update README * Check for active sentinel * chore(deps): bump archive from 3.3.7 to 3.3.8 (#73) * WalletConnect refactor (#48) * Release wallet lock after use * Use finally to release wallet lock * Restore WC_PROJECT_ID doc * Update ledger test * Add ledger test-run * Fix missing quotes * Fix missing quotes * Fix running auto-receiver (#76) * Correct ledger test run * Correct walletconnect test * Fix delete web3 cache (#78) * Add auto-receive test run * Add community-nodes test run * Remove unused import * Allow znn or qsr az donations (#84) * Restore connection on rejected chain identifier mismatch (#82) * Rearrange settings widgets (#83) * Remove type step from pillar stepper (#88) * Use wallet name to identify ledger device (#89) * Remove double error notification (#93) * Add auto-receive test run * Disconnect node on Wallet Reset (#94) * Properly await all notifications (#95) * Add community-nodes test run * Fix dashboard highlight overlap (#97) * Update znn_ledger_dart ref * Update QR child image (#98) * Fix error handling when generating new addresses (#100) * Refactor Sentinel stepper with Pillar impl. (#101) * Update ledger test run * Chore: upgrades and workflow fixes (#103) * Add walletconnect test run * Fix WalletConnect camera scanner (#104) * Add walletconnect test run * Delete web3 cache by deactivating pairings (#107) * Fix "shadows" on token cards (#109) * Apply sync check to all hosts (#112) * Refrase auto-receiver checkbox tooltip * Clean up unused AsyncMutex --------- Signed-off-by: georgezgeorgez <92196048+georgezgeorgez@users.noreply.github.com> Signed-off-by: vilkris <90819192+vilkris4@users.noreply.github.com> Co-authored-by: vilkris <90819192+vilkris4@users.noreply.github.com> Co-authored-by: georgezgeorgez <92196048+georgezgeorgez@users.noreply.github.com> Co-authored-by: Github Actions Co-authored-by: Sol Sanctum Co-authored-by: mik3mast3rs Co-authored-by: Alien Coder <118136375+alienc0der@users.noreply.github.com> Co-authored-by: Interstellar Traveler <140100591+interstellartraveler77@users.noreply.github.com> Co-authored-by: vilkris Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/syrius_builder.yml | 71 +- .github/workflows/syrius_lib_updater.yml | 4 +- README.md | 21 +- assets/community-nodes.json | 1 + assets/images/qr_code_child_image_znn.png | Bin 10050 -> 8563 bytes assets/lottie/ic_anim_plasma_generation.json | 1 + lib/blocs/accelerator/project_list_bloc.dart | 31 +- lib/blocs/auto_receive_tx_worker.dart | 95 ++- lib/blocs/auto_unlock_htlc_worker.dart | 7 +- lib/blocs/blocs.dart | 5 +- lib/blocs/decrypt_key_store_bloc.dart | 16 - lib/blocs/decrypt_wallet_file_bloc.dart | 15 + lib/blocs/hide_widget_status_bloc.dart | 4 +- lib/blocs/key_store_file_bloc.dart | 25 + lib/blocs/key_store_path_bloc.dart | 21 - lib/blocs/ledger_wallet_file_bloc.dart | 24 + lib/blocs/node_sync_status_bloc.dart | 3 - lib/blocs/notifications_bloc.dart | 10 +- .../htlc_swap/complete_htlc_swap_bloc.dart | 5 +- .../htlc_swap/join_htlc_swap_bloc.dart | 8 +- .../reclaim_htlc_swap_funds_bloc.dart | 8 +- .../htlc_swap/start_htlc_swap_bloc.dart | 6 +- .../sentinels/sentinel_qsr_info_bloc.dart | 15 +- .../transfer/pending_transactions_bloc.dart | 17 + .../transfer/receive_transaction_bloc.dart | 17 + lib/blocs/transfer/send_payment_bloc.dart | 8 +- lib/blocs/wallet_connect/chains/i_chain.dart | 5 + .../wallet_connect/chains/nom_service.dart | 316 +++++++++ .../wallet_connect_pairings_bloc.dart | 4 +- .../wallet_connect_sessions_bloc.dart | 6 +- lib/main.dart | 40 +- lib/model/database/notification_type.dart | 3 + lib/model/database/notification_type.g.dart | 5 + lib/model/database/wallet_notification.dart | 3 + lib/model/model.dart | 1 + lib/model/sentinels_qsr_info.dart | 9 + .../change_wallet_password_screen.dart | 44 +- lib/screens/dump_mnemonic_screen.dart | 9 +- .../export/export_wallet_password_screen.dart | 12 +- lib/screens/node_management_screen.dart | 137 +++- .../onboarding/access_wallet_screen.dart | 9 +- .../onboarding/create_key_store_screen.dart | 9 +- .../onboarding/create_ledger_screen.dart | 82 +++ .../hardware_wallet/hardware_wallet.dart | 4 + .../hardware_wallet_device_choice_screen.dart | 334 +++++++++ .../hardware_wallet_password_screen.dart | 153 +++++ .../import_wallet_decrypt_screen.dart | 21 +- .../new_wallet_confirm_seed_screen.dart | 44 +- lib/screens/onboarding/onboarding.dart | 1 + lib/screens/splash_screen.dart | 45 +- lib/services/htlc_swaps_service.dart | 33 +- lib/services/i_web3wallet_service.dart | 26 + lib/services/wallet_connect_service.dart | 644 ------------------ lib/services/web3wallet_service.dart | 447 ++++++++++++ lib/utils/account_block_utils.dart | 80 ++- lib/utils/address_utils.dart | 125 ++-- lib/utils/clipboard_utils.dart | 4 +- lib/utils/constants.dart | 18 +- lib/utils/extensions.dart | 52 ++ lib/utils/format_utils.dart | 20 + lib/utils/functions.dart | 38 +- lib/utils/global.dart | 26 +- lib/utils/init_utils.dart | 58 +- lib/utils/keystore_utils.dart | 73 -- lib/utils/navigation_utils.dart | 2 +- lib/utils/node_utils.dart | 52 +- lib/utils/notification_utils.dart | 40 +- .../notifiers/default_address_notifier.dart | 4 +- lib/utils/utils.dart | 2 +- lib/utils/wallet_file.dart | 220 ++++++ lib/utils/wallet_utils.dart | 63 ++ lib/widgets/charts/realtime_txs_chart.dart | 2 +- lib/widgets/main_app_container.dart | 207 ++++-- .../accelerator_donation_stepper.dart | 63 +- .../accelerator_project_list_item.dart | 9 +- .../phase_creation_stepper.dart | 4 +- .../project_creation_stepper.dart | 4 +- .../accelerator_widgets/project_list.dart | 4 +- .../accelerator_widgets/projects_stats.dart | 2 +- .../update_phase_stepper.dart | 4 +- .../realtime_statistics.dart | 13 +- .../help_widgets/about_card.dart | 5 + .../modular_widgets/modular_widgets.dart | 2 +- .../create_pillar.dart | 2 +- .../pillar_collect.dart | 4 +- .../pillar_list_widget.dart} | 23 +- .../pillar_rewards.dart | 0 .../pillar_stepper_container.dart} | 163 ++--- .../pillar_update_stepper.dart | 4 +- .../pillar_widgets.dart} | 4 +- .../plasma_list/plasma_list.dart | 4 +- .../plasma_options/plasma_options.dart | 18 +- .../sentinel_widgets/create_sentinel.dart | 16 +- .../sentinel_widgets/sentinel_collect.dart | 2 +- ..._widget.dart => sentinel_list_widget.dart} | 12 +- ...r.dart => sentinel_stepper_container.dart} | 608 ++++++++--------- .../sentinel_widgets/sentinel_widgets.dart | 4 +- .../settings_widgets/addresses.dart | 4 - .../settings_widgets/backup.dart | 43 +- .../settings_widgets/display.dart | 10 +- .../settings_widgets/node_management.dart | 115 ++-- .../settings_widgets/security.dart | 51 +- .../settings_widgets/wallet_options.dart | 113 ++- .../staking_widgets/stake_collect.dart | 2 +- .../staking_list/staking_list.dart | 4 +- .../staking_options/staking_options.dart | 4 +- .../token_widgets/token_card.dart | 131 ++-- .../token_widgets/token_favorite.dart | 16 +- .../token_widgets/token_stepper.dart | 4 +- .../latest_transactions.dart | 380 +++++++++++ .../pending_transactions.dart | 304 +++++++++ .../receive/receive_large.dart | 70 +- .../receive/receive_medium.dart | 8 +- .../transfer_widgets/send/send_large.dart | 18 +- .../transfer_widgets/send/send_medium.dart | 18 +- .../transfer_widgets/transfer_widgets.dart | 3 +- .../wallet_connect_camera_card.dart | 55 +- .../wallet_connect_pairing_list_card.dart | 8 +- .../wallet_connect_qr_card.dart | 50 +- .../wallet_connect_uri_card.dart | 11 +- .../accelerator_project_details.dart | 5 +- .../buttons/material_icon_button.dart | 4 +- .../buttons/send_payment_button.dart | 4 +- .../chart/standard_chart.dart | 7 +- .../chart/standard_line_chart_bar_data.dart | 17 +- .../infinite_scroll_table.dart | 1 + .../reusable_widgets/receive_qr_image.dart | 242 ++++--- .../reusable_widgets/settings_address.dart | 10 +- .../reusable_widgets/settings_node.dart | 186 ++--- .../reusable_widgets/stepper_utils.dart | 15 +- .../tab_children_widgets/lock_tab_child.dart | 23 +- .../pillars_tab_child.dart | 2 +- .../sentinels_tab_child.dart | 2 +- .../settings_tab_child.dart | 78 ++- .../transfer_tab_child.dart | 9 +- .../wallet_connect_tab_child.dart | 2 + linux/CMakeLists.txt | 17 + macos/Runner.xcodeproj/project.pbxproj | 22 + macos/Runner/DebugProfile.entitlements | 2 + macos/Runner/Release.entitlements | 2 + pubspec.lock | 505 +++++++------- pubspec.yaml | 55 +- .../p2p-swap-2023-06-07-windows-10.csv | 11 + .../p2p-swap-2023-09-17-windows-10.csv | 55 ++ .../ledger/ledger-2024-03-08-windows-10.csv | 77 +++ .../ledger/ledger-2024-03-09-linux.csv | 77 +++ .../auto-receive-2024-03-11-windows-10.csv | 21 + .../community-nodes-2024-03-11-windows-10.csv | 23 + .../walletconnect-2024-03-10-windows-10.csv | 24 + .../auto-receive-2024-03-13-windows-11.csv | 21 + .../community-nodes-2024-03-13-windows-11.csv | 23 + .../ledger/ledger-2024-03-16-macos-m1.csv | 77 +++ .../walletconnect-2024-03-22-macos-11.csv | 24 + .../walletconnect-2024-03-25-macos-11.csv | 24 + test/widget_test.dart | 30 - tests/auto-receive/auto-receive-tests.csv | 21 + .../community-nodes/community-nodes-tests.csv | 23 + tests/ledger/ledger-tests.csv | 77 +++ tests/p2p-swap/p2p-swap-tests.csv | 57 ++ tests/walletconnect/walletconnect-tests.csv | 24 + udev/20-hw1.rules | 14 + udev/README.md | 19 + windows/CMakeLists.txt | 8 + windows/flutter/CMakeLists.txt | 7 +- 164 files changed, 5680 insertions(+), 2633 deletions(-) create mode 100644 assets/community-nodes.json create mode 100644 assets/lottie/ic_anim_plasma_generation.json delete mode 100644 lib/blocs/decrypt_key_store_bloc.dart create mode 100644 lib/blocs/decrypt_wallet_file_bloc.dart create mode 100644 lib/blocs/key_store_file_bloc.dart delete mode 100644 lib/blocs/key_store_path_bloc.dart create mode 100644 lib/blocs/ledger_wallet_file_bloc.dart create mode 100644 lib/blocs/transfer/pending_transactions_bloc.dart create mode 100644 lib/blocs/transfer/receive_transaction_bloc.dart create mode 100644 lib/blocs/wallet_connect/chains/i_chain.dart create mode 100644 lib/blocs/wallet_connect/chains/nom_service.dart create mode 100644 lib/model/sentinels_qsr_info.dart create mode 100644 lib/screens/onboarding/create_ledger_screen.dart create mode 100644 lib/screens/onboarding/hardware_wallet/hardware_wallet.dart create mode 100644 lib/screens/onboarding/hardware_wallet/hardware_wallet_device_choice_screen.dart create mode 100644 lib/screens/onboarding/hardware_wallet/hardware_wallet_password_screen.dart create mode 100644 lib/services/i_web3wallet_service.dart delete mode 100644 lib/services/wallet_connect_service.dart create mode 100644 lib/services/web3wallet_service.dart delete mode 100644 lib/utils/keystore_utils.dart create mode 100644 lib/utils/wallet_file.dart create mode 100644 lib/utils/wallet_utils.dart rename lib/widgets/modular_widgets/{pillars_widgets => pillar_widgets}/create_pillar.dart (98%) rename lib/widgets/modular_widgets/{pillars_widgets => pillar_widgets}/pillar_collect.dart (97%) rename lib/widgets/modular_widgets/{pillars_widgets/pillars_list_widget.dart => pillar_widgets/pillar_list_widget.dart} (97%) rename lib/widgets/modular_widgets/{pillars_widgets => pillar_widgets}/pillar_rewards.dart (100%) rename lib/widgets/modular_widgets/{pillars_widgets/pillars_stepper_container.dart => pillar_widgets/pillar_stepper_container.dart} (89%) rename lib/widgets/modular_widgets/{pillars_widgets => pillar_widgets}/pillar_update_stepper.dart (99%) rename lib/widgets/modular_widgets/{pillars_widgets/pillars_widgets.dart => pillar_widgets/pillar_widgets.dart} (62%) rename lib/widgets/modular_widgets/sentinel_widgets/{sentinels_list_widget.dart => sentinel_list_widget.dart} (95%) rename lib/widgets/modular_widgets/sentinel_widgets/{sentinels_stepper_container.dart => sentinel_stepper_container.dart} (58%) create mode 100644 lib/widgets/modular_widgets/transfer_widgets/latest_transactions/latest_transactions.dart create mode 100644 lib/widgets/modular_widgets/transfer_widgets/pending_transactions/pending_transactions.dart create mode 100644 test-runs/v0.1.0-rc.1/p2p-swap/p2p-swap-2023-06-07-windows-10.csv create mode 100644 test-runs/v0.1.0-rc.8/p2p-swap/p2p-swap-2023-09-17-windows-10.csv create mode 100644 test-runs/v0.2.0-rc.0/ledger/ledger-2024-03-08-windows-10.csv create mode 100644 test-runs/v0.2.0-rc.0/ledger/ledger-2024-03-09-linux.csv create mode 100644 test-runs/v0.2.0-rc.1/auto-receive/auto-receive-2024-03-11-windows-10.csv create mode 100644 test-runs/v0.2.0-rc.1/community-nodes/community-nodes-2024-03-11-windows-10.csv create mode 100644 test-runs/v0.2.0-rc.1/walletconnect/walletconnect-2024-03-10-windows-10.csv create mode 100644 test-runs/v0.2.0-rc.3/auto-receive/auto-receive-2024-03-13-windows-11.csv create mode 100644 test-runs/v0.2.0-rc.3/community-nodes/community-nodes-2024-03-13-windows-11.csv create mode 100644 test-runs/v0.2.0-rc.4/ledger/ledger-2024-03-16-macos-m1.csv create mode 100644 test-runs/v0.2.0-rc.5/walletconnect/walletconnect-2024-03-22-macos-11.csv create mode 100644 test-runs/v0.2.0-rc.6/walletconnect/walletconnect-2024-03-25-macos-11.csv delete mode 100644 test/widget_test.dart create mode 100644 tests/auto-receive/auto-receive-tests.csv create mode 100644 tests/community-nodes/community-nodes-tests.csv create mode 100644 tests/ledger/ledger-tests.csv create mode 100644 tests/p2p-swap/p2p-swap-tests.csv create mode 100644 tests/walletconnect/walletconnect-tests.csv create mode 100644 udev/20-hw1.rules create mode 100644 udev/README.md diff --git a/.github/workflows/syrius_builder.yml b/.github/workflows/syrius_builder.yml index 368959b4..8c2b2f2f 100644 --- a/.github/workflows/syrius_builder.yml +++ b/.github/workflows/syrius_builder.yml @@ -9,24 +9,28 @@ on: workflow_dispatch: env: - FLUTTER_VERSION: "3.10.x" + FLUTTER_VERSION: "3.19.x" jobs: build-macos: - env: - WALLET_CONNECT_PROJECT_ID: ${{ secrets.WC_PROJECT_ID }} runs-on: macos-12 steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 + - name: Checkout zenon-node-database repository + uses: actions/checkout@v4 + with: + repository: HyperCore-One/zenon-node-database + path: ./node-db + ref: main + - name: Copy community nodes + run: cp -r ./node-db/rpc-nodes-mainnet.json ./assets/community-nodes.json - name: Setup environment run: | brew install unzip create-dmg brew cleanup - - name: Checkout - uses: actions/checkout@v3 - name: Setup Flutter - uses: subosito/flutter-action@v2.10.0 + uses: subosito/flutter-action@v2 with: flutter-version: ${{env.FLUTTER_VERSION}} channel: "stable" @@ -35,7 +39,7 @@ jobs: - name: Build syrius desktop run: | flutter config --enable-macos-desktop - flutter build macos --dart-define=WC_PROJECT_ID=$WALLET_CONNECT_PROJECT_ID --release + flutter build macos --release - name: Package into DMG run: | create-dmg --volname syrius \ @@ -52,19 +56,26 @@ jobs: --hdiutil-verbose syrius-alphanet-macos-universal.dmg build/macos/Build/Products/Release/s\ y\ r\ i\ u\ s.app \ syrius-alphanet-macos-universal.dmg build/macos/Build/Products/Release/s\ y\ r\ i\ u\ s.app - name: Upload artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: macos-artifacts path: syrius-alphanet-macos-universal.dmg build-windows: - env: - WALLET_CONNECT_PROJECT_ID: ${{ secrets.WC_PROJECT_ID }} runs-on: windows-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 + - name: Checkout zenon-node-database repository + uses: actions/checkout@v4 + with: + repository: HyperCore-One/zenon-node-database + path: ./node-db + ref: main + - name: Copy community nodes + shell: pwsh + run: Copy-Item .\node-db\rpc-nodes-mainnet.json .\assets\community-nodes.json -Force - name: Setup Flutter - uses: subosito/flutter-action@v2.10.0 + uses: subosito/flutter-action@v2 with: flutter-version: ${{env.FLUTTER_VERSION}} channel: "stable" @@ -73,28 +84,34 @@ jobs: - name: Build syrius desktop run: | flutter config --enable-windows-desktop - flutter build windows --dart-define=WC_PROJECT_ID=$env:WALLET_CONNECT_PROJECT_ID --release + flutter build windows --release - name: Package into zip run: | - Compress-Archive -Path build\windows\runner\Release\* -DestinationPath .\syrius-alphanet-windows-amd64.zip + Compress-Archive -Path build\windows\x64\runner\Release\* -DestinationPath .\syrius-alphanet-windows-amd64.zip - name: Upload artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: windows-artifacts path: syrius-alphanet-windows-amd64.zip build-linux: - env: - WALLET_CONNECT_PROJECT_ID: ${{ secrets.WC_PROJECT_ID }} runs-on: ubuntu-20.04 steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 + - name: Checkout zenon-node-database repository + uses: actions/checkout@v4 + with: + repository: HyperCore-One/zenon-node-database + path: ./node-db + ref: main + - name: Copy community nodes + run: cp -r ./node-db/rpc-nodes-mainnet.json assets/community-nodes.json - name: Prepare environment run: | sudo apt update sudo apt install -y curl clang cmake ninja-build pkg-config libgtk-3-dev liblzma-dev unzip xz-utils zip libnotify-dev libayatana-appindicator3-dev - name: Setup Flutter - uses: subosito/flutter-action@v2.10.0 + uses: subosito/flutter-action@v2 with: flutter-version: ${{env.FLUTTER_VERSION}} channel: "stable" @@ -108,13 +125,13 @@ jobs: - name: Build syrius desktop run: | flutter config --enable-linux-desktop - flutter build linux --dart-define=WC_PROJECT_ID=$WALLET_CONNECT_PROJECT_ID --release -v + flutter build linux --release -v - name: Package zip run: | cd build/linux/x64/release/bundle zip -r ../../../../../syrius-alphanet-linux-amd64.zip * - name: Upload artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: linux-artifacts path: syrius-alphanet-linux-amd64.zip @@ -124,7 +141,7 @@ jobs: if: startsWith(github.ref, 'refs/tags/v') steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set variables run: | echo "SYRIUS_VERSION=${{ github.ref }}" >> $GITHUB_ENV @@ -152,15 +169,15 @@ jobs: - name: Prepare releases directory run: mkdir releases - name: Download macOS artifacts - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: macos-artifacts - name: Download Windows artifacts - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: windows-artifacts - name: Download Linux artifacts - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: linux-artifacts - name: Prepare releases @@ -174,7 +191,7 @@ jobs: echo $(sha256sum *) echo $(sha256sum *) >> SHA256CHECKSUMS.txt - name: Upload files to a GitHub release - uses: svenstaro/upload-release-action@2.4.1 + uses: svenstaro/upload-release-action@2.9.0 with: repo_token: ${{ secrets.GITHUB_TOKEN }} release_name: ${{ env.SYRIUS_VERSION }} diff --git a/.github/workflows/syrius_lib_updater.yml b/.github/workflows/syrius_lib_updater.yml index 31b730e5..1b5af3c6 100644 --- a/.github/workflows/syrius_lib_updater.yml +++ b/.github/workflows/syrius_lib_updater.yml @@ -12,9 +12,9 @@ jobs: sudo apt update sudo apt install -y unzip - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Download libznn - uses: robinraju/release-downloader@v1.6 + uses: robinraju/release-downloader@v1.9 with: repository: "zenon-network/go-zenon" latest: true diff --git a/README.md b/README.md index 1eaef025..46cb0e10 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,9 @@ [![Build and release syrius](https://github.com/zenon-network/syrius/actions/workflows/syrius_builder.yml/badge.svg?branch=develop)](https://github.com/zenon-network/syrius/actions/workflows/syrius_builder.yml) [![Library updater for syrius](https://github.com/zenon-network/syrius/actions/workflows/syrius_lib_updater.yml/badge.svg?branch=develop)](https://github.com/zenon-network/syrius/actions/workflows/syrius_lib_updater.yml) -Cross-platform non-custodial wallet designed for Alphanet - Network of Momentum Phase 0. +Cross-platform non-custodial wallet designed for Alphanet - Network of Momentum Phase 1. -Developed in Flutter using the [Zenon Dart SDK](https://github.com/zenon-network/znn_sdk_dart), `s y r i u s` wallet provides a simple and intuitive interface to interact with Network of Momentum Phase 0. +Developed in Flutter using the [Zenon Dart SDK](https://github.com/zenon-network/znn_sdk_dart) and [Ledger Wallet for Zenon Dart SDK](https://github.com/hypercore-one/znn_ledger_dart), `s y r i u s` wallet provides a simple and intuitive interface to interact with Network of Momentum Phase 1. ## Architecture @@ -18,24 +18,27 @@ The wallet has a modular design with native full node integration. There are thr Follow the instructions to install the [Flutter SDK](https://docs.flutter.dev/get-started/install). Check the [documentation](https://docs.flutter.dev/desktop) in order to setup the Flutter desktop environment. -Learn about the foreign function interface (ffi) [here](https://docs.flutter.dev/development/platform-integration/c-interop) that enables state-of-the art KDF - [Argon2](https://github.com/zenon-network/argon2_ffi), feeless transactions - [PoW links](https://github.com/zenon-network/znn-pow-links-cpp) and native full node integration - [Embedded Node](https://github.com/zenon-network/go-zenon). +Learn about the foreign function interface (ffi) [here](https://docs.flutter.dev/development/platform-integration/c-interop) that enables state-of-the art KDF - [Argon2](https://github.com/zenon-network/argon2_ffi), feeless transactions - [PoW links](https://github.com/zenon-network/znn-pow-links-cpp), native full node integration - [Embedded Node](https://github.com/zenon-network/go-zenon) and communication library between Ledger devices - [Ledger](https://github.com/zenon-network/ledger_ffi_rs). Dependencies: -- Flutter: `>=2.10.x` -- Dart: `>=2.16.x` +- Flutter: `>=3.19.x` +- Dart: `>=3.3.x` Currently supported ``: `windows`, `macos`, `linux` -The [WalletConnect](https://github.com/WalletConnect) integration requires setting the `WC_PROJECT_ID` environmental variable. - ```bash git clone https://github.com/zenon-network/syrius.git flutter pub get -flutter run --dart-define=WC_PROJECT_ID=walletconnect_project_id -d -flutter build --dart-define=WC_PROJECT_ID=walletconnect_project_id +flutter run -d +flutter build ``` +## Linux + +Note that on Linux you will need to install an udev rule file with your application for unprivileged users to be able to access HID devices with hidapi. +Refer to the [README](udev/) file in the udev directory for an example. + ## Contributing Please check [CONTRIBUTING](./CONTRIBUTING.md) for more details. diff --git a/assets/community-nodes.json b/assets/community-nodes.json new file mode 100644 index 00000000..0637a088 --- /dev/null +++ b/assets/community-nodes.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/assets/images/qr_code_child_image_znn.png b/assets/images/qr_code_child_image_znn.png index 60d44a8d18c8aad8a2d416c34c7d0bd26021b75c..b23a6d80a74f2a49034049ab8cdb4a55e01ca3c8 100644 GIT binary patch literal 8563 zcmeHMdstNEy8qUig)Bs!HK3qrux4>H6&UWrIKW~U5L9rsQDSM$IE+FfGXmmRv|Y$p zV3>qyUQnm~bWZH@6x(jL?J^Ok%&g66XVLC9U>8rvYA8FKu6zH!8TDzO-M&2g-;>W{ z-}rv-{d?cv_kOD`OVy&OldqUe2$@<@ZeC7^N~C}JNua##npB4+!d1SqnUKh6`j;UG z`eqUmcDBB{rlqEGslo2tkY=lOu5qNfH@Lt}h_T4+vf0-=TEsPub@hz}(&s0hmBjkm z0%=7~rLNLd;;5@Hzq!eA_03h)_M6w+^J}G|Lec0pK*0t_i%oQIXlQIUxC^A9UITtp za=Ii2Sz6W?NX68kSW{^cOPoy(F()lI)t-^97xVJdGIFzZS-A_uOkGBPx=x>-k(H{; zFz9j(IeFs6KN74p)vh%xHsg2DEmo%6i&Gx2xS4+LKQKTAeYn+=}3M6#B3MIGgL7e?#@sGl53qGL$qqY%R{FYNxZ| z(m`1+xh!TPYH{w0`o>!4=H{z@B;YXHS{wyZrYM$I{g_@+QetUxuB~stM)UGTCb6QlBsU{JH#apiEhFe|Wu>8_vAM<8 zXm?bY3nU~bt-ik2keOq*XF2qa)I6OoJ2fjmTbHWW*>Y2}vh}$RU1qLr?V7wx@@A)f z6D@&D^0koH>+-YnGS_CN+OqWe)SRqrTWWrOR%U8;_L^FIwr-7Xt^Tt8Wli;HL0iMW ztcNxxtsO&oeKSsV%f;5Y+OhHCuZDUtSS|*eowh-NWT)rvsFf~~>wh8x|6BwiwVUf4 zQ2KZ3BE;70T-&nQ*5oK&hiv`Bx=a6C&Ntgy|Gw-0+~NJT>pxki|BCAu(`v7?HLi02 zQ`03HOgcrM(4nON-}4IY{RpWpu>=0-&&#-re_TdxM0heoXw#_GGc5a2axZ2(K z={xUkTy;XT{lD%yHLj0YH@dHHyM3eSi|3#2o!gsbERt34K7B3saeLAmi!QwJM10XN zjn@0Ne*gLVzw9U8&OiO|WzqZBZdEgG~H}@!}a{9l9Iv z(>Pi@W3a;o);pu)s-?mj6Ie6GDC?34fw(K(maYkFKEskDbl*5~8)1}PL*du(>c^3K zf8JHEbKS8_3Vfh zh(SY@l~;gcI~6OwiRF|}JmGsf)vra1q&msiZ+2U5(-_E%E~@M9u`o_qjD23cI12ls zvA?X%vX?O8!tvsy!HzFrbtSbL_o&S?O)eYjSUeQ|A8v*bqv7CS$6oBu;?)mm;vY?t z$;hDz>eVfRJNNJ$Y(-abz-4aVp!ibV;whn!Z}FwAur`^vH;?&5E03mqYbK( z5X}ltIPz6fu8=@Ctd26XA@o!V74jv+ZD$vvjT@K+`LmF!X!G61@v?KUV}~caVW~nc zBo)FdnPoX|{Ah9Mn5q}@xAN+FQE@$||A@Mh`Eg2)`hsOG2LoPdl6q|J<4hP>Pokji3MhkM{Q$-g%tn;q7bu5Bqc?(K zdu|VS=Z!Nvc|n;o6h0$DU58-KhlF0>c;8`9_%&VXx+)MC!DxWQ?Yf;XBV9qM?8u^_MiYL>H39WB0QP~}Fw4MoSplv0AY=YTk-(j)F6vOpc1LPAXL1hTq)?aj}~`4-4PILWJ6?PpKh)>{LGx74Msl zXdXf|Nk-+@;O$E(>|fkQevioYRY7%w5PKMv8XYgsW!S)h3Gcj7W`R~Fg*YatT{7!l zRa;_E>SahxAapqKI2b(Pk9Dc7^I7g(Ra=mjC@T-4B|y9$#4@*KKNwSFr3&pCp%$ky zYzz8iX&d=9FL=@O$N=p)>{092u^i5t%Dnb1jc^hoOfFQS;Pnigcyye670aCrnL^LW z*A8}^8VaX9cMS0*8L#BeyoDNSiNxI?D+NJI#0l1<(c(>GD*u%XdsHf8BE<_DOvOW*- zMn(w!6c`>{#m5YC}`DRCcZFANc1 z0kJqpEH^3g1_bU2mor)Jrl`0Uv5-VXiI!axUc)HU0~td|<^zuZAjNo2c9ZGjG{h%5 z)dnG+Y< z7Vn(!szw>#5|;Z2mb8UrqL;KkIYfOT)1pNk+B|Zc@u9?CKzv#wIc`>b`jFBmc;R@+ zkUy7U(OA^_lmyv-#b|Ntm`b^q1Q!?3DZ~2%-e~w0!||)BP#kF} zRfc!MSp>7Ud-%W2Z8QPa>u->e_2KOSV!eg+pnwh`?&X3AjG%t?>>&5T*%Ub2!tuB( zao>^qlH`6F;sGKSFs8MG9q3b%#0$rw;%NfrmMT3k1nY7>>sbJm@cz^{XiYyE52||Y zDP!`vBK9wMMO2%_$H}OT6oihvZEfV(8(5DkaREG7;@`S&O`1I(ERcIT3;%q@# z59e3;!#Mw(QHJCJ)a+0;#m`0h*N*{A##uJd7a$6~)!G0CJ`TY6L!yybrF4-z+odM^ zPDk(h_r~Y8($pA#Yf~a*i%iS!15iNJY7R!;~(QxkJ zcwH~N2=o$WEp_{T5bl7ALP7K`g^Gy|Y(!%t9e1j=ZyGVisTp=CN=t_O2=gmMT!(>Z zKHRE9APRcq6Y(OB^NYb)495Ln zTm#01h-WU`UI~qJWrgg=T8_ZqqAM)TLOAkrOd3lei0kfT6;{l${W8`ltb;g|C_$8S z;CwE^-GOi$;Mz5Cb)<(d^N>>I6-+oPxO5PVv6NB77zD-z5NJGa3V}%yRtBJwY$D=5 z!u%Fl5CCa3#F-ey4#MM?FaaKc+|oo0WXOpQbKpcD^0`!$$qf+D!Q#XLa2A6z1D5D8 z{~}yDjv1uSj(k!qTn`niAn_0+TsVp2i#&wREB-=cgTiPxO3sg|#<~Dbr$b^K5*T>M zM+1mv83<2;@DwFLMIeu1g%YSJQHc3iB;zf>zn6K)Fy^Wn(1&571}28@#QJg?r(6r? z;-DZEif%=sPvMzx3(Di}WDLmmSXN#M(>F?-(geo$!1!-q{2o!9LNxhEh-DjLPNF1x z4q%1m9#}eAffbfcQVY1&!%Tdavc&r0>OR=3?wnEdYODMCDuOoPbo`%;GsA%18 zsMw0cnV}-sP4XNKOP_{!^9)`pav?>gcWPtVVN7m)&lGt0ue$^VFRu@hO|;yz6J)VS zV``N+uZCfB^e;(h8ro&AOTqo=K%BVHz7G3c!ZFlDDe&s$G|zc>HyNYh5nj7c!&*8? zJI?(rsFwg&qltH%H}f*Xa^GmQwQFP9o<7W=q5b=?|6yp~#BzeR7G2Q`U^=TcokX)I zO0t4R*gxxjRU+o_C(Z{wX+YACKoxjtE3N|rOjQDmW~8j71_p3x&w&Osa|4=wQJ0Vj zU(uDmS(4|pnfFbih_;&FkFKY^`Cv#xDX6#e+AIw_fV_Ry)LXh%;%JWo(`p^DEN#<- zkS+#zc3}4cs&+SK{TPkuT8X32!X0$Z^mUDKjOUt@?t1%`y6TH4Gb zdv-54hW2z9E93-Bpu`H1yD1^w%^cse9SlGfJa@#|8dDkcZjBb@GRbq)%nu*N%PNXW zDY%n*_OGTccvBg+S~Q(Jna0)8r&}kcMXfv^usBo^L-+B>tA^j@9@^{Pc~B~GbMfJ2 z53-e66}}kiH^FTv?dAC4?YuTY!`__`1jktrpAI-Yn|Md}d6SZf{XN0``*2~sYUZ&o z=3xJ0?3;Ot026oc+Sw-r5j_pGq^j;i$NUhoUk?b`bw23IRrvWa$_zmAHjw9G_!6b{ zJr1mSv#<89yLjet!0VmJP=ZPM6uiwL-s{nqABT8jz?%cV6HUs8_*o!%8qEB_-9XZ1 zo}Qhn`2oPe%;zg&sE@dPW-bAhXG{@6AjQ=};bI^5sS8>tSQS)2Z{U4Y0ngwLCK)i> zDOe{1OkYg%G$03~04Jlylq+#F%M@}Hg~`MKIhKVu%I9Jz&tVL4fHw6#R{S78xJXUwHtIVXVp2Xor!c{!&j*7vFvx zqA7qavq#PX`le}2F|FERObY##!Tm`TibMMwv9HFy#7%~#r%`4Dk?p_2ei0%t^V9LE z@8)BX)c$rDeGe7gDa-}nqBLWwSJ5D=a8DgK0f=$^`sbyrBz=_dSI7(%Hv)*e z0j>0<`4h;zdja`Dlu4&(B5q?GSoFBE8CIUFVd=Od9$3xGc95?z^K{Hn_FzS>lrw#` zOg3tJWIG@n7(al>>6l~9t2#z#is+m(9aT){oVjIS2%#?{ssP=S0LGn0RZSpzWXRK(wYfE zJSLd9;V zc;{?lq@M%9j&OmB!2#zP=%fQq0K-zEtPyX_3BkG^8NCT`wii*p4qOF*k^WaIV(5c`6aY^m!SIcs^)+F=d1G(b0(^0% zF9rIMkG>S}P$0u_4QNGtX&n7OIrOU49U=^Q4~nZH=l)$qjBY0ghk+z6N+<;oLBNm6 zhGG+01Y`p;5&sko`wl8qdBn`S_Cw+!S8v!VN>~U2@ euE`SD`)!fznRRMvp8h3*KNY1_=7&tyZGQ&95I}?g literal 10050 zcmeHNdo-14-`*sdM7B4P*laR29T+lGiEJXBMor~ZVw{@Pl$;ujWO8Ubgqoq6RGMZ+ zA!2HZkq*c%rZlE>F2_O&B~yQ!MpzY2iId%86e8wi}UKKxbPA?mMI_p43<%-siqFX;ZJM`kt)s%0awe2?qC_dm!ha97^@pr((-bmgBvMDyg>Tkn4$q|BA~KKvK3%h1;?97_~8g?Bp zWf;6CvFyrw65sA?SL(k%)D9W?CqD1_eXuwC!2zA&5AJy4lt{gUQEywk2cCxy+?%PQ z{qp#mry2eIt&^be(tt>v4C}t?XBYfykAx3walhabGzKRhnPC~v{C-81Bol!{ zl}K^!V_w&@Du*|t@3?f^l>gGw=MkB9NmGs2|Ga^Jp6_X;m~C^|yUegG!gA(Fc;T<{ z0?k!n?z2_2$L>4kU4H!uXRcyR?#)epl1%4Hm(*0}4g(P{>uJ~N$sL<>uld)0oEypq zsPM3`x)?!C<+{~4Xoa4hX%l;XdEO2CXr8F0!St&JFG*&`XMVb7TSHmAb5gpqwal_t zvqyg0rB(gvdeh~<9f_MGoIC!^{5pjft@rU9)#`%4xE_6dZQW{w z&tK#9UWZPZ+j^pg`MN?N=+1D6?Cttx#Ib#sv48UzGH7DixX2;0`*fhTIk;fk3IVw^ zAYznb%*FkeUu9(pgXyZ4fUk4^Mb?O5up2UbhR6-j(W`2?n$({)tZ-%Dnf~av$tL=| zQl~JxO%;q;?l>I^HJwbw9gr6hlU_xH;gu4&Ng2r3mHSMJuW5^6`OgHLs-a!8PCQ+a zcVoDmyy#@NX-02u)kE{Ocip)w4S|JmRVV8hyXRd-6V-7XRhdm0Ro1G;c8AC|v58=Y zV~pxz#1AnV|Bj~{gCw!sa9jC4!HY(j>A+;`%CuRX`;u62pITll@;GJY>o}T4@-u@3 zAt#p`Y7@;{8W^ebTmPnei8rL@HLX)lMdp0VQJq|FSQhfcWbfP#c7G+fg`Rx+(ERba zcPq9T=C4{kTgBpI&D_vK;SgGMs+(Vb%%ft&ezDy1zQ$pbM|U^^s6B}d-qJRk(Z3*gaVcjMf|tS7-_E~t%rQkCe(9~O zG94P@Y4vz+>7#BgfHVa@-M@?&+{4i~qfjYr3*4j~V&L3ye}DADo}lTyR#;9S(c6b6q2Z^c+a`B*`{W6Tv?&=*BtZZsTv^kliI zucIfW6LiWMnOd0VX_cR;s0-&I*3#p)BYbfpZca{0o$Gcs;`B!QCDc0Dq;{_ZelE0M0uWy#k|;N&BdERBEbLghZQ_p(Jtcl#tve@h)A z(XLN#M7ut~LAzjhzen8Ze7G)adf~118!_8xtDz-K6@aq?++3E54%@HV&7UqXX^c^2 z8mwH`V86Wln~4pk-}f?!H=Pm>2Am>wnwEelhtpmbrPHa!f#~(ve6lCi>^Bw>v1HCuwCW?%#`CB)lJTRIQ3uYG$eD ze<8=$0~y*>*Yl0Z9A0~aveKKI4g7%c3K!}l<5{qu(^zr_bY&6VwtK3I%IUSai z)Utl;5pAbPt0bka4AEYKtvs6Vb<7OGa zo4J*%@%LHH6E85`Yd4F10#ipGb>H%@6>_j5ll=zUO;M@-er7(>^#>{6IWIe$(ExYK zj-aHYm>e@D)OsaD==tB;my9Cv<1FjraCJ@+DQ+KG zd$g|S_c3}rYhzDQ)&YniW^7&~=AvkkhP06#{k^5{p^Xi~E7N+8>ME)T0vL?ueY@}- zQ6;q_M3ma}i?x*J`l75v=}ZzLS+^S7SEzFnrbXz=s8jJZa%%dUhWc+7tQu#Q?Lb8i z^~@>Op|l0VBgH3+V|WAK)1on{YDZnxa`xO^{o-*XM~Mc-12(0{RxQPb2b6s9M~*nv z=gmiD5w#Pcx0?Ra1!2i>{EJSieWK`dx2XNxH93n&SWZ@>XS}$=KX+C~kFV=lIYJjM zcvd-r7l2W@|7|&CPk{j0!`a@}1(jYeYOOWo&W=pRG_YkK+8CagW`4s=I=Sn!ZL(qP zVcw`i<<Bb17vLhbr-niS4^5D2{w1C3nn_q z;o5Vify}T%^rloUyqC&);C}ziuezb-87BA`4(=NosKC>6M`SM#k_}vtb!=U96iBX7 zQRO;nZ`}-51sfUdD$WO21nl(n;xg-cHKJF4j0z%qz?aMr>uI?WS7?@E)T3S&*^6n3 zk`{=kZ?8HYM}>J0v*3vSoD}lpAJQ|KQADc!`ml;fZWO_E%To{=W^L<7f?LZ2q$qk* zYnQCf-=rAl$z5<`hA&U6tS=E+(Y%J5cWv5Kx`}IEbflX83N^j>ngKoy5crn)35LI@ z-qV;u*xQ-2DA}3GIRs-Tum3wd;e`$JM0C8^Ktsg2vn_Z~?_chH^659-a=fz`09?_`88hX60 zi+Cc6@-1A}cVnM`oJ(pyCrrN(9p2lb0D6Ux>x0t$ zW>$I6>qaf!&>6cKy)|rfiagC18C6EMPj!dkyy&Sj7R6ecoK;4MDXHm~yWW`M=E5$D_ZqO;r!0qJ zo@SKqwU_;L*@({^CO=s<-7pw8$k)xcolUL*J&KyZs z+@9`M3O8pLRUgW8DN#S{V-rHrBf}YCG4&-JLy>`LdDSEioSp zlZY2}l}f0FdSSNr_th@v^D>AaoP#EQ<6nXik#an9Qg| z=JFHSYYFe;oXpx=M!u4hPa}%38e*#1vz8^2G1slYR|NqCQt=?=X5h;jGOPF`uU|c= zX%dOyTRejWSa*oT!buYLq!s^$UwV7(LFM8g2C$j?SNxz&EcS}UlEXgah_sgDc9Nuq z5JnttL31ug=syC_nM^EB`;ui>l*|P$I3#jO02@exN_WYb!>mCHQ&3+_i2zmEM1Y`7 zBH+CRB?6wcm+c_W$SXwK&6O^kqe5tgzY) znnOB9bDT#?!BG@eWPU#wWBt*W#cAh1C&xnuiiDQt2ujQz~Ia`dI5&JOA#Xi|)V!ynNp;X~6sW-CCXAO<&9^TB+ zU;I8?$^wGR8K*1&O3wJQN=pfaB*_xr()Sg6GXoTYg?%iajD1G72mWA?OUx(ypyeGsDzxrN(Cu zo;q8*>vViC%SZcp$C9(1{GMj>wP`>qCvUDLe30>Ad&|L7jxmZ552f(1uxIIw0%5D| z+8yi?ZDC!{qo9G$@L~;7Pb>K9E=7%tB$!TZfLTGQcCEN#@%)d<@j+dP{Gd2+3>=G#-p0*MJy-FTRPmk(pK=n!I^!h% z1J9!d{(xN_N^77UCoEVMyZCQFiVE7#1Z$fksv(QE>Q0~T2#XDfEqzh+JVW2f9qChZ zBmDoMbOi42_+}<&#cAKwWtp$fJK9b>y2fC*6aJ$rg`TKIr{Y?#&lj8O<6Nb-Z~ z&M+%0=iZWC_ET7Z-Ac34Lyarar>^v7+@|6n=@=`7-**+Vh<@dhlOUHbwJs3P*HGiIr1s|O1`*>nFOR?L~}du4=1dd>wf7+ z4X)bA$)6Y}EYz)r#1l~ILh}x@1o>vWL!F*o@~=(1%39e7_VY_Khwx;N8bgO!+Bu?P zeuAG9ycp;^80a2LvSqlQ6l6aDDwf%f?UKU0VeOzclH1Sdwc*wy+j-1mEQI!m;tS~j z%~i3~=mBoLL3cz%UVErflJnhnu{}?6G%r|~?|Ur~uUXY_|< zHuUwp{!NGZ+Sp<>QS?mttQfU~^DnfP&d{_a`SBX9n+dS$fFN!2Mk;!^)FxBz*vHH% zs*m{3TKKjB~0hV;_=6_Yy6JarJ;I+Hh2R;$~E|W`Kt}Zl*ufrS6+P^6#-lIHF zVkb#I%q$iLA+z%EwjjD+Uft7Hm4V;Mz$fyl5FFb#$8z#PT{0Rgl}|och){a0Z~IG8 z*@2C4FbC6MLcVXMI|XkX(dC7x$+R1bSOUxud{}lDPjNeO&Rq$$(L$V7-`E>+o9`*2 zvI_E@O1C_hoJF*$3r#hHo=swa$O3LSO^p~1anv0uV%|>AkXh$kZ#bP{F>j(!+@p*t zGEECLhd}TR@N>cT#)V^hQJCQqM}uba9l}7yqm|M|3$-8X&Gm$@b36*N>$XwVfgw#& zt3K_P|I%43?+Lcvf(fm*2Qn2^{!ToeO4WI8ih_J$VjZ@(p8Wl`0S!S6ySuY|`iW|jhxe-V2aicDD{LRqUnBYm<143f$Y-$e(q0Jb$Z!sQzrL=Q7GNi^a z=CTaNbA1eM&E`k$N)=|1&CP%;SJ5zs(ypo228-(i0_UCMid}!a3&F@!emM8%elg^T0&d=MToihx_& zzO1hc*sqvAdn&&N88U%Ij#IXj2IrSZFe;K#Za}id&5~nS9 z(=*Q8uc+}m!UGzz7q>V)Bg3pJ$Zcd2`k2q$jQYBxw~oI-@_O zkJm8Q4lC`=u-;vnBQ3zEmB=*V@n z(u@cAIg&+DKh4(BF(y$Z1yzJMXeT+<*&C7@fwsl_DNZUDGo;x`h9X`yo3o!;SIy35 zCZ8#GDZoPe6Nmx8I-Q{@YMFuWeUBy9vsCDon~-ijS~wOt6a76r*xPX7mOnFIiqjY= zXqr#s3k!2*eCiu+rHd*dS}CsRXbY4Mv)@Z;(|HT{JsVbCfBldJ6gR&HEs@<5Td^7C zBG5<~=ZO}^mTu$+1!==7&&;6t&P&Qv`tqmk1jRZZ(U#5JE%f6p(q&A|lkBe}u4x(u zRVXEJMRbKyg3}qG0arPB-bZ}1fP(CUnTm8PPYt)agQYfUWOLBxYOgSeF@e?7pJoy( z*7P`jPv>X=q(hDD9IFrZRR>5N)f}3XCL<0J-nj`~hj1tG&DabjEfIs%|0hZTqL$_R z;zujq{DidSoURqrpSH@J@7x>ZIuzbvPEqC?Cz)HALqAZ6yki$vAFSqVEZ`TNJurme zgFRmgzKXBV0pTPVoba2E+jaa@$J3w2PS#{j-_W*fuz>~mQ>=7~9tI8I7=$R!g%Jc5 z&W(D+ohvDXTWJYw({rSg2?U{7+3jhEmW-M~Nm_b9Koc|l@(Mk(xswv;Bs6k%$1RuM z`xYu>0eQ&w5UROI59ydk_r@_r+qfdfgTDC-)u#(h$D_-#XW-GnW621zGFBMu=F*F1 zNjH*}5{lEpW(^*$Bf*d^*+m|ge%R053%_T86lR5CM_%#@qT0u8#9znS@v9kuUJi{} zo;B4b(hbN^k0q&$zp#b;<@U(dUCI-I?aHcbL@J+hWVCdajun)BI5^O#gPxK&otZg; zGll$PACIUG>Koa5&qm#46t$PQ-=GH~&TZbt6FeN>K9;(KvSp3*p2sOA#1JPjW}IMc zVp?R|?0Q2k6jhWQWG7jH>>@Z?Om=Y_PEcL4m>|}dKdkZ6a9~D51<^;b(AA_V1Wr>VJ4y& zz@4wmwew*BiD;~_oL>`XIZFi&;Ua?CI9d6Vh|wD*3J)T7qrMJ4r1MB>%pHPEMaYSA uNMjEEfeFgQ0Z01_%I&+TVU^Kmv8&Uw*LVNdSX)m diff --git a/assets/lottie/ic_anim_plasma_generation.json b/assets/lottie/ic_anim_plasma_generation.json new file mode 100644 index 00000000..090cf9cd --- /dev/null +++ b/assets/lottie/ic_anim_plasma_generation.json @@ -0,0 +1 @@ +{"v":"5.7.1","fr":30,"ip":0,"op":80,"w":500,"h":500,"nm":"Plasma generation","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"thunder Outlines 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[250,250,0],"ix":2},"a":{"a":0,"k":[250,250,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[95.758,-41.79],[21.469,-41.79],[22.96,-175.92],[-95.757,41.789],[-21.469,41.789],[-22.96,175.92]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"rd","nm":"Round Corners 1","r":{"a":0,"k":10,"ix":1},"ix":2,"mn":"ADBE Vector Filter - RC","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[100]},{"t":43,"s":[0]}],"ix":1},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":3,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0.31,0.82,0.4],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":10,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[250,250],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"thunder Outlines","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[250,250,0],"ix":2},"a":{"a":0,"k":[250,250,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[95.758,-41.79],[21.469,-41.79],[22.96,-175.92],[-95.757,41.789],[-21.469,41.789],[-22.96,175.92]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"rd","nm":"Round Corners 1","r":{"a":0,"k":10,"ix":1},"ix":2,"mn":"ADBE Vector Filter - RC","hd":false},{"ty":"st","c":{"a":0,"k":[0.702,0.702,0.702],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":4,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[250,250],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"thunder_2 Outlines","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":34,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":41,"s":[60]},{"t":49,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[250,250,0],"ix":2},"a":{"a":0,"k":[250,250,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[95.758,-41.79],[21.469,-41.79],[22.96,-175.92],[-95.757,41.789],[-21.469,41.789],[-22.96,175.92]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.31,0.82,0.4],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[250,250],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"rd","nm":"Round Corners 1","r":{"a":0,"k":10,"ix":1},"ix":2,"mn":"ADBE Vector Filter - RC","hd":false}],"ip":32,"op":322,"st":22,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/lib/blocs/accelerator/project_list_bloc.dart b/lib/blocs/accelerator/project_list_bloc.dart index 17514b6f..ce72805d 100644 --- a/lib/blocs/accelerator/project_list_bloc.dart +++ b/lib/blocs/accelerator/project_list_bloc.dart @@ -207,6 +207,7 @@ class ProjectListBloc with RefreshBlocMixin { Future> _filterProjectsByTags(List projects) async { if (selectedProjectsFilterTag.isNotEmpty) { Iterable? votedProjectIds; + Iterable? votedPhaseIds; Iterable filteredProjects = projects; if (selectedProjectsFilterTag.contains(AccProjectsFilterTag.myProjects)) { filteredProjects = filteredProjects.where( @@ -219,19 +220,29 @@ class ProjectListBloc with RefreshBlocMixin { (project) => project.status == AcceleratorProjectStatus.active); } if (selectedProjectsFilterTag - .contains(AccProjectsFilterTag.votingOpened)) { + .contains(AccProjectsFilterTag.needsVoting)) { votedProjectIds ??= await _getVotedProjectIdsByPillar(filteredProjects); + votedPhaseIds ??= await _getVotedPhaseIdsByPillar(filteredProjects); filteredProjects = filteredProjects.where( (project) => - project.status == AcceleratorProjectStatus.voting && - !votedProjectIds!.contains(project.id), + (project.status == AcceleratorProjectStatus.voting && + !votedProjectIds!.contains(project.id)) || + project.phases.any((phase) => + phase.status == AcceleratorProjectStatus.voting && + !votedPhaseIds!.contains(phase.id)), ); } if (selectedProjectsFilterTag .contains(AccProjectsFilterTag.alreadyVoted)) { votedProjectIds ??= await _getVotedProjectIdsByPillar(filteredProjects); + votedPhaseIds ??= await _getVotedPhaseIdsByPillar(filteredProjects); filteredProjects = filteredProjects.where( - (project) => votedProjectIds!.contains(project.id), + (project) => + (project.status == AcceleratorProjectStatus.voting && + votedProjectIds!.contains(project.id)) || + project.phases.any((phase) => + phase.status == AcceleratorProjectStatus.voting && + votedPhaseIds!.contains(phase.id)), ); } return filteredProjects.toSet(); @@ -248,4 +259,16 @@ class ProjectListBloc with RefreshBlocMixin { ); return pillarVotes.where((e) => e != null).map((e) => e!.id); } + + Future> _getVotedPhaseIdsByPillar( + Iterable projects) async { + var pillarVotes = await zenon!.embedded.accelerator.getPillarVotes( + pillarInfo!.name, + projects + .expand((project) => project.phaseIds) + .map((id) => id.toString()) + .toList(), + ); + return pillarVotes.where((e) => e != null).map((e) => e!.id); + } } diff --git a/lib/blocs/auto_receive_tx_worker.dart b/lib/blocs/auto_receive_tx_worker.dart index 6d8cbe54..e736bb02 100644 --- a/lib/blocs/auto_receive_tx_worker.dart +++ b/lib/blocs/auto_receive_tx_worker.dart @@ -9,7 +9,7 @@ import 'package:zenon_syrius_wallet_flutter/main.dart'; import 'package:zenon_syrius_wallet_flutter/model/model.dart'; import 'package:zenon_syrius_wallet_flutter/utils/account_block_utils.dart'; import 'package:zenon_syrius_wallet_flutter/utils/address_utils.dart'; -import 'package:zenon_syrius_wallet_flutter/utils/global.dart'; +import 'package:zenon_syrius_wallet_flutter/utils/constants.dart'; import 'package:znn_sdk_dart/znn_sdk_dart.dart'; class AutoReceiveTxWorker extends BaseBloc { @@ -22,20 +22,52 @@ class AutoReceiveTxWorker extends BaseBloc { return _instance!; } + Future autoReceiveTransactionHash( + Hash currentHash) async { + if (!running) { + running = true; + try { + Address toAddress = + (await zenon!.ledger.getAccountBlockByHash(currentHash))!.toAddress; + AccountBlockTemplate transactionParams = AccountBlockTemplate.receive( + currentHash, + ); + AccountBlockTemplate response = + await AccountBlockUtils.createAccountBlock( + transactionParams, + 'receive transaction', + address: toAddress, + waitForRequiredPlasma: true, + ); + _sendSuccessNotification(response, toAddress.toString()); + return response; + } on RpcException catch (e, stackTrace) { + _sendErrorNotification(e.toString()); + Logger('AutoReceiveTxWorker') + .log(Level.WARNING, 'autoReceive', e, stackTrace); + } finally { + running = false; + } + } + return null; + } + Future autoReceive() async { + if (!sharedPrefsService!.get( + kAutoReceiveKey, + defaultValue: kAutoReceiveDefaultValue, + )) { + pool.clear(); + return; + } // Make sure that AutoUnlockHtlcWorker is not running since it should be // given priority to send transactions. if (pool.isNotEmpty && !running && !sl().running) { running = true; Hash currentHash = pool.first; try { - String toAddress = - (await zenon!.ledger.getAccountBlockByHash(currentHash))! - .toAddress - .toString(); - KeyPair keyPair = kKeyStore!.getKeyPair( - kDefaultAddressList.indexOf(toAddress), - ); + Address toAddress = + (await zenon!.ledger.getAccountBlockByHash(currentHash))!.toAddress; AccountBlockTemplate transactionParams = AccountBlockTemplate.receive( currentHash, ); @@ -43,30 +75,47 @@ class AutoReceiveTxWorker extends BaseBloc { await AccountBlockUtils.createAccountBlock( transactionParams, 'receive transaction', - blockSigningKey: keyPair, + address: toAddress, waitForRequiredPlasma: true, ); - pool.removeFirst(); - _sendSuccessNotification(response, toAddress); + _sendSuccessNotification(response, toAddress.toString()); + if (pool.isNotEmpty) { + pool.removeFirst(); + } + running = false; + autoReceive(); } on RpcException catch (e, stackTrace) { _sendErrorNotification(e.toString()); Logger('AutoReceiveTxWorker') .log(Level.WARNING, 'autoReceive', e, stackTrace); if (e.message.compareTo('account-block from-block already received') == 0) { - pool.removeFirst(); - } else { - _sendErrorNotification(e.toString()); + if (pool.isNotEmpty) { + pool.removeFirst(); + } } } catch (e, stackTrace) { Logger('AutoReceiveTxWorker') .log(Level.WARNING, 'autoReceive', e, stackTrace); _sendErrorNotification(e.toString()); + } finally { + running = false; } - running = false; } } + Future addHash(Hash hash) async { + zenon!.stats.syncInfo().then((syncInfo) { + if (!pool.contains(hash) && + (syncInfo.state == SyncState.syncDone || + (syncInfo.targetHeight > 0 && + syncInfo.currentHeight > 0 && + (syncInfo.targetHeight - syncInfo.currentHeight) < 3))) { + pool.add(hash); + } + }); + } + void _sendErrorNotification(String errorText) { addEvent( WalletNotification( @@ -89,20 +138,4 @@ class AutoReceiveTxWorker extends BaseBloc { ), ); } - - Future addHash(Hash hash) async { - if (!pool.contains(hash)) { - zenon!.stats.syncInfo().then((syncInfo) { - // Verify that the pool does not already contain the hash after the - // asynchronous request has completed and that the node is in sync. - if (!pool.contains(hash) && - (syncInfo.state == SyncState.syncDone || - (syncInfo.targetHeight > 0 && - syncInfo.currentHeight > 0 && - (syncInfo.targetHeight - syncInfo.currentHeight) < 3))) { - pool.add(hash); - } - }); - } - } } diff --git a/lib/blocs/auto_unlock_htlc_worker.dart b/lib/blocs/auto_unlock_htlc_worker.dart index 1abb70a4..63e7a644 100644 --- a/lib/blocs/auto_unlock_htlc_worker.dart +++ b/lib/blocs/auto_unlock_htlc_worker.dart @@ -25,7 +25,7 @@ class AutoUnlockHtlcWorker extends BaseBloc { } Future autoUnlock() async { - if (pool.isNotEmpty && !running && kKeyStore != null) { + if (pool.isNotEmpty && !running && kWalletFile != null) { running = true; Hash currentHash = pool.first; try { @@ -38,16 +38,13 @@ class AutoUnlockHtlcWorker extends BaseBloc { if (!kDefaultAddressList.contains(htlc.hashLocked.toString())) { throw 'Swap address not in default addresses. Please add the address in the addresses list.'; } - KeyPair? keyPair = kKeyStore!.getKeyPair( - kDefaultAddressList.indexOf(htlc.hashLocked.toString()), - ); AccountBlockTemplate transactionParams = zenon!.embedded.htlc .unlock(htlc.id, FormatUtils.decodeHexString(swap.preimage!)); AccountBlockTemplate response = await AccountBlockUtils.createAccountBlock( transactionParams, 'complete swap', - blockSigningKey: keyPair, + address: htlc.hashLocked, waitForRequiredPlasma: true, ); _sendSuccessNotification(response, htlc.hashLocked.toString()); diff --git a/lib/blocs/blocs.dart b/lib/blocs/blocs.dart index 1b1ecb40..bdfc4b21 100644 --- a/lib/blocs/blocs.dart +++ b/lib/blocs/blocs.dart @@ -4,10 +4,11 @@ export 'auto_receive_tx_worker.dart'; export 'base_bloc.dart'; export 'base_bloc_for_reloading_indicator.dart'; export 'base_bloc_with_refresh_mixin.dart'; -export 'decrypt_key_store_bloc.dart'; +export 'decrypt_wallet_file_bloc.dart'; export 'hide_widget_status_bloc.dart'; export 'infinite_scroll_bloc.dart'; -export 'key_store_path_bloc.dart'; +export 'key_store_file_bloc.dart'; +export 'ledger_wallet_file_bloc.dart'; export 'lock_bloc.dart'; export 'node_sync_status_bloc.dart'; export 'notifications_bloc.dart'; diff --git a/lib/blocs/decrypt_key_store_bloc.dart b/lib/blocs/decrypt_key_store_bloc.dart deleted file mode 100644 index 323ac64b..00000000 --- a/lib/blocs/decrypt_key_store_bloc.dart +++ /dev/null @@ -1,16 +0,0 @@ -import 'package:zenon_syrius_wallet_flutter/blocs/blocs.dart'; -import 'package:zenon_syrius_wallet_flutter/utils/keystore_utils.dart'; -import 'package:znn_sdk_dart/znn_sdk_dart.dart'; - -class DecryptKeyStoreBloc extends BaseBloc { - Future decryptKeyStoreFile(String path, String password) async { - try { - addEvent(null); - KeyStore keyStore = - await KeyStoreUtils.decryptKeyStoreFile(path, password); - addEvent(keyStore); - } catch (e, stackTrace) { - addError(e, stackTrace); - } - } -} diff --git a/lib/blocs/decrypt_wallet_file_bloc.dart b/lib/blocs/decrypt_wallet_file_bloc.dart new file mode 100644 index 00000000..6436c362 --- /dev/null +++ b/lib/blocs/decrypt_wallet_file_bloc.dart @@ -0,0 +1,15 @@ +import 'package:zenon_syrius_wallet_flutter/blocs/blocs.dart'; +import 'package:zenon_syrius_wallet_flutter/utils/wallet_file.dart'; +import 'package:zenon_syrius_wallet_flutter/utils/wallet_utils.dart'; + +class DecryptWalletFileBloc extends BaseBloc { + Future decryptWalletFile(String path, String password) async { + try { + addEvent(null); + final walletFile = await WalletUtils.decryptWalletFile(path, password); + addEvent(walletFile); + } catch (e, stackTrace) { + addError(e, stackTrace); + } + } +} diff --git a/lib/blocs/hide_widget_status_bloc.dart b/lib/blocs/hide_widget_status_bloc.dart index 58776961..f79c3368 100644 --- a/lib/blocs/hide_widget_status_bloc.dart +++ b/lib/blocs/hide_widget_status_bloc.dart @@ -2,7 +2,7 @@ import 'package:zenon_syrius_wallet_flutter/blocs/blocs.dart'; import 'package:zenon_syrius_wallet_flutter/main.dart'; import 'package:zenon_syrius_wallet_flutter/utils/constants.dart'; import 'package:zenon_syrius_wallet_flutter/utils/global.dart'; -import 'package:zenon_syrius_wallet_flutter/utils/keystore_utils.dart'; +import 'package:zenon_syrius_wallet_flutter/utils/wallet_utils.dart'; import 'package:zenon_syrius_wallet_flutter/utils/widget_utils.dart'; import 'package:znn_sdk_dart/znn_sdk_dart.dart'; @@ -15,7 +15,7 @@ class HideWidgetStatusBloc extends BaseBloc { try { addEvent(null); if (!isHidden) { - await KeyStoreUtils.decryptKeyStoreFile(kKeyStorePath!, password); + await WalletUtils.decryptWalletFile(kWalletPath!, password); } await _markWidgetAsHidden(widgetTitle, isHidden); addEvent(isHidden); diff --git a/lib/blocs/key_store_file_bloc.dart b/lib/blocs/key_store_file_bloc.dart new file mode 100644 index 00000000..ff09a926 --- /dev/null +++ b/lib/blocs/key_store_file_bloc.dart @@ -0,0 +1,25 @@ +import 'dart:async'; +import 'dart:convert'; + +import 'package:zenon_syrius_wallet_flutter/blocs/blocs.dart'; +import 'package:zenon_syrius_wallet_flutter/utils/global.dart'; +import 'package:zenon_syrius_wallet_flutter/utils/wallet_utils.dart'; +import 'package:zenon_syrius_wallet_flutter/utils/init_utils.dart'; +import 'package:zenon_syrius_wallet_flutter/utils/wallet_file.dart'; +import 'package:znn_sdk_dart/znn_sdk_dart.dart'; + +class KeyStoreFileBloc extends BaseBloc { + Future getKeyStorePath( + String mnemonic, + String password, + ) async { + try { + await WalletUtils.createKeyStoreWalletFile(mnemonic, password); + await InitUtils.initWalletAfterDecryption( + Crypto.digest(utf8.encode(password))); + addEvent(kWalletFile as KeyStoreWalletFile); + } catch (e, stackTrace) { + addError(e, stackTrace); + } + } +} diff --git a/lib/blocs/key_store_path_bloc.dart b/lib/blocs/key_store_path_bloc.dart deleted file mode 100644 index 31f98cae..00000000 --- a/lib/blocs/key_store_path_bloc.dart +++ /dev/null @@ -1,21 +0,0 @@ -import 'dart:async'; - -import 'package:zenon_syrius_wallet_flutter/blocs/blocs.dart'; -import 'package:zenon_syrius_wallet_flutter/utils/global.dart'; -import 'package:zenon_syrius_wallet_flutter/utils/keystore_utils.dart'; -import 'package:zenon_syrius_wallet_flutter/utils/init_utils.dart'; - -class KeyStorePathBloc extends BaseBloc { - Future getKeyStorePath( - String mnemonic, - String passphrase, - ) async { - try { - await KeyStoreUtils.createKeyStore(mnemonic, passphrase); - await InitUtils.initWalletAfterDecryption(); - addEvent(kKeyStorePath); - } catch (e, stackTrace) { - addError(e, stackTrace); - } - } -} diff --git a/lib/blocs/ledger_wallet_file_bloc.dart b/lib/blocs/ledger_wallet_file_bloc.dart new file mode 100644 index 00000000..413900b5 --- /dev/null +++ b/lib/blocs/ledger_wallet_file_bloc.dart @@ -0,0 +1,24 @@ +import 'dart:async'; +import 'dart:convert'; + +import 'package:zenon_syrius_wallet_flutter/blocs/blocs.dart'; +import 'package:zenon_syrius_wallet_flutter/utils/global.dart'; +import 'package:zenon_syrius_wallet_flutter/utils/wallet_utils.dart'; +import 'package:zenon_syrius_wallet_flutter/utils/init_utils.dart'; +import 'package:zenon_syrius_wallet_flutter/utils/wallet_file.dart'; +import 'package:znn_sdk_dart/znn_sdk_dart.dart'; + +class LedgerWalletFileBloc extends BaseBloc { + Future getLedgerWalletPath(String walletId, String password, + String? walletName) async { + try { + await WalletUtils.createLedgerWalletFile(walletId, password, + walletName: walletName); + await InitUtils.initWalletAfterDecryption( + Crypto.digest(utf8.encode(password))); + addEvent(kWalletFile as LedgerWalletFile); + } catch (e, stackTrace) { + addError(e, stackTrace); + } + } +} diff --git a/lib/blocs/node_sync_status_bloc.dart b/lib/blocs/node_sync_status_bloc.dart index c012338d..b996ee91 100644 --- a/lib/blocs/node_sync_status_bloc.dart +++ b/lib/blocs/node_sync_status_bloc.dart @@ -19,9 +19,6 @@ class NodeSyncStatusBloc extends DashboardBaseBloc { (syncInfo.targetHeight - syncInfo.currentHeight) > 3))) { lastSyncState = syncInfo.state; if (syncInfo.state == SyncState.syncDone) { - NodeUtils.getUnreceivedTransactions().then((value) { - sl().autoReceive(); - }); Future.delayed(const Duration(seconds: 5)).then((value) { NodeUtils.getUnreceivedTransactions().then((value) { sl().autoReceive(); diff --git a/lib/blocs/notifications_bloc.dart b/lib/blocs/notifications_bloc.dart index cf8c3212..2e58736b 100644 --- a/lib/blocs/notifications_bloc.dart +++ b/lib/blocs/notifications_bloc.dart @@ -23,7 +23,7 @@ class NotificationsBloc extends BaseBloc { title: notification.title ?? 'Empty title', body: notification.details ?? 'No details available', ); - localNotification.show(); + await localNotification.show(); } addEvent(notification); } catch (e, stackTrace) { @@ -31,8 +31,8 @@ class NotificationsBloc extends BaseBloc { } } - sendPlasmaNotification(String purposeOfGeneratingPlasma) { - addNotification( + Future sendPlasmaNotification(String purposeOfGeneratingPlasma) async { + await addNotification( WalletNotification( title: 'Plasma will be generated in order to ' '$purposeOfGeneratingPlasma', @@ -43,8 +43,8 @@ class NotificationsBloc extends BaseBloc { ); } - addErrorNotification(Object error, String title) { - addNotification( + Future addErrorNotification(Object error, String title) async { + await addNotification( WalletNotification( title: title, timestamp: DateTime.now().millisecondsSinceEpoch, diff --git a/lib/blocs/p2p_swap/htlc_swap/complete_htlc_swap_bloc.dart b/lib/blocs/p2p_swap/htlc_swap/complete_htlc_swap_bloc.dart index d37dda75..dc8d49f3 100644 --- a/lib/blocs/p2p_swap/htlc_swap/complete_htlc_swap_bloc.dart +++ b/lib/blocs/p2p_swap/htlc_swap/complete_htlc_swap_bloc.dart @@ -30,11 +30,8 @@ class CompleteHtlcSwapBloc extends BaseBloc { AccountBlockTemplate transactionParams = zenon!.embedded.htlc.unlock( Hash.parse(htlcId), FormatUtils.decodeHexString(swap.preimage!)); - KeyPair blockSigningKeyPair = kKeyStore!.getKeyPair( - kDefaultAddressList.indexOf(swap.selfAddress.toString()), - ); AccountBlockUtils.createAccountBlock(transactionParams, 'complete swap', - blockSigningKey: blockSigningKeyPair, waitForRequiredPlasma: true) + address: Address.parse(swap.selfAddress), waitForRequiredPlasma: true) .then( (response) async { swap.state = P2pSwapState.completed; diff --git a/lib/blocs/p2p_swap/htlc_swap/join_htlc_swap_bloc.dart b/lib/blocs/p2p_swap/htlc_swap/join_htlc_swap_bloc.dart index 181ed084..45010baf 100644 --- a/lib/blocs/p2p_swap/htlc_swap/join_htlc_swap_bloc.dart +++ b/lib/blocs/p2p_swap/htlc_swap/join_htlc_swap_bloc.dart @@ -6,7 +6,6 @@ import 'package:zenon_syrius_wallet_flutter/utils/account_block_utils.dart'; import 'package:zenon_syrius_wallet_flutter/utils/address_utils.dart'; import 'package:zenon_syrius_wallet_flutter/utils/date_time_utils.dart'; import 'package:zenon_syrius_wallet_flutter/utils/format_utils.dart'; -import 'package:zenon_syrius_wallet_flutter/utils/global.dart'; import 'package:znn_sdk_dart/znn_sdk_dart.dart'; class JoinHtlcSwapBloc extends BaseBloc { @@ -19,7 +18,7 @@ class JoinHtlcSwapBloc extends BaseBloc { required P2pSwapChain fromChain, required P2pSwapChain toChain, required int counterHtlcExpirationTime, - }) { + }) async { try { addEvent(null); AccountBlockTemplate transactionParams = zenon!.embedded.htlc.create( @@ -31,11 +30,8 @@ class JoinHtlcSwapBloc extends BaseBloc { initialHtlc.keyMaxSize, initialHtlc.hashLock, ); - KeyPair blockSigningKeyPair = kKeyStore!.getKeyPair( - kDefaultAddressList.indexOf(initialHtlc.hashLocked.toString()), - ); AccountBlockUtils.createAccountBlock(transactionParams, 'join swap', - blockSigningKey: blockSigningKeyPair, waitForRequiredPlasma: true) + address: initialHtlc.hashLocked, waitForRequiredPlasma: true) .then( (response) async { final swap = HtlcSwap( diff --git a/lib/blocs/p2p_swap/htlc_swap/reclaim_htlc_swap_funds_bloc.dart b/lib/blocs/p2p_swap/htlc_swap/reclaim_htlc_swap_funds_bloc.dart index 0dd86440..8aec5c9d 100644 --- a/lib/blocs/p2p_swap/htlc_swap/reclaim_htlc_swap_funds_bloc.dart +++ b/lib/blocs/p2p_swap/htlc_swap/reclaim_htlc_swap_funds_bloc.dart @@ -2,24 +2,20 @@ import 'package:zenon_syrius_wallet_flutter/blocs/base_bloc.dart'; import 'package:zenon_syrius_wallet_flutter/main.dart'; import 'package:zenon_syrius_wallet_flutter/utils/account_block_utils.dart'; import 'package:zenon_syrius_wallet_flutter/utils/address_utils.dart'; -import 'package:zenon_syrius_wallet_flutter/utils/global.dart'; import 'package:znn_sdk_dart/znn_sdk_dart.dart'; class ReclaimHtlcSwapFundsBloc extends BaseBloc { void reclaimFunds({ required Hash htlcId, required Address selfAddress, - }) { + }) async { try { addEvent(null); AccountBlockTemplate transactionParams = zenon!.embedded.htlc.reclaim(htlcId); - KeyPair blockSigningKeyPair = kKeyStore!.getKeyPair( - kDefaultAddressList.indexOf(selfAddress.toString()), - ); AccountBlockUtils.createAccountBlock( transactionParams, 'reclaim swap funds', - blockSigningKey: blockSigningKeyPair, waitForRequiredPlasma: true) + address: selfAddress, waitForRequiredPlasma: true) .then( (response) { ZenonAddressUtils.refreshBalance(); diff --git a/lib/blocs/p2p_swap/htlc_swap/start_htlc_swap_bloc.dart b/lib/blocs/p2p_swap/htlc_swap/start_htlc_swap_bloc.dart index d7950869..281dfe55 100644 --- a/lib/blocs/p2p_swap/htlc_swap/start_htlc_swap_bloc.dart +++ b/lib/blocs/p2p_swap/htlc_swap/start_htlc_swap_bloc.dart @@ -8,7 +8,6 @@ import 'package:zenon_syrius_wallet_flutter/utils/account_block_utils.dart'; import 'package:zenon_syrius_wallet_flutter/utils/address_utils.dart'; import 'package:zenon_syrius_wallet_flutter/utils/date_time_utils.dart'; import 'package:zenon_syrius_wallet_flutter/utils/format_utils.dart'; -import 'package:zenon_syrius_wallet_flutter/utils/global.dart'; import 'package:znn_sdk_dart/znn_sdk_dart.dart'; class StartHtlcSwapBloc extends BaseBloc { @@ -37,11 +36,8 @@ class StartHtlcSwapBloc extends BaseBloc { htlcPreimageMaxLength, hashLock.getBytes(), ); - KeyPair blockSigningKeyPair = kKeyStore!.getKeyPair( - kDefaultAddressList.indexOf(selfAddress.toString()), - ); AccountBlockUtils.createAccountBlock(transactionParams, 'start swap', - blockSigningKey: blockSigningKeyPair, waitForRequiredPlasma: true) + address: selfAddress, waitForRequiredPlasma: true) .then( (response) async { final swap = HtlcSwap( diff --git a/lib/blocs/sentinels/sentinel_qsr_info_bloc.dart b/lib/blocs/sentinels/sentinel_qsr_info_bloc.dart index 62a9d871..978b80d3 100644 --- a/lib/blocs/sentinels/sentinel_qsr_info_bloc.dart +++ b/lib/blocs/sentinels/sentinel_qsr_info_bloc.dart @@ -1,15 +1,22 @@ import 'package:zenon_syrius_wallet_flutter/blocs/blocs.dart'; import 'package:zenon_syrius_wallet_flutter/main.dart'; +import 'package:zenon_syrius_wallet_flutter/model/sentinels_qsr_info.dart'; import 'package:znn_sdk_dart/znn_sdk_dart.dart'; -class SentinelsQsrInfoBloc extends BaseBloc { - Future getQsrDepositedAmount(String address) async { +class SentinelsQsrInfoBloc extends BaseBloc { + Future getQsrManagementInfo(String address) async { try { addEvent(null); - BigInt response = await zenon!.embedded.sentinel.getDepositedQsr( + BigInt deposit = (await zenon!.embedded.sentinel.getDepositedQsr( Address.parse(address), + )); + BigInt cost = sentinelRegisterQsrAmount; + addEvent( + SentinelsQsrInfo( + deposit: deposit, + cost: cost, + ), ); - addEvent(response); } catch (e, stackTrace) { addError(e, stackTrace); } diff --git a/lib/blocs/transfer/pending_transactions_bloc.dart b/lib/blocs/transfer/pending_transactions_bloc.dart new file mode 100644 index 00000000..b581d2ee --- /dev/null +++ b/lib/blocs/transfer/pending_transactions_bloc.dart @@ -0,0 +1,17 @@ +import 'dart:async'; + +import 'package:zenon_syrius_wallet_flutter/blocs/blocs.dart'; +import 'package:zenon_syrius_wallet_flutter/main.dart'; +import 'package:zenon_syrius_wallet_flutter/utils/global.dart'; +import 'package:znn_sdk_dart/znn_sdk_dart.dart'; + +class PendingTransactionsBloc extends InfiniteScrollBloc { + @override + Future> getData(int pageKey, int pageSize) async => + (await zenon!.ledger.getUnreceivedBlocksByAddress( + Address.parse(kSelectedAddress!), + pageIndex: pageKey, + pageSize: pageSize, + )) + .list!; +} diff --git a/lib/blocs/transfer/receive_transaction_bloc.dart b/lib/blocs/transfer/receive_transaction_bloc.dart new file mode 100644 index 00000000..83e347bb --- /dev/null +++ b/lib/blocs/transfer/receive_transaction_bloc.dart @@ -0,0 +1,17 @@ +import 'package:flutter/material.dart'; +import 'package:zenon_syrius_wallet_flutter/blocs/blocs.dart'; +import 'package:zenon_syrius_wallet_flutter/main.dart'; +import 'package:znn_sdk_dart/znn_sdk_dart.dart'; + +class ReceiveTransactionBloc extends BaseBloc { + void receiveTransaction(String id, BuildContext context) async { + try { + addEvent(null); + var response = await sl() + .autoReceiveTransactionHash(Hash.parse(id)); + addEvent(response); + } catch (e, stackTrace) { + addError(e, stackTrace); + } + } +} diff --git a/lib/blocs/transfer/send_payment_bloc.dart b/lib/blocs/transfer/send_payment_bloc.dart index c656eadb..626172b0 100644 --- a/lib/blocs/transfer/send_payment_bloc.dart +++ b/lib/blocs/transfer/send_payment_bloc.dart @@ -1,7 +1,6 @@ import 'package:zenon_syrius_wallet_flutter/blocs/blocs.dart'; import 'package:zenon_syrius_wallet_flutter/utils/account_block_utils.dart'; import 'package:zenon_syrius_wallet_flutter/utils/address_utils.dart'; -import 'package:zenon_syrius_wallet_flutter/utils/global.dart'; import 'package:znn_sdk_dart/znn_sdk_dart.dart'; class SendPaymentBloc extends BaseBloc { @@ -13,7 +12,7 @@ class SendPaymentBloc extends BaseBloc { List? data, Token? token, AccountBlockTemplate? block, - }) { + }) async { assert( block == null && fromAddress != null && @@ -31,13 +30,10 @@ class SendPaymentBloc extends BaseBloc { amount!, data, ); - KeyPair blockSigningKeyPair = kKeyStore!.getKeyPair( - kDefaultAddressList.indexOf(fromAddress), - ); AccountBlockUtils.createAccountBlock( accountBlock, 'send transaction', - blockSigningKey: blockSigningKeyPair, + address: Address.parse(fromAddress!), waitForRequiredPlasma: true, ).then( (response) { diff --git a/lib/blocs/wallet_connect/chains/i_chain.dart b/lib/blocs/wallet_connect/chains/i_chain.dart new file mode 100644 index 00000000..f3a3e36b --- /dev/null +++ b/lib/blocs/wallet_connect/chains/i_chain.dart @@ -0,0 +1,5 @@ +abstract class IChain { + String getNamespace(); + String getChainId(); + List getEvents(); +} diff --git a/lib/blocs/wallet_connect/chains/nom_service.dart b/lib/blocs/wallet_connect/chains/nom_service.dart new file mode 100644 index 00000000..02bc3f1d --- /dev/null +++ b/lib/blocs/wallet_connect/chains/nom_service.dart @@ -0,0 +1,316 @@ +import 'package:flutter/material.dart'; +import 'package:walletconnect_flutter_v2/walletconnect_flutter_v2.dart'; +import 'package:window_manager/window_manager.dart'; +import 'package:zenon_syrius_wallet_flutter/blocs/transfer/send_payment_bloc.dart'; +import 'package:zenon_syrius_wallet_flutter/blocs/wallet_connect/chains/i_chain.dart'; +import 'package:zenon_syrius_wallet_flutter/main.dart'; +import 'package:zenon_syrius_wallet_flutter/services/i_web3wallet_service.dart'; +import 'package:zenon_syrius_wallet_flutter/utils/address_utils.dart'; +import 'package:zenon_syrius_wallet_flutter/utils/constants.dart'; +import 'package:zenon_syrius_wallet_flutter/utils/extensions.dart'; +import 'package:zenon_syrius_wallet_flutter/utils/functions.dart'; +import 'package:zenon_syrius_wallet_flutter/utils/global.dart'; +import 'package:zenon_syrius_wallet_flutter/utils/notification_utils.dart'; +import 'package:zenon_syrius_wallet_flutter/widgets/main_app_container.dart'; +import 'package:zenon_syrius_wallet_flutter/widgets/reusable_widgets/dialogs.dart'; +import 'package:zenon_syrius_wallet_flutter/widgets/reusable_widgets/icons/link_icon.dart'; +import 'package:znn_sdk_dart/znn_sdk_dart.dart'; + +enum NoMChainId { + mainnet, + testnet, +} + +extension NoMChainIdX on NoMChainId { + String chain() { + String name = ''; + + switch (this) { + case NoMChainId.mainnet: + name = '1'; + break; + case NoMChainId.testnet: + name = '3'; + break; + } + + return '${NoMService.namespace}:$name'; + } +} + +class NoMService extends IChain { + static const namespace = 'zenon'; + + final IWeb3WalletService _web3WalletService = sl(); + + final NoMChainId reference; + + final _walletLockedError = const WalletConnectError( + code: 9000, + message: 'Wallet is locked', + ); + + Web3Wallet? wallet; + + NoMService({ + required this.reference, + }) { + wallet = _web3WalletService.getWeb3Wallet(); + + // Register event emitters + // wallet!.registerEventEmitter(chainId: getChainId(), event: 'chainIdChange'); + // wallet!.registerEventEmitter(chainId: getChainId(), event: 'addressChange'); + + // Register request handlers + wallet!.registerRequestHandler( + chainId: getChainId(), + method: 'znn_info', + handler: _methodZnnInfo, + ); + wallet!.registerRequestHandler( + chainId: getChainId(), + method: 'znn_sign', + handler: _methodZnnSign, + ); + wallet!.registerRequestHandler( + chainId: getChainId(), + method: 'znn_send', + handler: _methodZnnSend, + ); + } + + @override + String getNamespace() { + return namespace; + } + + @override + String getChainId() { + return reference.chain(); + } + + @override + List getEvents() { + return ['chainIdChange', 'addressChange']; + } + + Future _methodZnnInfo(String topic, dynamic params) async { + if (!await windowManager.isFocused() || !await windowManager.isVisible()) { + windowManager.show(); + } + final dAppMetadata = wallet! + .getActiveSessions() + .values + .firstWhere((element) => element.topic == topic) + .peer + .metadata; + + if (kCurrentPage != Tabs.lock) { + if (globalNavigatorKey.currentContext!.mounted) { + final actionWasAccepted = await showDialogWithNoAndYesOptions( + context: globalNavigatorKey.currentContext!, + isBarrierDismissible: false, + title: '${dAppMetadata.name} - Information', + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text('Are you sure you want to allow ${dAppMetadata.name} to ' + 'retrieve the current address, node URL and chain identifier information?'), + kVerticalSpacing, + Image( + image: NetworkImage(dAppMetadata.icons.first), + height: 100.0, + fit: BoxFit.fitHeight, + ), + kVerticalSpacing, + Text(dAppMetadata.description), + kVerticalSpacing, + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text(dAppMetadata.url), + LinkIcon( + url: dAppMetadata.url, + ) + ], + ), + ], + ), + onYesButtonPressed: () async {}, + onNoButtonPressed: () {}, + ); + + if (actionWasAccepted) { + return { + 'address': kSelectedAddress, + 'nodeUrl': kCurrentNode, + 'chainId': getChainIdentifier(), + }; + } else { + await NotificationUtils.sendNotificationError( + Errors.getSdkError(Errors.USER_REJECTED), + 'You have rejected the WalletConnect request'); + throw Errors.getSdkError(Errors.USER_REJECTED); + } + } else { + throw _walletLockedError; + } + } else { + throw _walletLockedError; + } + } + + Future _methodZnnSign(String topic, dynamic params) async { + if (!await windowManager.isFocused() || !await windowManager.isVisible()) { + windowManager.show(); + } + final dAppMetadata = wallet! + .getActiveSessions() + .values + .firstWhere((element) => element.topic == topic) + .peer + .metadata; + if (kCurrentPage != Tabs.lock) { + final message = params as String; + + if (globalNavigatorKey.currentContext!.mounted) { + final actionWasAccepted = await showDialogWithNoAndYesOptions( + context: globalNavigatorKey.currentContext!, + isBarrierDismissible: false, + title: '${dAppMetadata.name} - Sign Message', + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text('Are you sure you want to ' + 'sign message $message ?'), + kVerticalSpacing, + Image( + image: NetworkImage(dAppMetadata.icons.first), + height: 100.0, + fit: BoxFit.fitHeight, + ), + kVerticalSpacing, + Text(dAppMetadata.description), + kVerticalSpacing, + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text(dAppMetadata.url), + LinkIcon( + url: dAppMetadata.url, + ) + ], + ), + ], + ), + onYesButtonPressed: () async {}, + onNoButtonPressed: () {}, + ); + + if (actionWasAccepted) { + return await walletSign(message.codeUnits); + } else { + await NotificationUtils.sendNotificationError( + Errors.getSdkError(Errors.USER_REJECTED), + 'You have rejected the WalletConnect request'); + throw Errors.getSdkError(Errors.USER_REJECTED); + } + } else { + throw _walletLockedError; + } + } else { + throw _walletLockedError; + } + } + + Future _methodZnnSend(String topic, dynamic params) async { + if (!await windowManager.isFocused() || !await windowManager.isVisible()) { + windowManager.show(); + } + final dAppMetadata = wallet! + .getActiveSessions() + .values + .firstWhere((element) => element.topic == topic) + .peer + .metadata; + if (kCurrentPage != Tabs.lock) { + final accountBlock = + AccountBlockTemplate.fromJson(params['accountBlock']); + + final toAddress = ZenonAddressUtils.getLabel( + accountBlock.toAddress.toString(), + ); + + final token = + await zenon!.embedded.token.getByZts(accountBlock.tokenStandard); + + final amount = accountBlock.amount.addDecimals(token!.decimals); + + final sendPaymentBloc = SendPaymentBloc(); + + if (globalNavigatorKey.currentContext!.mounted) { + final wasActionAccepted = await showDialogWithNoAndYesOptions( + context: globalNavigatorKey.currentContext!, + isBarrierDismissible: false, + title: '${dAppMetadata.name} - Send Payment', + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text('Are you sure you want to transfer ' + '$amount ${token.symbol} to ' + '$toAddress ?'), + kVerticalSpacing, + Image( + image: NetworkImage(dAppMetadata.icons.first), + height: 100.0, + fit: BoxFit.fitHeight, + ), + kVerticalSpacing, + Text(dAppMetadata.description), + kVerticalSpacing, + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text(dAppMetadata.url), + LinkIcon( + url: dAppMetadata.url, + ) + ], + ), + ], + ), + description: 'Are you sure you want to transfer ' + '$amount ${token.symbol} to ' + '$toAddress ?', + onYesButtonPressed: () {}, + onNoButtonPressed: () {}, + ); + + if (wasActionAccepted) { + sendPaymentBloc.sendTransfer( + fromAddress: params['fromAddress'], + block: AccountBlockTemplate.fromJson(params['accountBlock']), + ); + + final result = await sendPaymentBloc.stream.firstWhere( + (element) => element != null, + ); + + return result!; + } else { + await NotificationUtils.sendNotificationError( + Errors.getSdkError(Errors.USER_REJECTED), + 'You have rejected the WalletConnect request'); + throw Errors.getSdkError(Errors.USER_REJECTED); + } + } else { + throw _walletLockedError; + } + } else { + throw _walletLockedError; + } + } +} diff --git a/lib/blocs/wallet_connect/wallet_connect_pairings_bloc.dart b/lib/blocs/wallet_connect/wallet_connect_pairings_bloc.dart index e9cf29f3..43d098a4 100644 --- a/lib/blocs/wallet_connect/wallet_connect_pairings_bloc.dart +++ b/lib/blocs/wallet_connect/wallet_connect_pairings_bloc.dart @@ -1,7 +1,7 @@ import 'package:walletconnect_flutter_v2/walletconnect_flutter_v2.dart'; import 'package:zenon_syrius_wallet_flutter/blocs/infinite_scroll_bloc.dart'; import 'package:zenon_syrius_wallet_flutter/main.dart'; -import 'package:zenon_syrius_wallet_flutter/services/wallet_connect_service.dart'; +import 'package:zenon_syrius_wallet_flutter/services/i_web3wallet_service.dart'; class WalletConnectPairingsBloc extends InfiniteScrollBloc { WalletConnectPairingsBloc() : super(isDataRequestPaginated: false); @@ -9,6 +9,6 @@ class WalletConnectPairingsBloc extends InfiniteScrollBloc { @override Future> getData(int pageKey, int pageSize) => Future.delayed(const Duration(milliseconds: 500)).then( - (value) => sl.get().pairings, + (value) => sl.get().pairings.value, ); } diff --git a/lib/blocs/wallet_connect/wallet_connect_sessions_bloc.dart b/lib/blocs/wallet_connect/wallet_connect_sessions_bloc.dart index 12fbb1f2..4d91c736 100644 --- a/lib/blocs/wallet_connect/wallet_connect_sessions_bloc.dart +++ b/lib/blocs/wallet_connect/wallet_connect_sessions_bloc.dart @@ -1,16 +1,16 @@ import 'package:walletconnect_flutter_v2/walletconnect_flutter_v2.dart'; import 'package:zenon_syrius_wallet_flutter/blocs/infinite_scroll_bloc.dart'; import 'package:zenon_syrius_wallet_flutter/main.dart'; -import 'package:zenon_syrius_wallet_flutter/services/wallet_connect_service.dart'; +import 'package:zenon_syrius_wallet_flutter/services/i_web3wallet_service.dart'; class WalletConnectSessionsBloc extends InfiniteScrollBloc { WalletConnectSessionsBloc() : super(isDataRequestPaginated: false); @override Future> getData(int pageKey, int pageSize) async { - final wcService = sl.get(); + final wcService = sl.get(); final sessions = []; - for (var pairing in wcService.pairings) { + for (var pairing in wcService.pairings.value) { sessions.addAll( wcService.getSessionsForPairing(pairing.topic).values, ); diff --git a/lib/main.dart b/lib/main.dart index 5550c81e..f5a1e137 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -18,13 +18,17 @@ import 'package:window_manager/window_manager.dart'; import 'package:zenon_syrius_wallet_flutter/blocs/auto_unlock_htlc_worker.dart'; import 'package:zenon_syrius_wallet_flutter/blocs/blocs.dart'; import 'package:zenon_syrius_wallet_flutter/handlers/htlc_swaps_handler.dart'; +import 'package:zenon_syrius_wallet_flutter/blocs/wallet_connect/chains/i_chain.dart'; +import 'package:zenon_syrius_wallet_flutter/blocs/wallet_connect/chains/nom_service.dart'; import 'package:zenon_syrius_wallet_flutter/blocs/wallet_connect/wallet_connect_pairings_bloc.dart'; import 'package:zenon_syrius_wallet_flutter/blocs/wallet_connect/wallet_connect_sessions_bloc.dart'; import 'package:zenon_syrius_wallet_flutter/model/model.dart'; import 'package:zenon_syrius_wallet_flutter/screens/screens.dart'; import 'package:zenon_syrius_wallet_flutter/services/htlc_swaps_service.dart'; +import 'package:zenon_syrius_wallet_flutter/services/i_web3wallet_service.dart'; import 'package:zenon_syrius_wallet_flutter/services/shared_prefs_service.dart'; -import 'package:zenon_syrius_wallet_flutter/services/wallet_connect_service.dart'; +import 'package:zenon_syrius_wallet_flutter/services/web3wallet_service.dart'; +import 'package:zenon_syrius_wallet_flutter/utils/functions.dart'; import 'package:zenon_syrius_wallet_flutter/utils/utils.dart'; import 'package:zenon_syrius_wallet_flutter/widgets/widgets.dart'; import 'package:znn_sdk_dart/znn_sdk_dart.dart'; @@ -35,6 +39,9 @@ HtlcSwapsService? htlcSwapsService; final sl = GetIt.instance; +IWeb3WalletService? web3WalletService; +final globalNavigatorKey = GlobalKey(); + main() async { WidgetsFlutterBinding.ensureInitialized(); @@ -70,9 +77,14 @@ main() async { windowManager.ensureInitialized(); await windowManager.setPreventClose(true); + web3WalletService = Web3WalletService(); + web3WalletService!.create(); + // Setup services setup(); + await web3WalletService!.init(); + // Setup local_notifier await localNotifier.setup( appName: 's y r i u s', @@ -83,6 +95,9 @@ main() async { // Setup tray manager await _setupTrayManager(); + // Load default community nodes from assets + await _loadDefaultCommunityNodes(); + // Register Hive adapters Hive.registerAdapter(NotificationTypeAdapter()); Hive.registerAdapter(WalletNotificationAdapter()); @@ -160,6 +175,20 @@ Future _setupTrayManager() async { await trayManager.setContextMenu(Menu(items: items)); } +Future _loadDefaultCommunityNodes() async { + try { + var nodes = await loadJsonFromAssets('assets/community-nodes.json') + as List; + kDefaultCommunityNodes = nodes + .map((node) => node.toString()) + .where((node) => InputValidators.node(node) == null) + .toList(); + } catch (e, stackTrace) { + Logger('main') + .log(Level.WARNING, '_loadDefaultCommunityNodes', e, stackTrace); + } +} + void setup() { sl.registerSingleton(Zenon()); zenon = sl(); @@ -167,7 +196,13 @@ void setup() { (() => SharedPrefsService.getInstance().then((value) => value!))); sl.registerSingleton(HtlcSwapsService.getInstance()); - sl.registerLazySingleton(() => WalletConnectService()); + // Initialize WalletConnect service + sl.registerSingleton(web3WalletService!); + sl.registerSingleton( + NoMService(reference: NoMChainId.mainnet), + instanceName: NoMChainId.mainnet.chain(), + ); + sl.registerSingleton(AutoReceiveTxWorker.getInstance()); sl.registerSingleton( AutoUnlockHtlcWorker.getInstance()); @@ -279,6 +314,7 @@ class _MyAppState extends State with WindowListener, TrayListener { child: Layout( child: MaterialApp( title: 's y r i u s', + navigatorKey: globalNavigatorKey, debugShowCheckedModeBanner: false, theme: AppTheme.lightTheme, darkTheme: AppTheme.darkTheme, diff --git a/lib/model/database/notification_type.dart b/lib/model/database/notification_type.dart index e5ea7778..0fd48b24 100644 --- a/lib/model/database/notification_type.dart +++ b/lib/model/database/notification_type.dart @@ -49,4 +49,7 @@ enum NotificationType { @HiveField(14) delete, + + @HiveField(15) + confirm, } diff --git a/lib/model/database/notification_type.g.dart b/lib/model/database/notification_type.g.dart index 3a2a638d..f83f9a33 100644 --- a/lib/model/database/notification_type.g.dart +++ b/lib/model/database/notification_type.g.dart @@ -43,6 +43,8 @@ class NotificationTypeAdapter extends TypeAdapter { return NotificationType.changedNode; case 14: return NotificationType.delete; + case 15: + return NotificationType.confirm; default: return NotificationType.paymentSent; } @@ -96,6 +98,9 @@ class NotificationTypeAdapter extends TypeAdapter { case NotificationType.delete: writer.writeByte(14); break; + case NotificationType.confirm: + writer.writeByte(15); + break; } } diff --git a/lib/model/database/wallet_notification.dart b/lib/model/database/wallet_notification.dart index 5189d964..18814228 100644 --- a/lib/model/database/wallet_notification.dart +++ b/lib/model/database/wallet_notification.dart @@ -78,6 +78,9 @@ class WalletNotification extends HiveObject { return _getCircledIcon(Icons.link); case NotificationType.delete: return _getCircledIcon(Icons.delete_forever); + case NotificationType.confirm: + return _getCircledIcon(Icons.remove_red_eye, + iconColor: AppColors.alertNotification); default: return _getCircledIcon(MaterialCommunityIcons.arrow_top_right); } diff --git a/lib/model/model.dart b/lib/model/model.dart index d43634a3..ce36f258 100644 --- a/lib/model/model.dart +++ b/lib/model/model.dart @@ -9,3 +9,4 @@ export 'pillars_qsr_info.dart'; export 'plasma_info_wrapper.dart'; export 'p2p_swap/p2p_swap.dart'; export 'p2p_swap/htlc_swap.dart'; +export 'sentinels_qsr_info.dart'; diff --git a/lib/model/sentinels_qsr_info.dart b/lib/model/sentinels_qsr_info.dart new file mode 100644 index 00000000..5a627e53 --- /dev/null +++ b/lib/model/sentinels_qsr_info.dart @@ -0,0 +1,9 @@ +class SentinelsQsrInfo { + final BigInt cost; + final BigInt deposit; + + SentinelsQsrInfo({ + required this.cost, + required this.deposit, + }); +} diff --git a/lib/screens/change_wallet_password_screen.dart b/lib/screens/change_wallet_password_screen.dart index 929a6a01..6c382423 100644 --- a/lib/screens/change_wallet_password_screen.dart +++ b/lib/screens/change_wallet_password_screen.dart @@ -1,6 +1,9 @@ +import 'dart:convert'; + import 'package:flutter/material.dart'; import 'package:stacked/stacked.dart'; -import 'package:zenon_syrius_wallet_flutter/blocs/blocs.dart'; +import 'package:zenon_syrius_wallet_flutter/blocs/decrypt_wallet_file_bloc.dart'; +import 'package:zenon_syrius_wallet_flutter/services/htlc_swaps_service.dart'; import 'package:zenon_syrius_wallet_flutter/utils/utils.dart'; import 'package:zenon_syrius_wallet_flutter/widgets/widgets.dart'; import 'package:znn_sdk_dart/znn_sdk_dart.dart'; @@ -145,15 +148,14 @@ class _ChangeWalletPasswordScreenState String currentPassword, String newPassword, ) async { - String mnemonic = kKeyStore!.mnemonic!; - String oldKeyStorePath = kKeyStorePath!; - await KeyStoreUtils.createKeyStore( - mnemonic, - newPassword, - keyStoreName: - '${await kKeyStore!.getKeyPair(0).address}_${DateTime.now().millisecondsSinceEpoch}', + final baseAddress = WalletUtils.baseAddress; + await kWalletFile!.changePassword(currentPassword, newPassword); + await HtlcSwapsService.getInstance().closeBoxes(); + await HtlcSwapsService.getInstance().openBoxes( + baseAddress.toString(), + Crypto.digest(utf8.encode(currentPassword)), + newCipherKey: Crypto.digest(utf8.encode(newPassword)), ); - await FileUtils.deleteFile(oldKeyStorePath); if (!mounted) return; Navigator.pop(context); } @@ -169,20 +171,20 @@ class _ChangeWalletPasswordScreenState } Widget _getDecryptKeyStoreFileViewModel() { - return ViewModelBuilder.reactive( + return ViewModelBuilder.reactive( onViewModelReady: (model) { - model.stream.listen((keyStore) async { - if (keyStore != null) { + model.stream.listen((walletFile) async { + if (walletFile != null) { setState(() { _currentPassErrorText = null; }); try { await _changePassword( - _newPasswordController.text, + _currentPasswordController.text, _newPasswordController.text, ); } catch (e) { - NotificationUtils.sendNotificationError( + await NotificationUtils.sendNotificationError( e, 'An error occurred while trying to change password', ); @@ -190,14 +192,14 @@ class _ChangeWalletPasswordScreenState _loadingButtonKey.currentState!.animateReverse(); } } - }, onError: (e) { + }, onError: (e) async { _loadingButtonKey.currentState!.animateReverse(); if (e is IncorrectPasswordException) { setState(() { _currentPassErrorText = 'Incorrect password'; }); } else { - NotificationUtils.sendNotificationError( + await NotificationUtils.sendNotificationError( e, 'An error occurred while trying to decrypt wallet', ); @@ -208,20 +210,18 @@ class _ChangeWalletPasswordScreenState _loadingButton = _getLoadingButton(model); return _getLoadingButton(model); }, - viewModelBuilder: () => DecryptKeyStoreBloc(), + viewModelBuilder: () => DecryptWalletFileBloc(), ); } - LoadingButton _getLoadingButton(DecryptKeyStoreBloc model) { + LoadingButton _getLoadingButton(DecryptWalletFileBloc model) { return LoadingButton.onboarding( key: _loadingButtonKey, onPressed: _arePasswordsValid() ? () { _loadingButtonKey.currentState!.animateForward(); - model.decryptKeyStoreFile( - kKeyStorePath!, - _currentPasswordController.text, - ); + model.decryptWalletFile( + kWalletPath!, _currentPasswordController.text); } : null, text: 'Change password', diff --git a/lib/screens/dump_mnemonic_screen.dart b/lib/screens/dump_mnemonic_screen.dart index 48c8f5f2..c1b3fc04 100644 --- a/lib/screens/dump_mnemonic_screen.dart +++ b/lib/screens/dump_mnemonic_screen.dart @@ -103,13 +103,14 @@ class _DumpMnemonicScreenState extends State { if (_passwordController.text.isNotEmpty) { try { _continueButtonKey.currentState!.animateForward(); - await KeyStoreUtils.decryptKeyStoreFile( - kKeyStorePath!, + var walletFile = await WalletUtils.decryptWalletFile( + kWalletPath!, _passwordController.text, - ).then((keyStore) { + ); + walletFile.open().then((wallet) { setState(() { _passwordController.clear(); - _seedWords = keyStore.mnemonic!.split(' '); + _seedWords = (wallet as KeyStore).mnemonic!.split(' '); _passwordError = null; }); }); diff --git a/lib/screens/export/export_wallet_password_screen.dart b/lib/screens/export/export_wallet_password_screen.dart index bd5936cd..a489d76c 100644 --- a/lib/screens/export/export_wallet_password_screen.dart +++ b/lib/screens/export/export_wallet_password_screen.dart @@ -144,7 +144,7 @@ class _ExportWalletPasswordScreenState initialDirectory = (await getApplicationDocumentsDirectory()).path; } - final walletPath = await getSavePath( + final walletPath = await getSaveLocation( acceptedTypeGroups: [ const XTypeGroup( label: 'file', @@ -161,17 +161,17 @@ class _ExportWalletPasswordScreenState if (walletPath != null) { KeyStoreManager keyStoreManager = KeyStoreManager( walletPath: Directory( - path.dirname(walletPath), + path.dirname(walletPath.path), ), ); KeyStore keyStore = KeyStore.fromMnemonic(widget.seed); await keyStoreManager.saveKeyStore( keyStore, _passwordController.text, - name: path.basename(walletPath), + name: path.basename(walletPath.path), ); if (widget.backupWalletFlow) { - _sendSuccessNotification(walletPath); + await _sendSuccessNotification(walletPath.path); } else { _updateExportedSeedList(); } @@ -197,8 +197,8 @@ class _ExportWalletPasswordScreenState ).value = exportedSeeds; } - void _sendSuccessNotification(String path) { - sl.get().addNotification( + Future _sendSuccessNotification(String path) async { + await sl.get().addNotification( WalletNotification( title: 'Seed Vault successfully exported', timestamp: DateTime.now().millisecondsSinceEpoch, diff --git a/lib/screens/node_management_screen.dart b/lib/screens/node_management_screen.dart index 507f53ab..64e474f1 100644 --- a/lib/screens/node_management_screen.dart +++ b/lib/screens/node_management_screen.dart @@ -2,6 +2,7 @@ import 'dart:isolate'; import 'package:flutter/material.dart'; import 'package:hive/hive.dart'; +import 'package:logging/logging.dart'; import 'package:wakelock_plus/wakelock_plus.dart'; import 'package:zenon_syrius_wallet_flutter/blocs/blocs.dart'; import 'package:zenon_syrius_wallet_flutter/embedded_node/embedded_node.dart'; @@ -26,6 +27,7 @@ class NodeManagementScreen extends StatefulWidget { class _NodeManagementScreenState extends State { String? _selectedNode; + bool? _autoReceive; final GlobalKey _confirmNodeButtonKey = GlobalKey(); final GlobalKey _addNodeButtonKey = GlobalKey(); @@ -35,11 +37,21 @@ class _NodeManagementScreenState extends State { late String _selectedNodeConfirmed; + @override + void initState() { + super.initState(); + kDefaultCommunityNodes.shuffle(); + _autoReceive = sharedPrefsService!.get( + kAutoReceiveKey, + defaultValue: kAutoReceiveDefaultValue, + ); + } + @override void didChangeDependencies() { super.didChangeDependencies(); - _selectedNode ??= kCurrentNode!; - _selectedNodeConfirmed = _selectedNode!; + _selectedNode ??= kCurrentNode ?? kEmbeddedNode; + _selectedNodeConfirmed = _selectedNode ?? kEmbeddedNode; } @override @@ -61,8 +73,11 @@ class _NodeManagementScreenState extends State { ), kVerticalSpacing, Text( - 'By default Syrius connects to its own built-in full node, which is called the Embedded Node. If you want to connect to a different node, you can add one below. Otherwise just connect and continue.', - style: Theme.of(context).textTheme.headlineMedium, + 'By default Syrius connects to its own built-in full node, which is called the Embedded Node. ' + 'It may take up to 24 hours to fully sync the network via the embedded node. ' + 'During this time, you cannot send or receive transactions.\n\n' + 'It you want to get started right away, please connect to a community node.', + style: Theme.of(context).textTheme.headlineSmall, textAlign: TextAlign.center, ), SizedBox( @@ -82,7 +97,14 @@ class _NodeManagementScreenState extends State { 'Node selection', style: Theme.of(context).textTheme.bodyLarge, ), - _getNodeSelectionColumn(), + _getNodeTiles(), + kVerticalSpacing, + Text( + 'Wallet options', + style: Theme.of(context).textTheme.bodyLarge, + ), + _getAutoReceiveCheckboxContainer(), + _getConfirmNodeSelectionButton(), kVerticalSpacing, Text( 'Add node', @@ -104,15 +126,71 @@ class _NodeManagementScreenState extends State { ); } - Widget _getNodeSelectionColumn() { - return Column( - children: [ - _getNodeTiles(), - _getConfirmNodeSelectionButton(), + Widget _getAutoReceiveCheckboxContainer() { + return Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Checkbox( + value: _autoReceive, + checkColor: Theme.of(context).colorScheme.primary, + activeColor: AppColors.znnColor, + onChanged: (bool? value) async { + if (value == true) { + NodeUtils.getUnreceivedTransactions().then((value) { + sl().autoReceive(); + }).onError((error, stackTrace) { + Logger('MainAppContainer').log(Level.WARNING, + '_getAutoReceiveCheckboxContainer', error, stackTrace); + }); + } else if (value == false && + sl().pool.isNotEmpty) { + sl().pool.clear(); + } + setState(() { + _autoReceive = value; + }); + await _changeAutoReceiveStatus(value ?? false); + }, + ), + Text( + 'Automatically receive transactions', + style: Theme.of(context).textTheme.headlineSmall, + ) ], ); } + Future _changeAutoReceiveStatus(bool enabled) async { + try { + await _saveAutoReceiveValueToCache(enabled); + await _sendAutoReceiveNotification(enabled); + } on Exception catch (e) { + await NotificationUtils.sendNotificationError( + e, + 'Something went wrong while setting automatic receive preference', + ); + } + } + + Future _sendAutoReceiveNotification(bool enabled) async { + await sl.get().addNotification( + WalletNotification( + title: 'Auto-receiver ${enabled ? 'enabled' : 'disabled'}', + details: + 'Auto-receiver preference was ${enabled ? 'enabled' : 'disabled'}', + timestamp: DateTime.now().millisecondsSinceEpoch, + type: NotificationType.paymentSent, + ), + ); + } + + Future _saveAutoReceiveValueToCache(bool enabled) async { + await sharedPrefsService!.put( + kAutoReceiveKey, + enabled, + ); + } + _getConfirmNodeSelectionButton() { return Row( mainAxisAlignment: MainAxisAlignment.center, @@ -134,12 +212,12 @@ class _NodeManagementScreenState extends State { try { _confirmNodeButtonKey.currentState?.animateForward(); - String url = _selectedNode == 'Embedded Node' + String url = _selectedNode == kEmbeddedNode ? kLocalhostDefaultNodeUrl : _selectedNode!; bool isConnectionEstablished = await NodeUtils.establishConnectionToNode(url); - if (_selectedNode == 'Embedded Node') { + if (_selectedNode == kEmbeddedNode) { // Check if node is already running if (!isConnectionEstablished) { // Initialize local full node @@ -154,8 +232,6 @@ class _NodeManagementScreenState extends State { await NodeUtils.establishConnectionToNode(url); } } else { - isConnectionEstablished = - await NodeUtils.establishConnectionToNode(url); if (isConnectionEstablished) { await NodeUtils.closeEmbeddedNode(); } @@ -166,7 +242,7 @@ class _NodeManagementScreenState extends State { _selectedNode, ); kCurrentNode = _selectedNode!; - _sendChangingNodeSuccessNotification(); + await _sendChangingNodeSuccessNotification(); if (widget.nodeConfirmationCallback != null) { widget.nodeConfirmationCallback!(); } else { @@ -176,7 +252,7 @@ class _NodeManagementScreenState extends State { throw 'Connection could not be established to $_selectedNode'; } } catch (e) { - NotificationUtils.sendNotificationError( + await NotificationUtils.sendNotificationError( e, 'Connection failed', ); @@ -230,10 +306,12 @@ class _NodeManagementScreenState extends State { bool _ifUserInputValid() => InputValidators.node(_newNodeController.text) == null; - void _onAddNodePressed() async { - if ([...kDbNodes, ...kDefaultNodes].contains(_newNodeController.text)) { - NotificationUtils.sendNotificationError( - 'Node already exists', 'Node already exists'); + Future _onAddNodePressed() async { + if ([...kDbNodes, ...kDefaultCommunityNodes, ...kDefaultNodes] + .contains(_newNodeController.text)) { + await NotificationUtils.sendNotificationError( + 'Node ${_newNodeController.text} already exists', + 'Node already exists'); } else { _addNodeToDb(); } @@ -247,13 +325,13 @@ class _NodeManagementScreenState extends State { } Hive.box(kNodesBox).add(_newNodeController.text); await NodeUtils.loadDbNodes(); - _sendAddNodeSuccessNotification(); + await _sendAddNodeSuccessNotification(); setState(() { _newNodeController = TextEditingController(); _newNodeKey = GlobalKey(); }); } catch (e) { - NotificationUtils.sendNotificationError(e, 'Error while adding new node'); + await NotificationUtils.sendNotificationError(e, 'Error while adding new node'); } finally { _addNodeButtonKey.currentState?.animateReverse(); } @@ -261,8 +339,11 @@ class _NodeManagementScreenState extends State { Widget _getNodeTiles() { return Column( - children: - [...kDefaultNodes, ...kDbNodes].map((e) => _getNodeTile(e)).toList(), + children: { + ...kDefaultNodes, + ...kDefaultCommunityNodes, + ...kDbNodes + }.toList().map((e) => _getNodeTile(e)).toList(), ); } @@ -297,8 +378,8 @@ class _NodeManagementScreenState extends State { ); } - void _sendChangingNodeSuccessNotification() { - sl.get().addNotification( + Future _sendChangingNodeSuccessNotification() async { + await sl.get().addNotification( WalletNotification( title: 'Successfully connected to $_selectedNode', timestamp: DateTime.now().millisecondsSinceEpoch, @@ -314,8 +395,8 @@ class _NodeManagementScreenState extends State { super.dispose(); } - void _sendAddNodeSuccessNotification() { - sl.get().addNotification( + Future _sendAddNodeSuccessNotification() async { + await sl.get().addNotification( WalletNotification( title: 'Successfully added node ${_newNodeController.text}', timestamp: DateTime.now().millisecondsSinceEpoch, diff --git a/lib/screens/onboarding/access_wallet_screen.dart b/lib/screens/onboarding/access_wallet_screen.dart index 35b2f6e4..5cc18dd2 100644 --- a/lib/screens/onboarding/access_wallet_screen.dart +++ b/lib/screens/onboarding/access_wallet_screen.dart @@ -53,7 +53,7 @@ class _AccessWalletScreenState extends State { context: context, ), AccessWalletFluidCell( - onPressed: null, + onPressed: _onHardwareWalletButtonPressed, buttonIconLocation: 'assets/svg/ic_hardware_wallet.svg', buttonText: 'Hardware wallet', context: context, @@ -82,4 +82,11 @@ class _AccessWalletScreenState extends State { const ImportWalletSeedChoiceScreen(), ); } + + void _onHardwareWalletButtonPressed() { + NavigationUtils.push( + context, + const HardwareWalletDeviceChoiceScreen(), + ); + } } diff --git a/lib/screens/onboarding/create_key_store_screen.dart b/lib/screens/onboarding/create_key_store_screen.dart index 778a2971..bc653974 100644 --- a/lib/screens/onboarding/create_key_store_screen.dart +++ b/lib/screens/onboarding/create_key_store_screen.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:zenon_syrius_wallet_flutter/blocs/blocs.dart'; import 'package:zenon_syrius_wallet_flutter/screens/screens.dart'; import 'package:zenon_syrius_wallet_flutter/utils/utils.dart'; +import 'package:zenon_syrius_wallet_flutter/utils/wallet_file.dart'; import 'package:zenon_syrius_wallet_flutter/widgets/widgets.dart'; class CreateKeyStoreScreen extends StatefulWidget { @@ -21,12 +22,12 @@ class CreateKeyStoreScreen extends StatefulWidget { } class _CreateKeyStoreScreenState extends State { - late KeyStorePathBloc _keyStorePathBloc; + late KeyStoreFileBloc _keyStoreFileBloc; @override void initState() { super.initState(); - _keyStorePathBloc = KeyStorePathBloc() + _keyStoreFileBloc = KeyStoreFileBloc() ..getKeyStorePath( widget.seed, widget.password, @@ -41,8 +42,8 @@ class _CreateKeyStoreScreenState extends State { vertical: 30.0, ), child: Center( - child: StreamBuilder( - stream: _keyStorePathBloc.stream, + child: StreamBuilder( + stream: _keyStoreFileBloc.stream, builder: (_, snapshot) { if (snapshot.hasData) { return Column( diff --git a/lib/screens/onboarding/create_ledger_screen.dart b/lib/screens/onboarding/create_ledger_screen.dart new file mode 100644 index 00000000..4e2d647d --- /dev/null +++ b/lib/screens/onboarding/create_ledger_screen.dart @@ -0,0 +1,82 @@ +import 'package:flutter/material.dart'; +import 'package:zenon_syrius_wallet_flutter/blocs/ledger_wallet_file_bloc.dart'; +import 'package:zenon_syrius_wallet_flutter/screens/screens.dart'; +import 'package:zenon_syrius_wallet_flutter/utils/utils.dart'; +import 'package:zenon_syrius_wallet_flutter/utils/wallet_file.dart'; +import 'package:zenon_syrius_wallet_flutter/widgets/widgets.dart'; +import 'package:znn_sdk_dart/znn_sdk_dart.dart'; + +class CreateLedgerWalletScreen extends StatefulWidget { + final WalletDefinition deviceInfo; + final String password; + final int progressBarNumLevels; + + const CreateLedgerWalletScreen( + this.deviceInfo, + this.password, { + this.progressBarNumLevels = 4, + Key? key, + }) : super(key: key); + + @override + State createState() => + _CreateLedgerWalletScreenState(); +} + +class _CreateLedgerWalletScreenState extends State { + late LedgerWalletFileBloc _ledgerWalletFileBloc; + + @override + void initState() { + super.initState(); + _ledgerWalletFileBloc = LedgerWalletFileBloc() + ..getLedgerWalletPath( + widget.deviceInfo.walletId, + widget.password, + widget.deviceInfo.walletName, + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Container( + padding: const EdgeInsets.symmetric( + vertical: 30.0, + ), + child: Center( + child: StreamBuilder( + stream: _ledgerWalletFileBloc.stream, + builder: (_, snapshot) { + if (snapshot.hasData) { + return Column( + children: [ + ProgressBar( + currentLevel: widget.progressBarNumLevels - 1, + numLevels: widget.progressBarNumLevels, + ), + Expanded( + child: NodeManagementScreen( + nodeConfirmationCallback: () { + NavigationUtils.push( + context, + WalletSuccessScreen( + progressBarNumLevels: widget.progressBarNumLevels, + ), + ); + }, + ), + ), + ], + ); + } else if (snapshot.hasError) { + return SyriusErrorWidget(snapshot.error!); + } + return const SyriusLoadingWidget(); + }, + ), + ), + ), + ); + } +} diff --git a/lib/screens/onboarding/hardware_wallet/hardware_wallet.dart b/lib/screens/onboarding/hardware_wallet/hardware_wallet.dart new file mode 100644 index 00000000..0fa29018 --- /dev/null +++ b/lib/screens/onboarding/hardware_wallet/hardware_wallet.dart @@ -0,0 +1,4 @@ +library hardware_wallet; + +export 'hardware_wallet_password_screen.dart'; +export 'hardware_wallet_device_choice_screen.dart'; diff --git a/lib/screens/onboarding/hardware_wallet/hardware_wallet_device_choice_screen.dart b/lib/screens/onboarding/hardware_wallet/hardware_wallet_device_choice_screen.dart new file mode 100644 index 00000000..7cd3800b --- /dev/null +++ b/lib/screens/onboarding/hardware_wallet/hardware_wallet_device_choice_screen.dart @@ -0,0 +1,334 @@ +import 'package:flutter/material.dart'; +import 'package:hive/hive.dart'; +import 'package:zenon_syrius_wallet_flutter/blocs/notifications_bloc.dart'; +import 'package:zenon_syrius_wallet_flutter/main.dart'; +import 'package:zenon_syrius_wallet_flutter/model/database/notification_type.dart'; +import 'package:zenon_syrius_wallet_flutter/model/database/wallet_notification.dart'; +import 'package:zenon_syrius_wallet_flutter/utils/utils.dart'; +import 'package:zenon_syrius_wallet_flutter/screens/screens.dart'; +import 'package:zenon_syrius_wallet_flutter/widgets/widgets.dart'; +import 'package:znn_ledger_dart/znn_ledger_dart.dart'; +import 'package:znn_sdk_dart/znn_sdk_dart.dart'; + +class HardwareWalletDeviceChoiceScreen extends StatefulWidget { + const HardwareWalletDeviceChoiceScreen({Key? key}) : super(key: key); + + @override + State createState() => + _HardwareWalletDeviceChoiceScreenState(); +} + +class _HardwareWalletDeviceChoiceScreenState + extends State { + final List _walletManagers = [LedgerWalletManager()]; + List _devices = []; + WalletDefinition? _selectedDevice; + final Map> _deviceValueMap = + >{}; + + @override + void initState() { + super.initState(); + _openHiveAddressesBox(); + } + + void _openHiveAddressesBox() { + Hive.boxExists(kAddressesBox).then( + (bool addressesBoxExists) { + if (addressesBoxExists) { + Hive.openBox(kAddressesBox) + .then((Box addressesBox) => addressesBox.clear()); + } + }, + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Container( + padding: const EdgeInsets.symmetric( + vertical: 30.0, + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + children: [ + const ProgressBar( + currentLevel: 1, + numLevels: 4, + ), + const SizedBox( + height: 30.0, + ), + const NotificationWidget(), + Text( + 'Choose your device', + style: Theme.of(context).textTheme.headlineLarge, + ), + kVerticalSpacing, + Text( + 'Please connect and unlock your device', + style: Theme.of(context).textTheme.headlineMedium, + ), + kVerticalSpacing, + SizedBox( + height: 40.0, + child: _getScanDevicesContainer(), + ), + kVerticalSpacing, + SizedBox( + width: 420, + child: Column(children: _getDevices()), + ), + ], + ), + _getActionButtons(), + ], + ), + ), + ); + } + + Widget _getScanDevicesContainer() { + return Container( + decoration: BoxDecoration( + color: Colors.transparent, + borderRadius: const BorderRadius.all(Radius.circular(6.0)), + border: Border.all( + color: AppColors.znnColor, + width: 1.0, + style: BorderStyle.solid, + )), + child: InkWell( + child: FocusableActionDetector( + child: SizedBox( + height: 50.0, + width: 150.0, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + color: Colors.transparent, + child: Icon(Icons.search, + color: + Theme.of(context).textTheme.headlineSmall!.color!, + size: 18.0)), + const SizedBox( + width: 15.0, + ), + Text( + 'Scan devices', + style: Theme.of(context).textTheme.headlineSmall, + ), + ], + ), + ), + ), + onTap: () async { + await _scanDevices(); + }), + ); + } + + Future _scanDevices() async { + List>> futures = _walletManagers + .map((manager) => manager.getWalletDefinitions()) + .toList(); + + List> listOfDefinitions = + await Future.wait(futures); + + // Combine all the iterables into a single list using fold or expand + // For example, using fold: + List combinedList = + listOfDefinitions.fold>( + [], + (previousList, element) => previousList..addAll(element), + ); + + for (var device in combinedList) { + if (!_deviceValueMap.containsKey(device.walletId)) { + _deviceValueMap[device.walletId] = ValueNotifier(null); + } + } + + setState(() { + _devices = combinedList; + _selectedDevice = null; + + for (var valueNotifier in _deviceValueMap.values) { + valueNotifier.value = null; + } + }); + } + + List _getDevices() { + return _devices + .map( + (e) => Row( + children: [ + Radio( + value: e, + groupValue: _selectedDevice, + onChanged: _onDevicePressedCallback, + ), + Expanded( + child: Container( + margin: const EdgeInsets.symmetric( + vertical: 5.0, + ), + child: Row( + children: [ + Expanded( + child: InkWell( + borderRadius: BorderRadius.circular( + 10.0, + ), + onTap: () => _onDevicePressedCallback(e), + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 5.0, vertical: 5.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + _getWalletName(e), + style: Theme.of(context) + .textTheme + .bodyLarge! + .copyWith( + color: Theme.of(context) + .textTheme + .bodyLarge! + .color! + .withOpacity(0.7), + ), + ), + ValueListenableBuilder( + valueListenable: _deviceValueMap[e.walletId]!, + builder: (context, value, _) => SizedBox( + height: 20, + child: value == null + ? const Text( + 'Select to connect the device') + : Text(value, + style: Theme.of(context) + .textTheme + .bodyMedium), + ), + ), + ], + ), + ), + ), + ), + ], + ), + ), + ) + ], + ), + ) + .toList(); + } + + Future _onDevicePressedCallback( + WalletDefinition? walletDefinition) async { + Wallet? wallet; + try { + for (var walletManager in _walletManagers) { + WalletDefinition wd = walletDefinition!; + if (await walletManager.supportsWallet(wd)) { + wallet = await walletManager.getWallet(walletDefinition); + break; + } + } + if (wallet == null) { + throw const LedgerError.connectionError( + origMessage: + 'Not connected, please connect the device and try again.'); + } + final walletAddress = await _getWalletAddress(wallet); + setState(() { + _deviceValueMap[walletDefinition!.walletId]!.value = + walletAddress.toString(); + _selectedDevice = walletDefinition; + }); + } catch (e) { + _mapError(walletDefinition, e); + } finally { + if (wallet != null) { + try { + await (wallet as LedgerWallet).disconnect(); + } catch (_) {} + } + wallet = null; + } + } + + Future
_getWalletAddress(Wallet wallet) async { + final account = await wallet.getAccount(); + if (account is LedgerWalletAccount) { + await sl.get().addNotification( + WalletNotification( + title: + 'Resolving address, please confirm the address on your hardware device', + timestamp: DateTime.now().millisecondsSinceEpoch, + details: 'Confirm address for account index: 0', + type: NotificationType.confirm, + ), + ); + return await account.getAddress(true); + } else { + return await account.getAddress(); + } + } + + void _mapError(WalletDefinition? walletDefinition, Object err) { + String? errorText; + if (err is LedgerError) { + errorText = err.toFriendlyString(); + } else { + errorText = 'Error: $err'; + } + setState(() { + _deviceValueMap[walletDefinition!.walletId]!.value = errorText; + _selectedDevice = null; + }); + } + + String _getWalletName(WalletDefinition walletDefinition) { + if (walletDefinition is LedgerWalletDefinition) { + return 'Ledger ${walletDefinition.walletName}'; + } + return walletDefinition.walletName; + } + + Widget _getActionButtons() { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + OnboardingButton( + onPressed: () { + Navigator.pop(context); + }, + text: 'Go back', + ), + kSpacingBetweenActionButtons, + OnboardingButton( + onPressed: _selectedDevice != null + ? () { + NavigationUtils.push( + context, + HardwareWalletPasswordScreen(_selectedDevice!), + ); + } + : null, + text: 'Continue', + ), + ], + ); + } +} diff --git a/lib/screens/onboarding/hardware_wallet/hardware_wallet_password_screen.dart b/lib/screens/onboarding/hardware_wallet/hardware_wallet_password_screen.dart new file mode 100644 index 00000000..c7b2024d --- /dev/null +++ b/lib/screens/onboarding/hardware_wallet/hardware_wallet_password_screen.dart @@ -0,0 +1,153 @@ +import 'package:flutter/material.dart'; +import 'package:zenon_syrius_wallet_flutter/screens/onboarding/create_ledger_screen.dart'; +import 'package:zenon_syrius_wallet_flutter/utils/utils.dart'; +import 'package:zenon_syrius_wallet_flutter/widgets/widgets.dart'; +import 'package:znn_sdk_dart/znn_sdk_dart.dart'; + +class HardwareWalletPasswordScreen extends StatefulWidget { + final WalletDefinition deviceInfo; + + const HardwareWalletPasswordScreen(this.deviceInfo, {Key? key}) : super(key: key); + + @override + State createState() => + _HardwareWalletPasswordScreenState(); +} + +class _HardwareWalletPasswordScreenState extends State { + final TextEditingController _passwordController = TextEditingController(); + final TextEditingController _confirmPasswordController = + TextEditingController(); + + final GlobalKey _passwordKey = GlobalKey(); + final GlobalKey _confirmPasswordKey = GlobalKey(); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Container( + padding: const EdgeInsets.symmetric( + vertical: 30.0, + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + children: [ + const ProgressBar( + currentLevel: 2, + numLevels: 4, + ), + const SizedBox( + height: 30.0, + ), + Text('Create a wallet password', + style: Theme.of(context).textTheme.headlineLarge), + kVerticalSpacing, + Text( + 'This is the password that will be required to unlock the wallet', + style: Theme.of(context).textTheme.headlineMedium), + const SizedBox( + height: 65.0, + ), + Column( + children: [ + Form( + key: _passwordKey, + autovalidateMode: AutovalidateMode.onUserInteraction, + child: PasswordInputField( + controller: _passwordController, + validator: InputValidators.validatePassword, + onChanged: (value) { + setState(() {}); + }, + hintText: 'Password', + ), + ), + kVerticalSpacing, + Form( + key: _confirmPasswordKey, + autovalidateMode: AutovalidateMode.onUserInteraction, + child: PasswordInputField( + controller: _confirmPasswordController, + validator: (value) => + InputValidators.checkPasswordMatch( + _passwordController.text, value), + onChanged: (value) { + setState(() {}); + }, + hintText: 'Confirm password', + ), + ), + const SizedBox( + height: 35.0, + ), + PasswordProgressBar( + password: _passwordController.text, + passwordKey: _passwordKey, + ), + ], + ), + ], + ), + const DottedBorderInfoWidget( + text: 'Use a password that has at least 8 characters, one ' + 'number, one uppercase letter, one lowercase letter and ' + 'one symbol', + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + _getPassiveButton(), + kSpacingBetweenActionButtons, + _getActionButton() + ], + ), + ], + ), + ), + ); + } + + Widget _getActionButton() { + return OnboardingButton( + onPressed: _arePasswordsValid() + ? () { + NavigationUtils.push( + context, + CreateLedgerWalletScreen( + widget.deviceInfo, + _passwordController.text, + ), + ); + } + : null, + text: 'Confirm password', + ); + } + + Widget _getPassiveButton() { + return OnboardingButton( + onPressed: () { + Navigator.pop(context, false); + }, + text: 'Go back', + ); + } + + bool _arePasswordsValid() { + return InputValidators.validatePassword(_passwordController.text) == null && + InputValidators.checkPasswordMatch( + _passwordController.text, + _confirmPasswordController.text, + ) == + null; + } + + @override + void dispose() { + _confirmPasswordController.dispose(); + _passwordController.dispose(); + super.dispose(); + } +} diff --git a/lib/screens/onboarding/import_wallet/import_wallet_decrypt_screen.dart b/lib/screens/onboarding/import_wallet/import_wallet_decrypt_screen.dart index 65db377a..32577f42 100644 --- a/lib/screens/onboarding/import_wallet/import_wallet_decrypt_screen.dart +++ b/lib/screens/onboarding/import_wallet/import_wallet_decrypt_screen.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:stacked/stacked.dart'; import 'package:zenon_syrius_wallet_flutter/blocs/blocs.dart'; import 'package:zenon_syrius_wallet_flutter/screens/screens.dart'; +import 'package:zenon_syrius_wallet_flutter/utils/wallet_file.dart'; import 'package:zenon_syrius_wallet_flutter/utils/utils.dart'; import 'package:zenon_syrius_wallet_flutter/widgets/widgets.dart'; import 'package:znn_sdk_dart/znn_sdk_dart.dart'; @@ -81,13 +82,13 @@ class _ImportWalletDecryptScreenState extends State { ); } - LoadingButton _getLoadingButton(DecryptKeyStoreBloc model) { + LoadingButton _getLoadingButton(DecryptWalletFileBloc model) { return LoadingButton.onboarding( key: _loadingButtonKey, onPressed: _passwordController.text.isNotEmpty ? () async { _loadingButtonKey.currentState!.animateForward(); - await model.decryptKeyStoreFile( + await model.decryptWalletFile( widget.path, _passwordController.text, ); @@ -107,18 +108,18 @@ class _ImportWalletDecryptScreenState extends State { } _getDecryptKeyStoreFileViewModel() { - return ViewModelBuilder.reactive( + return ViewModelBuilder.reactive( onViewModelReady: (model) { - model.stream.listen((keyStore) { - if (keyStore != null) { + model.stream.listen((walletFile) { + if (walletFile != null && walletFile is KeyStoreWalletFile) { _loadingButtonKey.currentState!.animateReverse(); setState(() { _passwordErrorText = null; }); - NavigationUtils.push( - context, - ImportWalletPasswordScreen(keyStore.mnemonic!), - ); + walletFile + .access((wallet) => Future.value((wallet as KeyStore).mnemonic!)) + .then((value) => NavigationUtils.push( + context, ImportWalletPasswordScreen(value))); } }, onError: (error) { _loadingButtonKey.currentState!.animateReverse(); @@ -137,7 +138,7 @@ class _ImportWalletDecryptScreenState extends State { _loadingButton = _getLoadingButton(model); return _getLoadingButton(model); }, - viewModelBuilder: () => DecryptKeyStoreBloc(), + viewModelBuilder: () => DecryptWalletFileBloc(), ); } diff --git a/lib/screens/onboarding/new_wallet/new_wallet_confirm_seed_screen.dart b/lib/screens/onboarding/new_wallet/new_wallet_confirm_seed_screen.dart index 5599c8bf..603d7aef 100644 --- a/lib/screens/onboarding/new_wallet/new_wallet_confirm_seed_screen.dart +++ b/lib/screens/onboarding/new_wallet/new_wallet_confirm_seed_screen.dart @@ -263,11 +263,27 @@ class _NewWalletConfirmSeedScreenState !seedGridElement.isValid; }, onAccept: (String data) { - _foundMissingRandomElementsIndexes - .add(widget.seedWords.indexOf(data)); - _seedGridElements[seedGridElementIndex].word = data; - if (_randomIndexes.length == - _foundMissingRandomElementsIndexes.length) {} + var element = _seedGridElements[seedGridElementIndex]; + var i = -1; + if (element.word != '') { + while ((i = + widget.seedWords.indexOf(element.word, i + 1)) != + -1) { + if (_foundMissingRandomElementsIndexes.contains(i)) { + _foundMissingRandomElementsIndexes.remove(i); + break; + } + } + } + i = -1; + while ((i = widget.seedWords.indexOf(data, i + 1)) != -1) { + if (!_foundMissingRandomElementsIndexes.contains(i) && + _randomIndexes.contains(i)) { + _foundMissingRandomElementsIndexes.add(i); + break; + } + } + element.word = data; setState(() { _textCursor = seedGridElementIndex; }); @@ -350,16 +366,22 @@ class _NewWalletConfirmSeedScreenState for (var element in _seedGridElements) { int i = _seedGridElements.indexOf(element); element.isValid = element.word == widget.seedWords[i]; + if (!element.isValid) { + _seedError = true; + } } - for (var item in _seedGridElements) { - if (!item.isValid) { - setState(() { - _seedError = true; - }); - break; + if (_seedError) { + for (var element in _seedGridElements) { + int i = _seedGridElements.indexOf(element); + if (_randomIndexes.contains(i)) { + element.isValid = false; + element.word = ''; + } } } + setState(() { + _seedError = true; _foundMissingRandomElementsIndexes = _randomIndexes .where((index) => _seedGridElements[index].isValid) .toList(); diff --git a/lib/screens/onboarding/onboarding.dart b/lib/screens/onboarding/onboarding.dart index cf3484e6..9041a189 100644 --- a/lib/screens/onboarding/onboarding.dart +++ b/lib/screens/onboarding/onboarding.dart @@ -5,3 +5,4 @@ export 'create_key_store_screen.dart'; export 'wallet_success_screen.dart'; export 'import_wallet/import_wallet.dart'; export 'new_wallet/new_wallet.dart'; +export 'hardware_wallet/hardware_wallet.dart'; diff --git a/lib/screens/splash_screen.dart b/lib/screens/splash_screen.dart index 142653c1..f00648df 100644 --- a/lib/screens/splash_screen.dart +++ b/lib/screens/splash_screen.dart @@ -3,11 +3,13 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:hive/hive.dart'; +import 'package:logging/logging.dart'; import 'package:lottie/lottie.dart'; import 'package:zenon_syrius_wallet_flutter/blocs/blocs.dart'; import 'package:zenon_syrius_wallet_flutter/main.dart'; import 'package:zenon_syrius_wallet_flutter/model/model.dart'; import 'package:zenon_syrius_wallet_flutter/screens/screens.dart'; +import 'package:zenon_syrius_wallet_flutter/services/i_web3wallet_service.dart'; import 'package:zenon_syrius_wallet_flutter/utils/utils.dart'; import 'package:zenon_syrius_wallet_flutter/widgets/widgets.dart'; @@ -72,7 +74,7 @@ class _SplashScreenState extends State void _navigateToNextScreen() { _controller.stop(); - return kKeyStorePath != null + return kWalletPath != null ? _checkForDefaultNode() : Navigator.pushReplacementNamed( context, @@ -99,20 +101,43 @@ class _SplashScreenState extends State // after the user creates or imports a new wallet kWalletInitCompleted = false; await sl.get().addNotification(null); - await _deleteKeyStoreFile(); - await Hive.deleteFromDisk(); + NodeUtils.stopWebSocketClient(); + if (sl().pool.isNotEmpty) { + sl().pool.clear(); + } + await _deleteCache(); + await _deleteWalletFile(); if (!mounted) return; await InitUtils.initApp(context); } - Future _deleteCache() async => Future.forEach( - kCacheBoxesToBeDeleted, - (boxName) async => await Hive.deleteBoxFromDisk(boxName), - ); + Future _deleteCache() async { + await Hive.close(); + await Future.forEach( + kCacheBoxesToBeDeleted, + (boxName) async => await Hive.deleteBoxFromDisk(boxName), + ); + await _deleteWeb3Cache(); + } + + Future _deleteWeb3Cache() async { + try { + final web3WalletService = sl(); + for (var pairing in web3WalletService.pairings.value) { + await web3WalletService.deactivatePairing(topic: pairing.topic); + } + } catch (e, stackTrace) { + Logger('SplashScreen') + .log(Level.WARNING, '_deleteWeb3Cache', e, stackTrace); + } + } - Future _deleteKeyStoreFile() async { - await FileUtils.deleteFile(kKeyStorePath!); - kKeyStorePath = null; + Future _deleteWalletFile() async { + await Hive.deleteBoxFromDisk(kKeyStoreBox); + if (kWalletFile != null) kWalletFile!.close(); + kWalletFile = null; + await FileUtils.deleteFile(kWalletPath!); + kWalletPath = null; } void _checkForDefaultNode() => sharedPrefsService!.get( diff --git a/lib/services/htlc_swaps_service.dart b/lib/services/htlc_swaps_service.dart index 2aefc1a5..ce6b2f8c 100644 --- a/lib/services/htlc_swaps_service.dart +++ b/lib/services/htlc_swaps_service.dart @@ -19,10 +19,20 @@ class HtlcSwapsService { bool get isMaxSwapsReached => _htlcSwapsBox!.length >= kMaxP2pSwapsToStore; - Future openBoxes(String htlcSwapsBoxSuffix, List cipherKey) async { + Future openBoxes(String htlcSwapsBoxSuffix, List cipherKey, + {List? newCipherKey}) async { if (_htlcSwapsBox == null || !_htlcSwapsBox!.isOpen) { _htlcSwapsBox = await Hive.openBox('${kHtlcSwapsBox}_$htlcSwapsBoxSuffix', encryptionCipher: HiveAesCipher(cipherKey)); + if (newCipherKey != null) { + final values = _htlcSwapsBox!.toMap(); + await _htlcSwapsBox!.deleteFromDisk(); + _htlcSwapsBox = await Hive.openBox( + '${kHtlcSwapsBox}_$htlcSwapsBoxSuffix', + encryptionCipher: HiveAesCipher(newCipherKey)); + _htlcSwapsBox!.putAll(values); + _htlcSwapsBox!.flush(); + } } if (_lastCheckedHtlcBlockHeightBox == null || @@ -30,6 +40,27 @@ class HtlcSwapsService { _lastCheckedHtlcBlockHeightBox = await Hive.openBox( kLastCheckedHtlcBlockBox, encryptionCipher: HiveAesCipher(cipherKey)); + if (newCipherKey != null) { + final values = _lastCheckedHtlcBlockHeightBox!.toMap(); + await _lastCheckedHtlcBlockHeightBox!.deleteFromDisk(); + _lastCheckedHtlcBlockHeightBox = await Hive.openBox( + kLastCheckedHtlcBlockBox, + encryptionCipher: HiveAesCipher(newCipherKey)); + _lastCheckedHtlcBlockHeightBox!.putAll(values); + _lastCheckedHtlcBlockHeightBox!.flush(); + } + } + } + + Future closeBoxes() async { + if (_htlcSwapsBox != null && _htlcSwapsBox!.isOpen) { + await _htlcSwapsBox!.close(); + _htlcSwapsBox = null; + } + if (_lastCheckedHtlcBlockHeightBox != null && + _lastCheckedHtlcBlockHeightBox!.isOpen) { + await _lastCheckedHtlcBlockHeightBox!.close(); + _lastCheckedHtlcBlockHeightBox = null; } } diff --git a/lib/services/i_web3wallet_service.dart b/lib/services/i_web3wallet_service.dart new file mode 100644 index 00000000..f7756c1f --- /dev/null +++ b/lib/services/i_web3wallet_service.dart @@ -0,0 +1,26 @@ +import 'package:flutter/material.dart'; +import 'package:get_it/get_it.dart'; +import 'package:walletconnect_flutter_v2/walletconnect_flutter_v2.dart'; + +abstract class IWeb3WalletService extends Disposable { + abstract ValueNotifier> pairings; + abstract ValueNotifier> sessions; + abstract ValueNotifier> auth; + + void create(); + Future init(); + Web3Wallet getWeb3Wallet(); + Future pair(Uri uri); + Future activatePairing({ + required String topic, + }); + Future deactivatePairing({ + required String topic, + }); + Map getSessionsForPairing(String pairingTopic); + Map getActiveSessions(); + Future disconnectSessions(); + Future disconnectSession({required String topic}); + Future emitAddressChangeEvent(String newAddress); + Future emitChainIdChangeEvent(String newChainId); +} diff --git a/lib/services/wallet_connect_service.dart b/lib/services/wallet_connect_service.dart deleted file mode 100644 index 681bb0b6..00000000 --- a/lib/services/wallet_connect_service.dart +++ /dev/null @@ -1,644 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:logging/logging.dart'; -import 'package:walletconnect_flutter_v2/walletconnect_flutter_v2.dart'; -import 'package:window_manager/window_manager.dart'; -import 'package:zenon_syrius_wallet_flutter/blocs/blocs.dart'; -import 'package:zenon_syrius_wallet_flutter/blocs/wallet_connect/wallet_connect_pairings_bloc.dart'; -import 'package:zenon_syrius_wallet_flutter/blocs/wallet_connect/wallet_connect_sessions_bloc.dart'; -import 'package:zenon_syrius_wallet_flutter/main.dart'; -import 'package:zenon_syrius_wallet_flutter/model/model.dart'; -import 'package:zenon_syrius_wallet_flutter/utils/functions.dart'; -import 'package:zenon_syrius_wallet_flutter/utils/utils.dart'; -import 'package:zenon_syrius_wallet_flutter/widgets/main_app_container.dart'; -import 'package:zenon_syrius_wallet_flutter/widgets/reusable_widgets/dialogs.dart'; -import 'package:zenon_syrius_wallet_flutter/widgets/reusable_widgets/icons/link_icon.dart'; -import 'package:znn_sdk_dart/znn_sdk_dart.dart'; - -class WalletConnectService { - WalletConnectService._internal(); - - factory WalletConnectService() => _instance; - - static final WalletConnectService _instance = - WalletConnectService._internal(); - - final List _idSessionsApproved = []; - - late Web3Wallet _wcClient; - late BuildContext _context; - - final List dAppsActiveSessions = []; - - set context(BuildContext context) => _context = context; - - final _walletLockedError = const WalletConnectError( - code: 9000, - message: 'Wallet is locked', - ); - - Future initClient() async { - if (kWcProjectId.isNotEmpty) { - _wcClient = await Web3Wallet.createInstance( - projectId: kWcProjectId, - metadata: const PairingMetadata( - name: 's y r i u s', - description: 'A wallet for interacting with Zenon Network', - url: 'https://zenon.network', - icons: [ - 'https://raw.githubusercontent.com/zenon-network/syrius/master/macos/Runner/Assets.xcassets/AppIcon.appiconset/Icon-MacOS-512x512%402x.png' - ], - ), - ).onError((e, stackTrace) { - Logger('WalletConnectService') - .log(Level.SEVERE, 'initClient onError ', e, stackTrace); - if (e != null) { - NotificationUtils.sendNotificationError( - e, 'WalletConnect initialization failed'); - } - throw 'WalletConnect init failed'; - }); - - for (var pairingInfo in pairings) { - dAppsActiveSessions - .addAll(getSessionsForPairing(pairingInfo.topic).values); - - Logger('WalletConnectService') - .log(Level.INFO, 'active pairings: $pairingInfo'); - } - Logger('WalletConnectService') - .log(Level.INFO, 'pairings num: ${pairings.length}'); - Logger('WalletConnectService') - .log(Level.INFO, 'active sessions: ${getActiveSessions()}'); - _initListeners(); - _initialChecks(); - } else { - Logger('WalletConnectService').log(Level.INFO, 'kWcProjectId missing'); - return; - } - return; - } - - Future pair(Uri uri) => _wcClient.pair(uri: uri); - - void _initListeners() { - _wcClient.onSessionProposal.subscribe(onSessionProposal); - - _wcClient.core.relayClient.onRelayClientDisconnect.subscribe((args) { - Logger('WalletConnectService').log( - Level.INFO, 'onRelayClientDisconnect triggered', args.toString()); - _wcClient.core.relayClient.connect(); - }); - - _wcClient.core.pairing.onPairingCreate.subscribe((args) { - Logger('WalletConnectService') - .log(Level.INFO, 'onPairingCreate triggered', args.toString()); - sl.get().refreshResults(); - }); - - _wcClient.core.pairing.onPairingActivate.subscribe((args) { - Logger('WalletConnectService') - .log(Level.INFO, 'onPairingActivate triggered', args.toString()); - sl.get().refreshResults(); - }); - - _wcClient.core.pairing.onPairingInvalid.subscribe((args) { - Logger('WalletConnectService') - .log(Level.INFO, 'onPairingInvalid triggered', args.toString()); - }); - - _wcClient.core.pairing.onPairingPing.subscribe((args) { - Logger('WalletConnectService') - .log(Level.INFO, 'onPairingPing triggered', args.toString()); - }); - - _wcClient.core.pairing.onPairingDelete.subscribe((args) { - Logger('WalletConnectService') - .log(Level.INFO, 'onPairingDelete triggered', args.toString()); - }); - - _wcClient.core.pairing.onPairingExpire.subscribe((args) { - Logger('WalletConnectService') - .log(Level.INFO, 'onPairingExpire triggered', args.toString()); - }); - - _wcClient.onSessionPing.subscribe((args) { - Logger('WalletConnectService') - .log(Level.INFO, 'onSessionPing triggered', args.toString()); - }); - - _wcClient.onSessionDelete.subscribe((args) { - Logger('WalletConnectService') - .log(Level.INFO, 'onSessionDelete triggered', args.toString()); - sl.get().refreshResults(); - }); - - _wcClient.onSessionProposalError.subscribe((args) { - Logger('WalletConnectService') - .log(Level.INFO, 'onSessionProposalError triggered', args.toString()); - sl.get().refreshResults(); - }); - - _wcClient.onSessionConnect.subscribe((args) { - Logger('WalletConnectService') - .log(Level.INFO, 'onSessionConnect triggered', args.toString()); - Future.delayed(const Duration(seconds: 3)).then( - (value) => sl.get().refreshResults()); - }); - - _wcClient.onSessionRequest.subscribe((SessionRequestEvent? request) async { - Logger('WalletConnectService') - .log(Level.INFO, 'onSessionRequest triggered', request.toString()); - }); - - _wcClient.onAuthRequest.subscribe((args) { - Logger('WalletConnectService') - .log(Level.INFO, 'onAuthRequest triggered', args.toString()); - }); - - _wcClient.registerRequestHandler( - chainId: 'zenon:1', - method: 'znn_info', - handler: (topic, params) async { - if (!await windowManager.isFocused() || - !await windowManager.isVisible()) { - windowManager.show(); - } - final dAppMetadata = dAppsActiveSessions - .firstWhere((element) => element.topic == topic) - .peer - .metadata; - - if (kCurrentPage != Tabs.lock) { - if (_context.mounted) { - final actionWasAccepted = await showDialogWithNoAndYesOptions( - context: _context, - isBarrierDismissible: false, - title: '${dAppMetadata.name} - Information', - content: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Text('Are you sure you want to allow ${dAppMetadata.name} to ' - 'retrieve the current address, node URL and chain identifier information?'), - kVerticalSpacing, - Image( - image: NetworkImage(dAppMetadata.icons.first), - height: 100.0, - fit: BoxFit.fitHeight, - ), - kVerticalSpacing, - Text(dAppMetadata.description), - kVerticalSpacing, - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text(dAppMetadata.url), - LinkIcon( - url: dAppMetadata.url, - ) - ], - ), - ], - ), - onYesButtonPressed: () async {}, - onNoButtonPressed: () {}, - ); - - if (actionWasAccepted) { - return { - 'address': kSelectedAddress, - 'nodeUrl': kCurrentNode, - 'chainId': getChainIdentifier(), - }; - } else { - NotificationUtils.sendNotificationError( - Errors.getSdkError(Errors.USER_REJECTED), - 'You have rejected the WalletConnect request'); - throw Errors.getSdkError(Errors.USER_REJECTED); - } - } else { - throw _walletLockedError; - } - } else { - throw _walletLockedError; - } - }, - ); - - _wcClient.onAuthRequest.subscribe((AuthRequest? args) async { - Logger('WalletConnectService') - .log(Level.INFO, 'onAuthRequest triggered', args.toString()); - }); - - _wcClient.registerRequestHandler( - chainId: 'zenon:1', - method: 'znn_sign', - handler: (topic, params) async { - if (!await windowManager.isFocused() || - !await windowManager.isVisible()) { - windowManager.show(); - } - final dAppMetadata = dAppsActiveSessions - .firstWhere((element) => element.topic == topic) - .peer - .metadata; - if (kCurrentPage != Tabs.lock) { - final message = params as String; - - if (_context.mounted) { - final actionWasAccepted = await showDialogWithNoAndYesOptions( - context: _context, - isBarrierDismissible: false, - title: '${dAppMetadata.name} - Sign Message', - content: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Text('Are you sure you want to ' - 'sign message $message ?'), - kVerticalSpacing, - Image( - image: NetworkImage(dAppMetadata.icons.first), - height: 100.0, - fit: BoxFit.fitHeight, - ), - kVerticalSpacing, - Text(dAppMetadata.description), - kVerticalSpacing, - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text(dAppMetadata.url), - LinkIcon( - url: dAppMetadata.url, - ) - ], - ), - ], - ), - onYesButtonPressed: () async {}, - onNoButtonPressed: () {}, - ); - - if (actionWasAccepted) { - return await walletSign(message.codeUnits); - } else { - NotificationUtils.sendNotificationError( - Errors.getSdkError(Errors.USER_REJECTED), - 'You have rejected the WalletConnect request'); - throw Errors.getSdkError(Errors.USER_REJECTED); - } - } else { - throw _walletLockedError; - } - } else { - throw _walletLockedError; - } - }, - ); - - _wcClient.registerRequestHandler( - chainId: 'zenon:1', - method: 'znn_send', - handler: (topic, params) async { - if (!await windowManager.isFocused() || - !await windowManager.isVisible()) { - windowManager.show(); - } - final dAppMetadata = dAppsActiveSessions - .firstWhere((element) => element.topic == topic) - .peer - .metadata; - if (kCurrentPage != Tabs.lock) { - final accountBlock = - AccountBlockTemplate.fromJson(params['accountBlock']); - - final toAddress = ZenonAddressUtils.getLabel( - accountBlock.toAddress.toString(), - ); - - final token = - await zenon!.embedded.token.getByZts(accountBlock.tokenStandard); - - final amount = accountBlock.amount.addDecimals(token!.decimals); - - final sendPaymentBloc = SendPaymentBloc(); - - if (_context.mounted) { - final wasActionAccepted = await showDialogWithNoAndYesOptions( - context: _context, - isBarrierDismissible: false, - title: '${dAppMetadata.name} - Send Payment', - content: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Text('Are you sure you want to transfer ' - '$amount ${token.symbol} to ' - '$toAddress ?'), - kVerticalSpacing, - Image( - image: NetworkImage(dAppMetadata.icons.first), - height: 100.0, - fit: BoxFit.fitHeight, - ), - kVerticalSpacing, - Text(dAppMetadata.description), - kVerticalSpacing, - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text(dAppMetadata.url), - LinkIcon( - url: dAppMetadata.url, - ) - ], - ), - ], - ), - description: 'Are you sure you want to transfer ' - '$amount ${token.symbol} to ' - '$toAddress ?', - onYesButtonPressed: () {}, - onNoButtonPressed: () {}, - ); - - if (wasActionAccepted) { - sendPaymentBloc.sendTransfer( - fromAddress: params['fromAddress'], - block: AccountBlockTemplate.fromJson(params['accountBlock']), - ); - - final result = await sendPaymentBloc.stream.firstWhere( - (element) => element != null, - ); - - return result!; - } else { - NotificationUtils.sendNotificationError( - Errors.getSdkError(Errors.USER_REJECTED), - 'You have rejected the WalletConnect request'); - throw Errors.getSdkError(Errors.USER_REJECTED); - } - } else { - throw _walletLockedError; - } - } else { - throw _walletLockedError; - } - }, - ); - } - - List get pairings => _wcClient.pairings.getAll(); - - Future approveSession( - {required int id, Map? namespaces}) async { - if (!await windowManager.isFocused() || !await windowManager.isVisible()) { - windowManager.show(); - } - namespaces = namespaces ?? - { - 'zenon': Namespace( - accounts: _getWalletAccounts(), - methods: [ - 'znn_sign', - 'znn_info', - 'znn_send', - ], - events: ['chainIdChange', 'addressChange'], - ), - }; - return _wcClient.approveSession( - id: id, - namespaces: namespaces, - ); - } - - Future rejectSession({ - required int id, - required WalletConnectError reason, - }) => - _wcClient.rejectSession(id: id, reason: reason); - - String _generateAccount(String address, int chainId) => - '$kZenonNameSpace:1:$address'; - - List _getWalletAccounts() => kAddressLabelMap.values - .map( - (address) => _generateAccount(address, getChainIdentifier()), - ) - .toList(); - - Future activatePairing({ - required String topic, - }) => - _wcClient.core.pairing.activate( - topic: topic, - ); - - Future deactivatePairing({ - required String topic, - }) async { - try { - _wcClient.core.pairing.disconnect(topic: topic); - } on WalletConnectError catch (e) { - // technically look for WalletConnectError 6 : Expired. to consider it a warning - Logger('WalletConnectService') - .log(Level.INFO, 'deactivatePairing ${e.code} : ${e.message}'); - } catch (e, s) { - // Catch anything else (not just Exceptions) and log stack - Logger('WalletConnectService').log(Level.INFO, - 'disconnectAllParings - Unexpected error: $e, topic $topic\n$s'); - } - } - - // Future disconnectSessions() async { - // IPairingStore pairingStore = getPairings(); - // pairingStore.getAll().forEach((element) async { - // await _wcClient.disconnectSession( - // topic: element.topic, - // reason: Errors.getSdkError(Errors.USER_DISCONNECTED)); - // }); - // } - - Future disconnectSession({required String topic}) async => - _wcClient.disconnectSession( - topic: topic, - reason: Errors.getSdkError(Errors.USER_DISCONNECTED), - ); - - Future _emitEventForTheDApps({ - required String changeName, - required String newValue, - }) async { - final sessionTopics = - pairings.fold>([], (previousValue, pairing) { - if (pairing.active) { - var sessionsPerPairing = getSessionsForPairing(pairing.topic).keys; - previousValue.addAll(sessionsPerPairing); - return previousValue; - } - return previousValue; - }); - - for (var sessionTopic in sessionTopics) { - _emitEventForADApp( - sessionTopic: sessionTopic, - changeName: changeName, - newValue: newValue, - ); - } - } - - Future _emitEventForADApp({ - required String sessionTopic, - required String changeName, - required String newValue, - }) => - _wcClient.emitSessionEvent( - topic: sessionTopic, - chainId: 'zenon:1', - event: SessionEventParams( - name: changeName, - data: newValue, - ), - ); - - Future emitAddressChangeEvent(String newAddress) { - return _emitEventForTheDApps( - changeName: 'addressChange', - newValue: newAddress, - ); - } - - Future emitChainIdChangeEvent(String newChainId) { - return _emitEventForTheDApps( - changeName: 'chainIdChange', - newValue: newChainId, - ); - } - - Map getActiveSessions() => _wcClient.getActiveSessions(); - - Map getSessionsForPairing(String pairingTopic) => - _wcClient.getSessionsForPairing( - pairingTopic: pairingTopic, - ); - - void _sendSuccessfullyApprovedSessionNotification( - PairingMetadata dAppMetadata) { - sl.get().addNotification( - WalletNotification( - title: 'Successfully connected to ${dAppMetadata.name}', - timestamp: DateTime.now().millisecondsSinceEpoch, - details: 'Successfully connected to ${dAppMetadata.name} ' - 'via WalletConnect', - type: NotificationType.paymentSent, - ), - ); - } - - void onSessionProposal(SessionProposalEvent? event) async { - Logger('WalletConnectService') - .log(Level.INFO, 'onSessionProposal triggered', event.toString()); - - if (event != null) { - Logger('WalletConnectService') - .log(Level.INFO, 'session proposal event', event.params.toJson()); - - final dAppMetadata = event.params.proposer.metadata; - - final actionWasAccepted = await showDialogWithNoAndYesOptions( - context: _context, - isBarrierDismissible: false, - title: 'Approve session', - content: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Text('Are you sure you want to ' - 'connect to ${dAppMetadata.name} ?'), - kVerticalSpacing, - Image( - image: NetworkImage(dAppMetadata.icons.first), - height: 100.0, - fit: BoxFit.fitHeight, - ), - kVerticalSpacing, - Text(dAppMetadata.description), - kVerticalSpacing, - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text(dAppMetadata.url), - LinkIcon( - url: dAppMetadata.url, - ) - ], - ), - ], - ), - onYesButtonPressed: () async {}, - onNoButtonPressed: () {}, - ); - - if (actionWasAccepted) { - if (!_idSessionsApproved.contains(event.id)) { - _idSessionsApproved.add(event.id); - ApproveResponse approveResponse = await _wcClient.approveSession( - id: event.id, - namespaces: { - 'zenon': Namespace( - accounts: _getWalletAccounts(), - methods: [ - 'znn_sign', - 'znn_info', - 'znn_send', - ], - events: [ - 'chainIdChange', - 'addressChange', - ], - ), - }, - ); - - _sendSuccessfullyApprovedSessionNotification(dAppMetadata); - dAppsActiveSessions.add(approveResponse.session); - } - } else { - await _wcClient.rejectSession( - id: event.id, - reason: Errors.getSdkError( - Errors.USER_REJECTED, - ), - ); - } - } - } - - void _initialChecks() { - final pendingProposals = _wcClient.getPendingSessionProposals(); - Logger('WalletConnectService').log( - Level.INFO, 'checkForPendingRequests', pendingProposals.keys.length); - if (pendingProposals.isNotEmpty) { - pendingProposals.forEach((key, value) { - _wcClient.approveSession(id: value.id, namespaces: { - 'zenon': Namespace( - accounts: _getWalletAccounts(), - methods: [ - 'znn_sign', - 'znn_info', - 'znn_send', - ], - events: [ - 'chainIdChange', - 'addressChange', - ], - ), - }); - }); - } - } -} diff --git a/lib/services/web3wallet_service.dart b/lib/services/web3wallet_service.dart new file mode 100644 index 00000000..5b67f2e5 --- /dev/null +++ b/lib/services/web3wallet_service.dart @@ -0,0 +1,447 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:logging/logging.dart'; +import 'package:walletconnect_flutter_v2/walletconnect_flutter_v2.dart'; +import 'package:window_manager/window_manager.dart'; +import 'package:zenon_syrius_wallet_flutter/blocs/notifications_bloc.dart'; +import 'package:zenon_syrius_wallet_flutter/blocs/wallet_connect/wallet_connect_pairings_bloc.dart'; +import 'package:zenon_syrius_wallet_flutter/blocs/wallet_connect/wallet_connect_sessions_bloc.dart'; +import 'package:zenon_syrius_wallet_flutter/main.dart'; +import 'package:zenon_syrius_wallet_flutter/model/database/notification_type.dart'; +import 'package:zenon_syrius_wallet_flutter/model/database/wallet_notification.dart'; +import 'package:zenon_syrius_wallet_flutter/services/i_web3wallet_service.dart'; +import 'package:zenon_syrius_wallet_flutter/utils/utils.dart'; +import 'package:zenon_syrius_wallet_flutter/widgets/reusable_widgets/dialogs.dart'; +import 'package:zenon_syrius_wallet_flutter/widgets/reusable_widgets/icons/link_icon.dart'; +import 'package:znn_sdk_dart/znn_sdk_dart.dart'; + +class Web3WalletService extends IWeb3WalletService { + Web3Wallet? _wcClient; + + /// The list of requests from the dapp + /// Potential types include, but aren't limited to: + /// [SessionProposalEvent], [AuthRequest] + @override + ValueNotifier> pairings = + ValueNotifier>([]); + @override + ValueNotifier> sessions = + ValueNotifier>([]); + @override + ValueNotifier> auth = ValueNotifier>([]); + + final List _idSessionsApproved = []; + + @override + void create() { + if (kWcProjectId.isNotEmpty) { + _wcClient = Web3Wallet( + core: Core( + projectId: kWcProjectId, + ), + metadata: const PairingMetadata( + name: 's y r i u s', + description: 'A wallet for interacting with Zenon Network', + url: 'https://zenon.network', + icons: [ + 'https://raw.githubusercontent.com/zenon-network/syrius/master/macos/Runner/Assets.xcassets/AppIcon.appiconset/Icon-MacOS-512x512%402x.png' + ], + ), + ); + + // Setup our listeners + _wcClient!.core.relayClient.onRelayClientConnect + .subscribe(_onRelayClientConnect); + _wcClient!.core.relayClient.onRelayClientDisconnect + .subscribe(_onRelayClientDisconnect); + _wcClient!.core.relayClient.onRelayClientError + .subscribe(_onRelayClientError); + + _wcClient!.core.pairing.onPairingCreate.subscribe(_onPairingCreate); + _wcClient!.core.pairing.onPairingActivate.subscribe(_onPairingActivate); + _wcClient!.core.pairing.onPairingPing.subscribe(_onPairingPing); + _wcClient!.core.pairing.onPairingInvalid.subscribe(_onPairingInvalid); + _wcClient!.core.pairing.onPairingDelete.subscribe(_onPairingDelete); + + _wcClient!.pairings.onSync.subscribe(_onPairingsSync); + _wcClient!.sessions.onSync.subscribe(_onSessionsSync); + + _wcClient!.onSessionProposal.subscribe(_onSessionProposal); + _wcClient!.onSessionConnect.subscribe(_onSessionConnect); + _wcClient!.onSessionRequest.subscribe(_onSessionRequest); + _wcClient!.onSessionProposalError.subscribe(_onSessionProposalError); + _wcClient!.onSessionDelete.subscribe(_onSessionDelete); + // _wcClient!.onAuthRequest.subscribe(_onAuthRequest); + } else { + Logger('WalletConnectService').log(Level.INFO, 'kWcProjectId missing'); + } + } + + @override + Future init() async { + // Await the initialization of the web3wallet + Logger('WalletConnectService').log(Level.INFO, 'initialization'); + await _wcClient!.init(); + + pairings.value = _wcClient!.pairings.getAll(); + sessions.value = _wcClient!.sessions.getAll(); + auth.value = _wcClient!.completeRequests.getAll(); + } + + @override + FutureOr onDispose() { + _wcClient!.core.relayClient.onRelayClientConnect + .unsubscribe(_onRelayClientConnect); + _wcClient!.core.relayClient.onRelayClientDisconnect + .unsubscribe(_onRelayClientDisconnect); + _wcClient!.core.relayClient.onRelayClientError + .unsubscribe(_onRelayClientError); + + _wcClient!.core.pairing.onPairingCreate.unsubscribe(_onPairingCreate); + _wcClient!.core.pairing.onPairingActivate.unsubscribe(_onPairingActivate); + _wcClient!.core.pairing.onPairingPing.unsubscribe(_onPairingPing); + _wcClient!.core.pairing.onPairingInvalid.unsubscribe(_onPairingInvalid); + _wcClient!.core.pairing.onPairingDelete.unsubscribe(_onPairingDelete); + + _wcClient!.pairings.onSync.unsubscribe(_onPairingsSync); + _wcClient!.sessions.onSync.unsubscribe(_onSessionsSync); + + _wcClient!.onSessionProposal.unsubscribe(_onSessionProposal); + _wcClient!.onSessionConnect.unsubscribe(_onSessionConnect); + _wcClient!.onSessionRequest.unsubscribe(_onSessionRequest); + _wcClient!.onSessionProposalError.unsubscribe(_onSessionProposalError); + _wcClient!.onSessionDelete.unsubscribe(_onSessionDelete); + // _wcClient!.onAuthRequest.unsubscribe(_onAuthRequest); + + pairings.dispose(); + sessions.dispose(); + auth.dispose(); + } + + @override + Web3Wallet getWeb3Wallet() { + return _wcClient!; + } + + @override + Future pair(Uri uri) { + return _wcClient!.pair(uri: uri); + } + + @override + Future activatePairing({ + required String topic, + }) { + return _wcClient!.core.pairing.activate( + topic: topic, + ); + } + + @override + Future deactivatePairing({ + required String topic, + }) async { + try { + _wcClient!.core.pairing.disconnect(topic: topic); + _idSessionsApproved.clear(); + } on WalletConnectError catch (e) { + // technically look for WalletConnectError 6 : Expired. to consider it a warning + Logger('WalletConnectService') + .log(Level.INFO, 'deactivatePairing ${e.code} : ${e.message}'); + } catch (e, s) { + // Catch anything else (not just Exceptions) and log stack + Logger('WalletConnectService').log(Level.INFO, + 'disconnectAllParings - Unexpected error: $e, topic $topic\n$s'); + } + } + + @override + Map getSessionsForPairing(String pairingTopic) { + return _wcClient!.getSessionsForPairing( + pairingTopic: pairingTopic, + ); + } + + @override + Future emitAddressChangeEvent(String newAddress) { + return _emitEventPairedDApps( + changeName: 'addressChange', + newValue: newAddress, + ); + } + + @override + Future emitChainIdChangeEvent(String newChainId) { + return _emitEventPairedDApps( + changeName: 'chainIdChange', + newValue: newChainId, + ); + } + + @override + Future disconnectSessions() async { + Logger('WalletConnectService') + .log(Level.INFO, 'disconnectSessions triggered'); + for (int i = 0; i < pairings.value.length; i++) { + await _wcClient!.disconnectSession( + topic: pairings.value[i].topic, + reason: Errors.getSdkError(Errors.USER_DISCONNECTED)); + } + _idSessionsApproved.clear(); + } + + @override + Future disconnectSession({required String topic}) async { + Logger('WalletConnectService') + .log(Level.INFO, 'disconnectSession triggered', topic); + _wcClient!.disconnectSession( + topic: topic, + reason: Errors.getSdkError(Errors.USER_DISCONNECTED), + ); + } + + @override + Map getActiveSessions() { + Logger('WalletConnectService') + .log(Level.INFO, 'getActiveSessions triggered'); + return _wcClient!.getActiveSessions(); + } + + void _onRelayClientConnect(var args) async { + Logger('WalletConnectService') + .log(Level.INFO, '_onRelayClientConnect triggered', args.toString()); + } + + void _onRelayClientDisconnect(var args) async { + Logger('WalletConnectService') + .log(Level.INFO, '_onRelayClientDisconnect triggered', args.toString()); + } + + void _onRelayClientError(var args) async { + Logger('WalletConnectService') + .log(Level.INFO, '_onRelayClientError triggered', args.toString()); + } + + void _onSessionsSync(StoreSyncEvent? args) async { + if (args != null) { + Logger('WalletConnectService') + .log(Level.INFO, '_onSessionsSync triggered', args.toString()); + sessions.value = _wcClient!.sessions.getAll(); + } + } + + void _onPairingCreate(PairingEvent? args) async { + Logger('WalletConnectService') + .log(Level.INFO, 'onPairingCreate triggered', args.toString()); + sl.get().refreshResults(); + } + + void _onPairingActivate(PairingActivateEvent? args) async { + Logger('WalletConnectService') + .log(Level.INFO, '_onPairingActivate triggered', args.toString()); + sl.get().refreshResults(); + } + + void _onPairingPing(PairingEvent? args) async { + Logger('WalletConnectService') + .log(Level.INFO, '_onPairingPing triggered', args.toString()); + } + + void _onPairingInvalid(PairingInvalidEvent? args) { + Logger('WalletConnectService') + .log(Level.INFO, 'onPairingInvalid triggered', args.toString()); + } + + void _onPairingDelete(PairingEvent? args) async { + Logger('WalletConnectService') + .log(Level.INFO, '_onPairingDelete triggered', args.toString()); + } + + void _onPairingsSync(StoreSyncEvent? args) async { + if (args != null) { + Logger('WalletConnectService') + .log(Level.INFO, '_onPairingsSync triggered', args.toString()); + pairings.value = _wcClient!.pairings.getAll(); + } + } + + void _onSessionRequest(SessionRequestEvent? args) async { + Logger('WalletConnectService') + .log(Level.INFO, '_onSessionRequest triggered', args.toString()); + } + + void _onSessionDelete(SessionDelete? args) { + Logger('WalletConnectService') + .log(Level.INFO, '_onSessionDelete triggered', args.toString()); + sl.get().refreshResults(); + } + + void _onSessionProposalError(SessionProposalErrorEvent? args) { + Logger('WalletConnectService') + .log(Level.INFO, '_onSessionProposalError triggered', args.toString()); + sl.get().refreshResults(); + } + + void _onSessionProposal(SessionProposalEvent? event) async { + Logger('WalletConnectService') + .log(Level.INFO, '_onSessionProposal triggered', event.toString()); + + if (event != null) { + Logger('WalletConnectService') + .log(Level.INFO, '_onSessionProposal event', event.params.toJson()); + + final dAppMetadata = event.params.proposer.metadata; + + final actionWasAccepted = await showDialogWithNoAndYesOptions( + context: globalNavigatorKey.currentContext!, + isBarrierDismissible: false, + title: 'Approve session', + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text('Are you sure you want to ' + 'connect to ${dAppMetadata.name} ?'), + kVerticalSpacing, + Image( + image: NetworkImage(dAppMetadata.icons.first), + height: 100.0, + fit: BoxFit.fitHeight, + ), + kVerticalSpacing, + Text(dAppMetadata.description), + kVerticalSpacing, + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text(dAppMetadata.url), + LinkIcon( + url: dAppMetadata.url, + ) + ], + ), + ], + ), + onYesButtonPressed: () async {}, + onNoButtonPressed: () async {}, + ); + + if (actionWasAccepted) { + if (!_idSessionsApproved.contains(event.id)) { + _idSessionsApproved.add(event.id); + try { + ApproveResponse approveResponse = + await _approveSession(id: event.id); + await _sendSuccessfullyApprovedSessionNotification(dAppMetadata); + sessions.value.add(approveResponse.session); + } catch (e, stackTrace) { + await NotificationUtils.sendNotificationError( + e, 'WalletConnect session approval failed'); + Logger('WalletConnectService').log( + Level.INFO, 'onSessionProposal approveResponse', e, stackTrace); + } + } + } else { + await _wcClient!.rejectSession( + id: event.id, + reason: Errors.getSdkError( + Errors.USER_REJECTED, + ), + ); + } + } + } + + void _onSessionConnect(SessionConnect? args) { + if (args != null) { + sessions.value.add(args.session); + } + Logger('WalletConnectService') + .log(Level.INFO, '_onSessionConnect triggered', args.toString()); + Future.delayed(const Duration(seconds: 3)) + .then((value) => sl.get().refreshResults()); + } + + Future _sendSuccessfullyApprovedSessionNotification( + PairingMetadata dAppMetadata) async { + await sl.get().addNotification( + WalletNotification( + title: 'Successfully connected to ${dAppMetadata.name}', + timestamp: DateTime.now().millisecondsSinceEpoch, + details: 'Successfully connected to ${dAppMetadata.name} ' + 'via WalletConnect', + type: NotificationType.paymentSent, + ), + ); + } + + Future _approveSession( + {required int id, Map? namespaces}) async { + if (!await windowManager.isFocused() || !await windowManager.isVisible()) { + windowManager.show(); + } + namespaces = namespaces ?? + { + 'zenon': Namespace( + accounts: _getWalletAccounts(), + methods: [ + 'znn_sign', + 'znn_info', + 'znn_send', + ], + events: ['chainIdChange', 'addressChange'], + ), + }; + return _wcClient!.approveSession( + id: id, + namespaces: namespaces, + ); + } + + String _generateAccount(String address, int chainId) => + '$kZenonNameSpace:1:$address'; + + List _getWalletAccounts() => kAddressLabelMap.values + .map( + (address) => _generateAccount(address, getChainIdentifier()), + ) + .toList(); + + Future _emitEventPairedDApps({ + required String changeName, + required String newValue, + }) async { + final sessionTopics = + pairings.value.fold>([], (previousValue, pairing) { + if (pairing.active) { + previousValue.addAll(getSessionsForPairing(pairing.topic).keys); + return previousValue; + } + return previousValue; + }); + + for (String sessionTopic in sessionTopics) { + _emitDAppEvent( + sessionTopic: sessionTopic, + changeName: changeName, + newValue: newValue, + ); + } + } + + Future _emitDAppEvent({ + required String sessionTopic, + required String changeName, + required String newValue, + }) { + return _wcClient!.emitSessionEvent( + topic: sessionTopic, + chainId: 'zenon:1', + event: SessionEventParams( + name: changeName, + data: newValue, + ), + ); + } +} diff --git a/lib/utils/account_block_utils.dart b/lib/utils/account_block_utils.dart index 31237735..72a0bf5b 100644 --- a/lib/utils/account_block_utils.dart +++ b/lib/utils/account_block_utils.dart @@ -4,65 +4,59 @@ import 'package:collection/collection.dart'; import 'package:zenon_syrius_wallet_flutter/blocs/blocs.dart'; import 'package:zenon_syrius_wallet_flutter/main.dart'; import 'package:zenon_syrius_wallet_flutter/model/model.dart'; -import 'package:zenon_syrius_wallet_flutter/utils/constants.dart'; import 'package:zenon_syrius_wallet_flutter/utils/format_utils.dart'; import 'package:zenon_syrius_wallet_flutter/utils/global.dart'; import 'package:znn_sdk_dart/znn_sdk_dart.dart'; class AccountBlockUtils { - static final Map?> _kIsRunningByAddress = {}; - static Future createAccountBlock( AccountBlockTemplate transactionParams, String purposeOfGeneratingPlasma, { - KeyPair? blockSigningKey, + Address? address, bool waitForRequiredPlasma = false, }) async { SyncInfo syncInfo = await zenon!.stats.syncInfo(); - bool nodeIsSynced = kCurrentNode == kLocalhostDefaultNodeUrl - ? (syncInfo.state == SyncState.syncDone || - (syncInfo.targetHeight > 0 && - syncInfo.currentHeight > 0 && - (syncInfo.targetHeight - syncInfo.currentHeight) < 20)) - : true; + bool nodeIsSynced = (syncInfo.state == SyncState.syncDone || + (syncInfo.targetHeight > 0 && + syncInfo.currentHeight > 0 && + (syncInfo.targetHeight - syncInfo.currentHeight) < 20)); if (nodeIsSynced) { - Address address = (await blockSigningKey?.address ?? - await zenon!.defaultKeyPair!.address)!; + // Acquire wallet lock to prevent concurrent access. + final wallet = await kWalletFile!.open(); try { - // Wait until the lock is unused. - // - // A while-loop is required since there is the case when a lot of routines are waiting, and only one should move - // forward when the main routine finishes. - while (_kIsRunningByAddress.containsKey(address.toString()) && - _kIsRunningByAddress[address.toString()] != null) { - await _kIsRunningByAddress[address.toString()]; - } - - // Acquire lock - Completer completer; - completer = Completer(); - _kIsRunningByAddress[address.toString()] = completer.future; + address ??= Address.parse(kSelectedAddress!); + final walletAccount = await wallet + .getAccount(kDefaultAddressList.indexOf(address.toString())); bool needPlasma = await zenon!.requiresPoW( transactionParams, - blockSigningKey: blockSigningKey, + blockSigningKey: walletAccount, ); + bool needReview = kWalletFile!.isHardwareWallet; if (needPlasma) { - sl + await sl .get() .sendPlasmaNotification(purposeOfGeneratingPlasma); + } else if (needReview) { + await _sendReviewNotification(transactionParams); } final AccountBlockTemplate response = await zenon!.send( transactionParams, - currentKeyPair: blockSigningKey, - generatingPowCallback: _addEventToPowGeneratingStatusBloc, + currentKeyPair: walletAccount, + generatingPowCallback: (status) async { + // Wait for plasma to be generated before sending review notification + if (needReview && status == PowStatus.done) { + await _sendReviewNotification(transactionParams); + } + _addEventToPowGeneratingStatusBloc(status); + }, waitForRequiredPlasma: waitForRequiredPlasma, ); if (BlockUtils.isReceiveBlock(transactionParams.blockType)) { sl.get().getBalanceForAllAddresses(); } - sl.get().addNotification( + await sl.get().addNotification( WalletNotification( title: 'Account-block published', timestamp: DateTime.now().millisecondsSinceEpoch, @@ -80,15 +74,11 @@ class AccountBlockUtils { // This is a problem when we create 2 transactions from the same address without requiring PoW. // If we release the lock too early, zenon.send will autofill the AccountBlockTemplate with an old value of // ledger.getFrontierAccountBlock, since the node did not had enough time to process the current transaction. - Future.delayed(const Duration(seconds: 1)).then((_) { - completer.complete(); - _kIsRunningByAddress[address.toString()] = null; - }); + await Future.delayed(const Duration(seconds: 1)); return response; - } catch (e) { - _kIsRunningByAddress[address.toString()] = null; - rethrow; + } finally { + kWalletFile!.close(); } } else { throw 'Node is not synced'; @@ -174,6 +164,22 @@ class AccountBlockUtils { return null; } + static Future _sendReviewNotification( + AccountBlockTemplate transactionParams) async { + await sl.get().addNotification( + WalletNotification( + title: + '${BlockUtils.isSendBlock(transactionParams.blockType) ? 'Sending transaction' : 'Receiving transaction'}, please review the transaction on your hardware device', + timestamp: DateTime.now().millisecondsSinceEpoch, + details: + 'Review account-block type: ${FormatUtils.extractNameFromEnum( + BlockTypeEnum.values[transactionParams.blockType], + )}', + type: NotificationType.confirm, + ), + ); + } + static void _addEventToPowGeneratingStatusBloc(PowStatus event) => sl.get().addEvent(event); } diff --git a/lib/utils/address_utils.dart b/lib/utils/address_utils.dart index 16791496..56f3e378 100644 --- a/lib/utils/address_utils.dart +++ b/lib/utils/address_utils.dart @@ -1,22 +1,19 @@ import 'dart:async'; -import 'dart:isolate'; import 'package:flutter/material.dart'; import 'package:hive/hive.dart'; import 'package:zenon_syrius_wallet_flutter/blocs/blocs.dart'; import 'package:zenon_syrius_wallet_flutter/main.dart'; +import 'package:zenon_syrius_wallet_flutter/model/database/notification_type.dart'; +import 'package:zenon_syrius_wallet_flutter/model/database/wallet_notification.dart'; +import 'package:zenon_syrius_wallet_flutter/utils/notification_utils.dart'; +import 'package:zenon_syrius_wallet_flutter/utils/wallet_file.dart'; import 'package:zenon_syrius_wallet_flutter/utils/constants.dart'; import 'package:zenon_syrius_wallet_flutter/utils/global.dart'; import 'package:zenon_syrius_wallet_flutter/utils/node_utils.dart'; +import 'package:znn_ledger_dart/znn_ledger_dart.dart'; import 'package:znn_sdk_dart/znn_sdk_dart.dart'; -class SetAddressArguments { - final KeyStore? keystore; - final SendPort port; - - SetAddressArguments(this.keystore, this.port); -} - class ZenonAddressUtils { static void refreshBalance() => sl.get().getBalanceForAllAddresses(); @@ -26,26 +23,50 @@ class ZenonAddressUtils { static Future generateNewAddress( {int numAddr = 1, VoidCallback? callback}) async { - await Future.delayed(const Duration(milliseconds: 500)); - List listAddr = []; - int addrListLength = kDefaultAddressList.length; - for (int i = 0; i < numAddr; i++) { - int addrListCounter = addrListLength + i; - Address? address = await kKeyStore!.getKeyPair(addrListCounter).address; - listAddr.add(address); - Box addressesBox = Hive.box(kAddressesBox); - await addressesBox.add(listAddr.elementAt(i).toString()); - _initAddresses(addressesBox); - Box addressLabelsBox = Hive.box(kAddressLabelsBox); - await addressLabelsBox.put( - listAddr.elementAt(i).toString(), - 'Address ${kDefaultAddressList.length}', - ); - _initAddressLabels(addressLabelsBox); - NodeUtils.getUnreceivedTransactionsByAddress(listAddr[i]!); - callback?.call(); + final wallet = await kWalletFile!.open(); + try { + await Future.delayed(const Duration(milliseconds: 500)); + List listAddr = []; + int addrListLength = kDefaultAddressList.length; + for (int i = 0; i < numAddr; i++) { + int addrListCounter = addrListLength + i; + WalletAccount walletAccount = await wallet.getAccount(addrListCounter); + Address? address; + if (walletAccount is LedgerWalletAccount) { + await sl.get().addNotification( + WalletNotification( + title: + 'Adding address, please confirm the address on your hardware device', + timestamp: DateTime.now().millisecondsSinceEpoch, + details: + 'Confirm address for account index: $addrListCounter', + type: NotificationType.confirm, + ), + ); + address = await walletAccount.getAddress(true); + } else { + address = await walletAccount.getAddress(); + } + listAddr.add(address); + Box addressesBox = Hive.box(kAddressesBox); + await addressesBox.add(listAddr.elementAt(i).toString()); + _initAddresses(addressesBox); + Box addressLabelsBox = Hive.box(kAddressLabelsBox); + await addressLabelsBox.put( + listAddr.elementAt(i).toString(), + 'Address ${kDefaultAddressList.length}', + ); + _initAddressLabels(addressLabelsBox); + NodeUtils.getUnreceivedTransactionsByAddress(listAddr[i]!); + callback?.call(); + } + listAddr.clear(); + } catch (e) { + await NotificationUtils.sendNotificationError( + e, 'Error while generating new address'); + } finally { + kWalletFile!.close(); } - listAddr.clear(); } static Future setAddressLabels() async { @@ -60,18 +81,6 @@ class ZenonAddressUtils { _initAddressLabels(addressLabelsBox); } - static void setAddressesFunction(SetAddressArguments args) async { - for (var element in (await Future.wait( - List>.generate( - kNumOfInitialAddresses, - (index) async => - (await args.keystore!.getKeyPair(index).address).toString()), - ))) { - args.port.send(element); - } - args.port.send('done'); - } - static Future setDefaultAddress() async { if (sharedPrefsService!.get(kDefaultAddressKey) == null) { await sharedPrefsService!.put( @@ -82,34 +91,22 @@ class ZenonAddressUtils { kSelectedAddress = sharedPrefsService!.get(kDefaultAddressKey); } - static Future setAddresses(KeyStore? keyStore) async { - final port = ReceivePort(); + static Future setAddresses(WalletFile? walletFile) async { Box addressesBox = await Hive.openBox(kAddressesBox); - final args = SetAddressArguments(keyStore, port.sendPort); if (addressesBox.isEmpty) { - Isolate.spawn( - setAddressesFunction, - args, - onError: port.sendPort, - onExit: port.sendPort, - ); - StreamSubscription? sub; - Completer completer = Completer(); - sub = port.listen( - (data) async { - if (data != null && data != 'done') { - addressesBox.add(data); - } else if (data == 'done') { - _initAddresses(addressesBox); - completer.complete(); - await sub?.cancel(); - } - }, - ); - return completer.future; - } else { - _initAddresses(addressesBox); + await walletFile!.access((wallet) async { + for (var element in (await Future.wait( + List>.generate( + kNumOfInitialAddresses, + (index) async => + (await (await wallet.getAccount(index)).getAddress()) + .toString()), + ))) { + addressesBox.add(element); + } + }); } + _initAddresses(addressesBox); } static void _initAddresses(Box addressesBox) => diff --git a/lib/utils/clipboard_utils.dart b/lib/utils/clipboard_utils.dart index 718f8c01..f48f4a37 100644 --- a/lib/utils/clipboard_utils.dart +++ b/lib/utils/clipboard_utils.dart @@ -29,11 +29,11 @@ class ClipboardUtils { static void pasteToClipboard( BuildContext context, Function(String) callback) { - Clipboard.getData('text/plain').then((value) { + Clipboard.getData('text/plain').then((value) async { if (value != null) { callback(value.text!); } else { - NotificationUtils.sendNotificationError( + await NotificationUtils.sendNotificationError( Exception('The clipboard data could not be obtained'), 'Something went wrong while getting the clipboard data', ); diff --git a/lib/utils/constants.dart b/lib/utils/constants.dart index 1cb2c11e..8aff387f 100644 --- a/lib/utils/constants.dart +++ b/lib/utils/constants.dart @@ -4,8 +4,7 @@ import 'package:zenon_syrius_wallet_flutter/widgets/widgets.dart'; import 'package:znn_sdk_dart/znn_sdk_dart.dart'; // WalletConnect -const String kWcProjectId = String.fromEnvironment('WC_PROJECT_ID', - defaultValue: '6106aa8c2f308b338f31465bef999a1f'); +const String kWcProjectId = '6106aa8c2f308b338f31465bef999a1f'; const String kZenonNameSpace = 'zenon'; const walletConnectDirName = 'walletconnect'; @@ -29,7 +28,7 @@ const SizedBox kSpacingBetweenActionButtons = SizedBox( const Size kAcceleratorProgressBarSize = Size(300.0, 10.0); // Wallet version -const String kWalletVersion = '0.1.0'; +const String kWalletVersion = '0.2.0'; // Boxes constants const String kFavoriteTokensBox = 'favourite_tokens_box'; @@ -109,25 +108,21 @@ const int kAmountInputMaxCharacterLength = 21; const int kSecondsPerMomentum = 10; const int kMaxP2pSwapsToStore = 500; -final List kNormalUsersPlasmaRequirements = [ +const List kNormalUsersPlasmaRequirements = [ kStakePlasmaAmountNeeded, kDelegatePlasmaAmountNeeded, ]; -final List kPowerUsersPlasmaRequirements = [ +const List kPowerUsersPlasmaRequirements = [ kPillarPlasmaAmountNeeded, kSentinelPlasmaAmountNeeded, kIssueTokenPlasmaAmountNeeded, ]; +const String kEmbeddedNode = 'Embedded Node'; const String kLocalhostDefaultNodeUrl = 'ws://127.0.0.1:$kDefaultPort'; const int kDefaultPort = 35998; -List kDefaultNodes = [ - 'Embedded Node', - kLocalhostDefaultNodeUrl, -]; - const List kWalletActions = [ 'pillar', 'sentinel', @@ -177,9 +172,12 @@ const String kAutoEraseNumAttemptsKey = 'auto_erase_num_attempts'; const String kLaunchAtStartupKey = 'launch_at_startup'; const String kEnableDesktopNotificationsKey = 'enable_desktop_notifications'; const String kEnableClipboardWatcherKey = 'enable_clipboard_watcher'; +const String kAutoReceiveKey = 'enable_auto_receive'; + const bool kLaunchAtStartupDefaultValue = false; const bool kEnableDesktopNotificationsDefaultValue = false; const bool kEnableClipboardWatcherDefaultValue = false; +const bool kAutoReceiveDefaultValue = true; /// Node management const String kChainIdKey = 'chain_id'; diff --git a/lib/utils/extensions.dart b/lib/utils/extensions.dart index 80cd685a..9d63bf7c 100644 --- a/lib/utils/extensions.dart +++ b/lib/utils/extensions.dart @@ -1,5 +1,6 @@ import 'dart:math' show pow; import 'package:big_decimal/big_decimal.dart'; +import 'package:znn_ledger_dart/znn_ledger_dart.dart'; extension StringExtensions on String { String capitalize() { @@ -68,3 +69,54 @@ extension ShortString on String { '${longString.substring(longString.length - 6)}'; } } + +extension LedgerErrorExtensions on LedgerError { + String toFriendlyString() { + return when( + connectionError: (origMessage) => origMessage, + responseError: (statusWord) => _mapStatusWord(statusWord)); + } + + String _mapStatusWord(StatusWord statusWord) { + switch (statusWord) { + case StatusWord.deny: + return 'Deny'; + case StatusWord.wrongP1P2: + return 'Invalid P1 or P2'; + case StatusWord.wrongDataLength: + return 'Invalid data length'; + case StatusWord.inactiveDevice: + return 'Device is inactive'; + case StatusWord.notAllowed: + return 'Request not allowed'; + case StatusWord.insNotSupported: + return 'Instruction not supported, please make sure the Zenon app is opened'; + case StatusWord.claNotSupported: + return 'Class not supported, please make sure the Zenon app is opened'; + case StatusWord.appIsNotOpen: + return 'App not open, please open the Zenon app on your device'; + case StatusWord.wrongResponseLength: + return 'Invalid response, please reconnect the device and try again'; + case StatusWord.displayBip32PathFail: + return 'Failed to display BIP32 path'; + case StatusWord.displayAddressFail: + return 'Failed to display address'; + case StatusWord.displayAmountFail: + return 'Failed to display amount'; + case StatusWord.wrongTxLength: + return 'Invalid transaction length'; + case StatusWord.txParsingFail: + return 'Failed to parse transaction'; + case StatusWord.txHashFail: + return 'Failed to hash transaction'; + case StatusWord.badState: + return 'Bad state, please reconnect the device and try again'; + case StatusWord.signatureFail: + return 'Failed to create signature'; + case StatusWord.success: + return 'Success'; + case StatusWord.unknownError: + return 'Unknown error, please make sure the device is unlocked'; + } + } +} diff --git a/lib/utils/format_utils.dart b/lib/utils/format_utils.dart index 384ef371..b1c0b9b9 100644 --- a/lib/utils/format_utils.dart +++ b/lib/utils/format_utils.dart @@ -59,4 +59,24 @@ class FormatUtils { ) .millisecondsSinceEpoch; } + + static String formatData(int transactionMillis) { + int currentMillis = DateTime.now().millisecondsSinceEpoch; + if (currentMillis - transactionMillis <= + const Duration(days: 1).inMilliseconds) { + return formatDataShort(currentMillis - transactionMillis); + } + return FormatUtils.formatDate(transactionMillis, dateFormat: 'MM/dd/yyyy'); + } + + static String formatDataShort(int i) { + Duration duration = Duration(milliseconds: i); + if (duration.inHours > 0) { + return '${duration.inHours} h ago'; + } + if (duration.inMinutes > 0) { + return '${duration.inMinutes} min ago'; + } + return '${duration.inSeconds} s ago'; + } } diff --git a/lib/utils/functions.dart b/lib/utils/functions.dart index 7e620d22..7cb1d350 100644 --- a/lib/utils/functions.dart +++ b/lib/utils/functions.dart @@ -1,13 +1,35 @@ +import 'dart:convert'; + import 'package:flutter/services.dart'; -import 'package:zenon_syrius_wallet_flutter/main.dart'; +import 'package:zenon_syrius_wallet_flutter/utils/global.dart'; import 'package:znn_sdk_dart/znn_sdk_dart.dart'; -Future walletSign(List message) async { - List signature = await zenon!.defaultKeyPair!.sign( - Uint8List.fromList( - message, - ), - ); +class Signature { + String signature; + String publicKey; + + Signature(this.signature, this.publicKey); +} + +Future walletSign(List message) async { + final wallet = await kWalletFile!.open(); + try { + final walletAccount = await wallet + .getAccount(kDefaultAddressList.indexOf(kSelectedAddress)); + List publicKey = await walletAccount.getPublicKey(); + List signature = await walletAccount.sign( + Uint8List.fromList( + message, + ), + ); + return Signature( + BytesUtils.bytesToHex(signature), BytesUtils.bytesToHex(publicKey)); + } finally { + kWalletFile!.close(); + } +} - return BytesUtils.bytesToHex(signature); +Future loadJsonFromAssets(String filePath) async { + String jsonString = await rootBundle.loadString(filePath); + return jsonDecode(jsonString); } \ No newline at end of file diff --git a/lib/utils/global.dart b/lib/utils/global.dart index 611ee340..88930941 100644 --- a/lib/utils/global.dart +++ b/lib/utils/global.dart @@ -1,15 +1,13 @@ -import 'dart:io'; - import 'package:flutter/cupertino.dart'; import 'package:zenon_syrius_wallet_flutter/model/model.dart'; +import 'package:zenon_syrius_wallet_flutter/utils/wallet_file.dart'; import 'package:zenon_syrius_wallet_flutter/utils/constants.dart'; import 'package:zenon_syrius_wallet_flutter/widgets/widgets.dart'; -import 'package:znn_sdk_dart/znn_sdk_dart.dart'; ValueNotifier kLastWalletConnectUriNotifier = ValueNotifier(null); String? kCurrentNode; String? kSelectedAddress; -String? kKeyStorePath; +String? kWalletPath; String? kLocalIpAddress; int? kAutoLockWalletMinutes; @@ -20,11 +18,7 @@ double? kAutoEraseWalletLimit; bool kWalletInitCompleted = false; -KeyStore? kKeyStore; - -KeyStoreManager kKeyStoreManager = KeyStoreManager( - walletPath: Directory(kKeyStorePath!), -); +WalletFile? kWalletFile; List kDbNodes = []; List kDefaultAddressList = []; @@ -48,10 +42,20 @@ final List kTabsWithIconTitles = [ Tabs.help, Tabs.notifications, Tabs.settings, - Tabs.resyncWallet, + Tabs.generation, + Tabs.sync, Tabs.lock, ]; final List kDisabledTabs = [ - Tabs.resyncWallet, + Tabs.generation, + Tabs.sync, ]; + +List kDefaultNodes = [ + kEmbeddedNode, + kLocalhostDefaultNodeUrl, +]; + +// Community supplied public rpc nodes +List kDefaultCommunityNodes = []; \ No newline at end of file diff --git a/lib/utils/init_utils.dart b/lib/utils/init_utils.dart index 527eefec..3d2ad2ec 100644 --- a/lib/utils/init_utils.dart +++ b/lib/utils/init_utils.dart @@ -2,36 +2,32 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:hive/hive.dart'; +import 'package:version/version.dart'; import 'package:zenon_syrius_wallet_flutter/handlers/htlc_swaps_handler.dart'; import 'package:zenon_syrius_wallet_flutter/main.dart'; import 'package:zenon_syrius_wallet_flutter/services/shared_prefs_service.dart'; -import 'package:zenon_syrius_wallet_flutter/services/wallet_connect_service.dart'; import 'package:zenon_syrius_wallet_flutter/utils/address_utils.dart'; import 'package:zenon_syrius_wallet_flutter/utils/constants.dart'; import 'package:zenon_syrius_wallet_flutter/utils/global.dart'; -import 'package:zenon_syrius_wallet_flutter/utils/keystore_utils.dart'; +import 'package:zenon_syrius_wallet_flutter/utils/wallet_utils.dart'; import 'package:zenon_syrius_wallet_flutter/utils/node_utils.dart'; import 'package:zenon_syrius_wallet_flutter/utils/widget_utils.dart'; import 'package:znn_sdk_dart/znn_sdk_dart.dart'; class InitUtils { static Future initApp(BuildContext context) async { - try { - WidgetUtils.setThemeMode(context); - WidgetUtils.setTextScale(context); - _setAutoEraseWalletNumAttempts(); - _setAutoLockWalletTimeInterval(); - await KeyStoreUtils.setKeyStorePath(); - await _setNumUnlockFailedAttempts(); - await NodeUtils.setNode(); - _setChainId(); - await NodeUtils.loadDbNodes(); - - // Initialize WalletConnect client - sl.get().initClient(); - } catch (e) { - rethrow; - } + WidgetUtils.setThemeMode(context); + WidgetUtils.setTextScale(context); + _setAutoEraseWalletNumAttempts(); + _setAutoLockWalletTimeInterval(); + await WalletUtils.setWalletPath(); + await _setNumUnlockFailedAttempts(); + await NodeUtils.setNode(); + _setChainId(); + await NodeUtils.loadDbNodes(); + await _openFavoriteTokensBox(); + await _openNotificationsBox(); + await _openRecipientBox(); } static void _setChainId() { @@ -72,21 +68,25 @@ class InitUtils { defaultValue: kAutoLockWalletDefaultIntervalMinutes, ); - static Future initWalletAfterDecryption() async { - await ZenonAddressUtils.setAddresses(kKeyStore); + static Future initWalletAfterDecryption(List cipherKey) async { + final walletVersion = Version.parse(sharedPrefsService! + .get(kWalletVersionKey, defaultValue: kWalletVersion)); + await ZenonAddressUtils.setAddresses(kWalletFile); await ZenonAddressUtils.setAddressLabels(); await ZenonAddressUtils.setDefaultAddress(); - zenon!.defaultKeyPair = kKeyStore!.getKeyPair( - kDefaultAddressList.indexOf(kSelectedAddress), - ); - await _openFavoriteTokensBox(); - await _openNotificationsBox(); - await _openRecipientBox(); await NodeUtils.initWebSocketClient(); await _setWalletVersion(); - final baseAddress = await kKeyStore!.getKeyPair(0).address; - await htlcSwapsService!.openBoxes( - baseAddress.toString(), kKeyStore!.getKeyPair(0).getPrivateKey()!); + if (walletVersion <= Version(0, 1, 0)) { + // Migrate to password as the cipherkey instead of the private key. + await kWalletFile!.access((Wallet wallet) async { + await htlcSwapsService!.openBoxes(WalletUtils.baseAddress.toString(), + (wallet as KeyStore).getKeyPair().getPrivateKey()!, + newCipherKey: cipherKey); + }); + } else { + await htlcSwapsService! + .openBoxes(WalletUtils.baseAddress.toString(), cipherKey); + } sl().start(); kWalletInitCompleted = true; } diff --git a/lib/utils/keystore_utils.dart b/lib/utils/keystore_utils.dart deleted file mode 100644 index b03e84d7..00000000 --- a/lib/utils/keystore_utils.dart +++ /dev/null @@ -1,73 +0,0 @@ -import 'dart:convert'; -import 'dart:io'; - -import 'package:hive/hive.dart'; -import 'package:zenon_syrius_wallet_flutter/main.dart'; -import 'package:zenon_syrius_wallet_flutter/utils/constants.dart'; -import 'package:zenon_syrius_wallet_flutter/utils/global.dart'; -import 'package:znn_sdk_dart/znn_sdk_dart.dart'; - -class KeyStoreUtils { - static Future decryptKeyStoreFile( - String keyStorePath, - String password, - ) async { - return await KeyFile.fromJson( - json.decode( - File(keyStorePath).readAsStringSync(), - ), - ).decrypt(password); - } - - static void _initKeyStoreConstants( - KeyStore keyStore, - String keyStorePath, - ) { - kKeyStorePath = keyStorePath; - kKeyStore = keyStore; - } - - static Future createKeyStore( - String mnemonic, - String passphrase, { - String? keyStoreName, - }) async { - KeyStoreManager keyStoreManager = - KeyStoreManager(walletPath: znnDefaultWalletDirectory); - KeyStore keyStore = KeyStore.fromMnemonic(mnemonic); - File keyStoreFile = await keyStoreManager.saveKeyStore( - keyStore, - passphrase, - name: keyStoreName, - ); - _initKeyStoreConstants( - keyStore, - keyStoreFile.path, - ); - await _saveKeyStorePath(keyStoreFile.path); - } - - static Future _saveKeyStorePath(String? keyStorePath) async { - Box keyStoreBox = await Hive.openBox(kKeyStoreBox); - await keyStoreBox.put(0, keyStorePath); - } - - static Future setKeyStorePath() async { - if (kKeyStorePath == null) { - Box keyStoreBox = await Hive.openBox(kKeyStoreBox); - if (keyStoreBox.isEmpty) { - // Here we check if the key store path is saved in another place - // and we copy that value, if it exists - String? keyStorePath = sharedPrefsService!.get(kEntropyFilePathKey); - if (keyStorePath != null) { - keyStoreBox.add(keyStorePath); - kKeyStorePath = keyStoreBox.values.first; - } - } else { - kKeyStorePath = keyStoreBox.values.first; - } - } else { - _saveKeyStorePath(kKeyStorePath); - } - } -} diff --git a/lib/utils/navigation_utils.dart b/lib/utils/navigation_utils.dart index 404f1f9a..da5d1bbc 100644 --- a/lib/utils/navigation_utils.dart +++ b/lib/utils/navigation_utils.dart @@ -14,7 +14,7 @@ class NavigationUtils { if (await canLaunchUrl(uri)) { await launchUrl(uri); } else { - sl.get().addNotification( + await sl.get().addNotification( WalletNotification( title: 'Error while trying to open $url', timestamp: DateTime.now().millisecondsSinceEpoch, diff --git a/lib/utils/node_utils.dart b/lib/utils/node_utils.dart index 947ccd69..6f9ad135 100644 --- a/lib/utils/node_utils.dart +++ b/lib/utils/node_utils.dart @@ -1,5 +1,4 @@ import 'dart:async'; -import 'dart:io'; import 'dart:isolate'; import 'package:hive/hive.dart'; @@ -46,14 +45,14 @@ class NodeUtils { } if (kCurrentNode == kLocalhostDefaultNodeUrl || - kCurrentNode == 'Embedded Node') { + kCurrentNode == kEmbeddedNode) { if (kEmbeddedNodeRunning) { - sl.get().addNotification( + await sl.get().addNotification( WalletNotification( - title: 'Waiting for embedded node to stop', + title: 'Waiting for Embedded Node to stop', timestamp: DateTime.now().millisecondsSinceEpoch, details: - 'The app will close after the embedded node has been stopped', + 'The app will close after the Embedded Node has been stopped', type: NotificationType.changedNode, ), ); @@ -77,15 +76,19 @@ class NodeUtils { } } + static stopWebSocketClient() { + try { + zenon!.wsClient.stop(); + } catch (_) {} + } + static initWebSocketClient() async { addOnWebSocketConnectedCallback(); - var url = kCurrentNode!; + var url = kCurrentNode ?? ''; bool connected = false; try { connected = await establishConnectionToNode(url); - } on WebSocketException { - url = kLocalhostDefaultNodeUrl; - } + } catch (_) {} if (!connected) { zenon!.wsClient.initialize( url, @@ -100,9 +103,10 @@ class NodeUtils { await _getSubscriptionForMomentums(); await _getSubscriptionForAllAccountEvents(); await getUnreceivedTransactions(); + sl().autoReceive(); Future.delayed(const Duration(seconds: 30)) - .then((value) => NotificationUtils.sendNodeSyncingNotification()); + .then((value) async => await NotificationUtils.sendNodeSyncingNotification()); _initListenForUnreceivedAccountBlocks(allResponseBroadcaster); }); } @@ -127,7 +131,12 @@ class NodeUtils { if (unreceivedBlocks.isNotEmpty) { for (AccountBlock unreceivedBlock in unreceivedBlocks) { - sl().addHash(unreceivedBlock.hash); + if (sharedPrefsService!.get( + kAutoReceiveKey, + defaultValue: kAutoReceiveDefaultValue, + )) { + sl().addHash(unreceivedBlock.hash); + } } } } @@ -146,7 +155,7 @@ class NodeUtils { (await zenon!.ledger.getFrontierMomentum()).timestamp; final timeDifference = (frontierTime - DateTimeUtils.unixTimeNow).abs(); if (timeDifference > maxAllowedDiscrepancy.inSeconds) { - NotificationUtils.sendNotificationError( + await NotificationUtils.sendNotificationError( Exception('Local time discrepancy detected.'), warningMessage, ); @@ -161,8 +170,11 @@ class NodeUtils { static void _initListenForUnreceivedAccountBlocks(Stream broadcaster) { broadcaster.listen( (event) { + // Only process unreceived account blocks when autoReceive is enabled if (event!.containsKey('method') && - event['method'] == 'ledger.subscription') { + event['method'] == 'ledger.subscription' && + sharedPrefsService! + .get(kAutoReceiveKey, defaultValue: kAutoReceiveDefaultValue)) { for (var i = 0; i < event['params']['result'].length; i += 1) { var tx = event['params']['result'][i]; if (tx.containsKey('toAddress') && @@ -178,7 +190,7 @@ class NodeUtils { (_kHeight == 0 || result['height'] >= _kHeight + 1)) { _kHeight = result['height']; if (sl().pool.isNotEmpty && - kKeyStore != null) { + kWalletFile != null) { sl().autoReceive(); } } @@ -204,21 +216,19 @@ class NodeUtils { kDbNodes.addAll(nodesBox.values); // Handle the case in which some default nodes were deleted // so they can't be found in the cache - String currentNode = kCurrentNode!; - if (!kDefaultNodes.contains(currentNode) && + String? currentNode = kCurrentNode; + if (currentNode != null && + !kDefaultNodes.contains(currentNode) && !kDbNodes.contains(currentNode)) { kDefaultNodes.add(currentNode); } } static Future setNode() async { - String savedNode = sharedPrefsService!.get( - kSelectedNodeKey, - defaultValue: kDefaultNodes.first, - ); + String? savedNode = sharedPrefsService!.get(kSelectedNodeKey); kCurrentNode = savedNode; - if (savedNode == 'Embedded Node') { + if (savedNode == kEmbeddedNode) { // First we need to check if the node is not already running bool isConnectionEstablished = await NodeUtils.establishConnectionToNode(kLocalhostDefaultNodeUrl); diff --git a/lib/utils/notification_utils.dart b/lib/utils/notification_utils.dart index 38cf51a0..8f81afcd 100644 --- a/lib/utils/notification_utils.dart +++ b/lib/utils/notification_utils.dart @@ -3,16 +3,15 @@ import 'package:zenon_syrius_wallet_flutter/blocs/blocs.dart'; import 'package:zenon_syrius_wallet_flutter/main.dart'; import 'package:zenon_syrius_wallet_flutter/model/model.dart'; import 'package:zenon_syrius_wallet_flutter/utils/global.dart'; -import 'package:znn_sdk_dart/znn_sdk_dart.dart'; class NotificationUtils { - static void sendNotificationError( + static Future sendNotificationError( Object error, String title, - ) { + ) async { Logger('NotificationUtils') .log(Level.WARNING, 'sendNotificationError', error); - sl.get().addErrorNotification( + await sl.get().addErrorNotification( error, title, ); @@ -22,22 +21,21 @@ class NotificationUtils { kLastNotification != null && kLastNotification?.timestamp != kLastDismissedNotification?.timestamp; - static void sendNodeSyncingNotification() { - zenon!.stats.syncInfo().then((SyncInfo syncInfo) { - if (syncInfo.targetHeight == 0 || - syncInfo.currentHeight == 0 || - (syncInfo.targetHeight - syncInfo.currentHeight) > 20) { - sl.get().addNotification( - WalletNotification( - title: - 'The node is still syncing with the network. Please wait until the loading circle turns green before sending any transactions', - timestamp: DateTime.now().millisecondsSinceEpoch, - details: - 'The information displayed in the wallet does not reflect the most recent network state. Operations should not be performed, as they will likely become invalid by the time the node is fully synced', - type: NotificationType.changedNode, - ), - ); - } - }); + static Future sendNodeSyncingNotification() async { + final syncInfo = await zenon!.stats.syncInfo(); + if (syncInfo.targetHeight == 0 || + syncInfo.currentHeight == 0 || + (syncInfo.targetHeight - syncInfo.currentHeight) > 20) { + await sl.get().addNotification( + WalletNotification( + title: + 'The node is still syncing with the network. Please wait until the loading circle turns green before sending any transactions', + timestamp: DateTime.now().millisecondsSinceEpoch, + details: + 'The information displayed in the wallet does not reflect the most recent network state. Operations should not be performed, as they will likely become invalid by the time the node is fully synced', + type: NotificationType.changedNode, + ), + ); + } } } diff --git a/lib/utils/notifiers/default_address_notifier.dart b/lib/utils/notifiers/default_address_notifier.dart index 1117ba40..6300d623 100644 --- a/lib/utils/notifiers/default_address_notifier.dart +++ b/lib/utils/notifiers/default_address_notifier.dart @@ -1,12 +1,12 @@ import 'package:flutter/material.dart'; import 'package:zenon_syrius_wallet_flutter/main.dart'; -import 'package:zenon_syrius_wallet_flutter/services/wallet_connect_service.dart'; +import 'package:zenon_syrius_wallet_flutter/services/i_web3wallet_service.dart'; import 'package:zenon_syrius_wallet_flutter/utils/global.dart'; class SelectedAddressNotifier extends ChangeNotifier { void changeSelectedAddress(String? newSelectedAddress) { kSelectedAddress = newSelectedAddress; - sl().emitAddressChangeEvent(newSelectedAddress!); + sl().emitAddressChangeEvent(newSelectedAddress!); notifyListeners(); } } diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart index d3d75599..1a696982 100644 --- a/lib/utils/utils.dart +++ b/lib/utils/utils.dart @@ -13,7 +13,7 @@ export 'format_utils.dart'; export 'global.dart'; export 'init_utils.dart'; export 'input_validators.dart'; -export 'keystore_utils.dart'; +export 'wallet_utils.dart'; export 'navigation_utils.dart'; export 'network_utils.dart'; export 'node_utils.dart'; diff --git a/lib/utils/wallet_file.dart b/lib/utils/wallet_file.dart new file mode 100644 index 00000000..9b8358b2 --- /dev/null +++ b/lib/utils/wallet_file.dart @@ -0,0 +1,220 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:hex/hex.dart'; +import 'package:mutex/mutex.dart'; +import 'package:znn_ledger_dart/znn_ledger_dart.dart'; +import 'package:znn_sdk_dart/znn_sdk_dart.dart'; +import 'package:path/path.dart' as path; + +abstract class WalletFile { + final String _path; + + static Future decrypt(String walletPath, String password) async { + final encrypted = await WalletFile.read(walletPath); + final walletType = + encrypted.metadata != null ? encrypted.metadata![walletTypeKey] : null; + if (walletType == null || walletType == keyStoreWalletType) { + return await KeyStoreWalletFile.decrypt(walletPath, password); + } else if (walletType == ledgerWalletType) { + return await LedgerWalletFile.decrypt(walletPath, password); + } else { + throw WalletException( + 'Wallet type (${encrypted.metadata![walletTypeKey]}) is not supported'); + } + } + + static Future read(String walletPath) async { + final file = File(walletPath); + if (!file.existsSync()) { + throw WalletException('Given wallet path does not exist ($walletPath)'); + } + return EncryptedFile.fromJson(json.decode(file.readAsStringSync())); + } + + static Future write(String walletPath, String password, List data, + {Map? metadata}) async { + final file = File(walletPath); + final encrypted = + await EncryptedFile.encrypt(data, password, metadata: metadata); + file.writeAsString(json.encode(encrypted), mode: FileMode.writeOnly); + } + + WalletFile(this._path); + + String get walletPath => _path; + + String get walletType; + + bool get isOpen; + + bool get isHardwareWallet; + + Future open(); + + void close(); + + Future access(Future Function(Wallet) accessSection) async { + final wallet = await open(); + try { + return await accessSection(wallet); + } finally { + close(); + } + } + + Future changePassword( + String currentPassword, String newPassword) async { + final file = await WalletFile.read(walletPath); + final decrypted = await file.decrypt(currentPassword); + await WalletFile.write(walletPath, newPassword, decrypted, + metadata: file.metadata); + } +} + +class KeyStoreWalletFile extends WalletFile { + final Mutex _lock = Mutex(); + final String _walletSeed; + KeyStore? _keyStore; + + static final KeyStoreManager keyStoreWalletManager = + KeyStoreManager(walletPath: znnDefaultWalletDirectory); + + static Future create(String mnemonic, String password, + {String? name}) async { + KeyStore wallet = KeyStore.fromMnemonic(mnemonic); + KeyStoreDefinition walletDefinition = + await keyStoreWalletManager.saveKeyStore(wallet, password, name: name); + return KeyStoreWalletFile._internal( + walletDefinition.walletId, wallet.entropy); + } + + static Future decrypt( + String walletPath, String password) async { + final encrypted = await WalletFile.read(walletPath); + if (encrypted.metadata != null && + encrypted.metadata![walletTypeKey] != null && + encrypted.metadata![walletTypeKey] != keyStoreWalletType) { + throw WalletException( + 'Wallet type (${encrypted.metadata![walletTypeKey]}) is not supported'); + } + final decrypted = await encrypted.decrypt(password); + return KeyStoreWalletFile._internal(walletPath, HEX.encode(decrypted)); + } + + KeyStoreWalletFile._internal(super._path, this._walletSeed); + + @override + String get walletType => keyStoreWalletType; + + @override + bool get isOpen => _lock.isLocked; + + @override + bool get isHardwareWallet => false; + + @override + Future open() async { + await _lock.acquire(); + try { + _keyStore ??= KeyStore.fromEntropy(_walletSeed); + return _keyStore!; + } catch (_) { + _lock.release(); + rethrow; + } + } + + @override + void close() async { + if (_lock.isLocked) _lock.release(); + } +} + +class LedgerWalletFile extends WalletFile { + final Mutex _lock = Mutex(); + final String _walletName; + LedgerWallet? _wallet; + + static final LedgerWalletManager ledgerWalletManager = LedgerWalletManager(); + + static Future _connect(String walletIdOrName) async { + for (var walletDefinition + in await ledgerWalletManager.getWalletDefinitions()) { + if (walletDefinition.walletId == walletIdOrName || + walletDefinition.walletName == walletIdOrName) { + return await ledgerWalletManager.getWallet(walletDefinition) + as LedgerWallet; + } + } + throw const LedgerError.connectionError( + origMessage: + 'Cannot find the hardware device, please connect/unlock the device on which the wallet is initialized'); + } + + static Future create(String walletId, String password, + {String? walletName}) async { + LedgerWallet wallet = await _connect(walletId); + try { + final baseAddress = (await (await wallet.getAccount()).getAddress()); + walletName ??= baseAddress.toString(); + final walletPath = path.join(znnDefaultWalletDirectory.path, walletName); + await WalletFile.write(walletPath, password, utf8.encode(walletName), + metadata: { + baseAddressKey: baseAddress.toString(), + walletTypeKey: ledgerWalletType + }); + return LedgerWalletFile._internal(walletPath, walletName); + } finally { + await wallet.disconnect(); + } + } + + static Future decrypt( + String walletPath, String password) async { + final encrypted = await WalletFile.read(walletPath); + if (encrypted.metadata == null || + encrypted.metadata![walletTypeKey] != ledgerWalletType) { + throw WalletException( + 'Wallet type (${encrypted.metadata![walletTypeKey]}) is not supported'); + } + final decrypted = await encrypted.decrypt(password); + return LedgerWalletFile._internal(walletPath, utf8.decode(decrypted)); + } + + LedgerWalletFile._internal(super._path, this._walletName); + + @override + String get walletType => ledgerWalletType; + + @override + bool get isOpen => _lock.isLocked; + + @override + bool get isHardwareWallet => true; + + @override + Future open() async { + await _lock.acquire(); + try { + _wallet = await _connect(_walletName); + return _wallet!; + } catch (_) { + _lock.release(); + rethrow; + } + } + + @override + void close() async { + if (_wallet != null) { + try { + await _wallet!.disconnect(); + } catch (_) { + } finally { + _wallet = null; + } + } + if (_lock.isLocked) _lock.release(); + } +} diff --git a/lib/utils/wallet_utils.dart b/lib/utils/wallet_utils.dart new file mode 100644 index 00000000..d6f7f0c1 --- /dev/null +++ b/lib/utils/wallet_utils.dart @@ -0,0 +1,63 @@ +import 'package:hive/hive.dart'; +import 'package:zenon_syrius_wallet_flutter/main.dart'; +import 'package:zenon_syrius_wallet_flutter/utils/wallet_file.dart'; +import 'package:zenon_syrius_wallet_flutter/utils/constants.dart'; +import 'package:zenon_syrius_wallet_flutter/utils/global.dart'; +import 'package:znn_sdk_dart/znn_sdk_dart.dart'; + +class WalletUtils { + static get baseAddress { + return Address.parse(kDefaultAddressList.first!); + } + + static Future decryptWalletFile( + String walletPath, String password) async { + return await WalletFile.decrypt(walletPath, password); + } + + static Future createLedgerWalletFile( + String walletId, + String password, { + String? walletName, + }) async { + kWalletFile = await LedgerWalletFile.create(walletId, password, + walletName: walletName); + kWalletPath = kWalletFile!.walletPath; + await _storeWalletPath(kWalletFile!.walletPath); + } + + static Future createKeyStoreWalletFile( + String mnemonic, + String password, { + String? name, + }) async { + kWalletFile = + await KeyStoreWalletFile.create(mnemonic, password, name: name); + kWalletPath = kWalletFile!.walletPath; + await _storeWalletPath(kWalletFile!.walletPath); + } + + static Future _storeWalletPath(String? walletPath) async { + Box keyStoreBox = await Hive.openBox(kKeyStoreBox); + await keyStoreBox.put(0, walletPath); + } + + static Future setWalletPath() async { + if (kWalletPath == null) { + Box keyStoreBox = await Hive.openBox(kKeyStoreBox); + if (keyStoreBox.isEmpty) { + // Here we check if the key store path is saved in another place + // and we copy that value, if it exists + String? keyStorePath = sharedPrefsService!.get(kEntropyFilePathKey); + if (keyStorePath != null) { + keyStoreBox.add(keyStorePath); + kWalletPath = keyStoreBox.values.first; + } + } else { + kWalletPath = keyStoreBox.values.first; + } + } else { + _storeWalletPath(kWalletPath); + } + } +} diff --git a/lib/widgets/charts/realtime_txs_chart.dart b/lib/widgets/charts/realtime_txs_chart.dart index ca7d24af..4c9862c8 100644 --- a/lib/widgets/charts/realtime_txs_chart.dart +++ b/lib/widgets/charts/realtime_txs_chart.dart @@ -57,7 +57,7 @@ class RealtimeTxsChartState extends State { } double _getTransactionsByDay(TokenStandard tokenId, DateTime date) { - var transactions = []; + List transactions = []; for (var transaction in widget.transactions) { AccountBlock? pairedAccountBlock; if (transaction.blockType == 3 && diff --git a/lib/widgets/main_app_container.dart b/lib/widgets/main_app_container.dart index 55e4615e..080a1a1c 100644 --- a/lib/widgets/main_app_container.dart +++ b/lib/widgets/main_app_container.dart @@ -9,6 +9,7 @@ import 'package:flutter/services.dart'; import 'package:flutter_svg/svg.dart'; import 'package:flutter_vector_icons/flutter_vector_icons.dart'; import 'package:logging/logging.dart'; +import 'package:lottie/lottie.dart'; import 'package:provider/provider.dart'; import 'package:wallet_connect_uri_validator/wallet_connect_uri_validator.dart'; import 'package:window_manager/window_manager.dart'; @@ -16,7 +17,6 @@ import 'package:zenon_syrius_wallet_flutter/blocs/blocs.dart'; import 'package:zenon_syrius_wallet_flutter/handlers/htlc_swaps_handler.dart'; import 'package:zenon_syrius_wallet_flutter/main.dart'; import 'package:zenon_syrius_wallet_flutter/model/model.dart'; -import 'package:zenon_syrius_wallet_flutter/services/wallet_connect_service.dart'; import 'package:zenon_syrius_wallet_flutter/utils/app_colors.dart'; import 'package:zenon_syrius_wallet_flutter/utils/clipboard_utils.dart'; import 'package:zenon_syrius_wallet_flutter/utils/constants.dart'; @@ -43,7 +43,8 @@ enum Tabs { plasma, tokens, p2pSwap, - resyncWallet, + generation, + sync, accelerator, walletConnect, } @@ -66,33 +67,24 @@ class _MainAppContainerState extends State with TickerProviderStateMixin, ClipboardListener, WindowListener { late AnimationController _animationController; late Animation _animation; - - final NodeSyncStatusBloc _netSyncStatusBloc = NodeSyncStatusBloc(); - late StreamSubscription _lockBlockStreamSubscription; late StreamSubscription _incomingLinkSubscription; - - Timer? _navigateToLockTimer; - late LockBloc _lockBloc; + Timer? _navigateToLockTimer; TabController? _tabController; - TransferTabChild? _transferTabChild; + bool _initialUriIsHandled = false; + final NodeSyncStatusBloc _netSyncStatusBloc = NodeSyncStatusBloc(); + final _appLinks = AppLinks(); final FocusNode _focusNode = FocusNode( skipTraversal: true, canRequestFocus: false, ); - bool _initialUriIsHandled = false; - - final _appLinks = AppLinks(); - @override void initState() { - sl().context = context; - clipboardWatcher.addListener(this); windowManager.addListener(this); @@ -112,6 +104,7 @@ class _MainAppContainerState extends State _initLockBlock(); _handleIncomingLinks(); _handleInitialUri(); + super.initState(); } @@ -206,7 +199,7 @@ class _MainAppContainerState extends State ), child: Padding( padding: const EdgeInsets.symmetric( - horizontal: 10.0, + horizontal: 12.0, ), child: Focus( focusNode: _focusNode, @@ -267,8 +260,9 @@ class _MainAppContainerState extends State ); } - void _onNavigateToLock() { - kKeyStore = null; + void _onNavigateToLock() async { + if (kWalletFile != null) kWalletFile!.close(); + kWalletFile = null; _navigateToLockTimer?.cancel(); } @@ -343,7 +337,10 @@ class _MainAppContainerState extends State ), ), Tab( - child: _getPowGeneratingStatus(), + child: _getGenerationStatus(), + ), + Tab( + child: _getSyncStatus(), ), Tab( child: _isTabSelected(Tabs.lock) @@ -363,7 +360,7 @@ class _MainAppContainerState extends State ]; } - Widget _getWebsocketConnectionStatusStreamBuilder() { + Widget _getSyncStatus() { return StreamBuilder( stream: _netSyncStatusBloc.stream, builder: (_, snapshot) { @@ -378,8 +375,34 @@ class _MainAppContainerState extends State ); } + Widget _getGenerationStatus() { + return StreamBuilder( + stream: sl.get().stream, + builder: (_, snapshot) { + if (snapshot.hasData && snapshot.data == PowStatus.generating) { + return Tooltip( + message: 'Generating Plasma', + child: Lottie.asset( + 'assets/lottie/ic_anim_plasma_generation.json', + fit: BoxFit.contain, + width: 30.0, + repeat: true, + ), + ); + } + return Tooltip( + message: 'Plasma generation idle', + child: Icon( + MaterialCommunityIcons.lightning_bolt, + color: Theme.of(context).iconTheme.color, + ), + ); + }, + ); + } + Widget _getSyncingStatusIcon(SyncState syncState, [SyncInfo? syncInfo]) { - var message = 'Connected and synced'; + String message = 'Connected and synced'; if (syncState != SyncState.notEnoughPeers && syncState != SyncState.syncDone && @@ -390,6 +413,13 @@ class _MainAppContainerState extends State if (syncState == SyncState.unknown) { message = 'Not ready'; + return Tooltip( + message: message, + child: Icon( + Icons.sync_disabled, + size: 24.0, + color: _getSyncIconColor(syncState), + )); } else if (syncState == SyncState.syncing) { if (syncInfo != null) { if (syncInfo.targetHeight > 0 && @@ -397,12 +427,44 @@ class _MainAppContainerState extends State (syncInfo.targetHeight - syncInfo.currentHeight) < 3) { message = 'Connected and synced'; syncState = SyncState.syncDone; + return Tooltip( + message: message, + child: Icon( + Icons.radio_button_unchecked, + size: 24.0, + color: _getSyncIconColor(syncState), + )); + } else if (syncInfo.targetHeight == 0 || syncInfo.currentHeight == 0) { + message = 'Started syncing with the network, please wait'; + syncState = SyncState.syncing; + return Tooltip( + message: message, + child: Icon(Icons.sync, + size: 24.0, color: _getSyncIconColor(syncState))); } else { message = 'Sync progress: momentum ${syncInfo.currentHeight} of ${syncInfo.targetHeight}'; + return Tooltip( + message: message, + child: SizedBox( + height: 18.0, + width: 18.0, + child: Center( + child: CircularProgressIndicator( + backgroundColor: Theme.of(context).iconTheme.color, + color: _getSyncIconColor(syncState), + value: syncInfo.currentHeight / syncInfo.targetHeight, + strokeWidth: 3.0, + )), + ), + ); } } else { message = 'Syncing momentums'; + return Tooltip( + message: message, + child: Icon(Icons.sync, + size: 24.0, color: _getSyncIconColor(syncState))); } } else if (syncState == SyncState.notEnoughPeers) { if (syncInfo != null) { @@ -411,17 +473,49 @@ class _MainAppContainerState extends State (syncInfo.targetHeight - syncInfo.currentHeight) < 20) { message = 'Connecting to peers'; syncState = SyncState.syncing; + return Tooltip( + message: message, + child: SizedBox( + height: 18.0, + width: 18.0, + child: Center( + child: CircularProgressIndicator( + backgroundColor: Theme.of(context).iconTheme.color, + color: _getSyncIconColor(syncState), + value: syncInfo.currentHeight / syncInfo.targetHeight, + strokeWidth: 3.0, + )))); } else if (syncInfo.targetHeight == 0 || syncInfo.currentHeight == 0) { - message = 'Connecting to peers'; + message = 'Connecting to peers, please wait'; syncState = SyncState.syncing; + return Tooltip( + message: message, + child: Icon(Icons.sync, + size: 24.0, color: _getSyncIconColor(syncState))); } else { message = 'Sync progress: momentum ${syncInfo.currentHeight} of ${syncInfo.targetHeight}'; syncState = SyncState.syncing; + return Tooltip( + message: message, + child: SizedBox( + height: 18.0, + width: 18.0, + child: Center( + child: CircularProgressIndicator( + backgroundColor: Theme.of(context).iconTheme.color, + color: _getSyncIconColor(syncState), + value: syncInfo.currentHeight / syncInfo.targetHeight, + strokeWidth: 3.0, + )))); } } else { message = 'Connecting to peers'; syncState = SyncState.syncing; + return Tooltip( + message: message, + child: Icon(Icons.sync_problem, + size: 24.0, color: _getSyncIconColor(syncState))); } } else { message = 'Connected and synced'; @@ -430,10 +524,16 @@ class _MainAppContainerState extends State return Tooltip( message: message, - child: Icon( - Icons.radio_button_unchecked, - size: 24.0, - color: _getSyncIconColor(syncState), + child: SizedBox( + height: 18.0, + width: 18.0, + child: Center( + child: CircularProgressIndicator( + backgroundColor: Theme.of(context).iconTheme.color, + color: _getSyncIconColor(syncState), + value: 1, + strokeWidth: 2.0, + )), ), ); } @@ -472,7 +572,6 @@ class _MainAppContainerState extends State const NotificationsTabChild(), SettingsTabChild( _onChangeAutoLockTime, - _onResyncWalletPressed, onStepperNotificationSeeMorePressed: () => _navigateTo( Tabs.notifications, ), @@ -481,6 +580,7 @@ class _MainAppContainerState extends State ), ), const SizedBox(), + const SizedBox(), LockTabChild(_mainLockCallback, _afterAppInitCallback), ], ); @@ -508,8 +608,8 @@ class _MainAppContainerState extends State super.dispose(); } - void _onChangeAutoLockTime() { - sl.get().addNotification( + Future _onChangeAutoLockTime() async { + await sl.get().addNotification( WalletNotification( title: 'Auto-lock interval changed successfully', details: 'Auto-lock interval changed successfully to ' @@ -529,6 +629,7 @@ class _MainAppContainerState extends State } else { _lockBloc.addEvent(LockEvent.navigateToDashboard); } + _listenToAutoReceiveTxWorkerNotifications(); } @@ -538,28 +639,6 @@ class _MainAppContainerState extends State }); } - void _onResyncWalletPressed() { - _navigateTo(Tabs.resyncWallet); - } - - Widget _getPowGeneratingStatus() { - return StreamBuilder( - stream: sl.get().stream, - builder: (_, snapshot) { - if (snapshot.hasData && snapshot.data == PowStatus.generating) { - return const Tooltip( - message: 'Generating Plasma', - child: SyriusLoadingWidget( - size: 20.0, - strokeWidth: 2.5, - ), - ); - } - return _getWebsocketConnectionStatusStreamBuilder(); - }, - ); - } - bool _isTabSelected(Tabs page) => _tabController!.index == kTabs.indexOf(page); @@ -672,7 +751,7 @@ class _MainAppContainerState extends State } void _handleIncomingLinks() async { - if (!kIsWeb) { + if (!kIsWeb && !Platform.isLinux) { _incomingLinkSubscription = _appLinks.allUriLinkStream.listen((Uri? uri) async { if (!await windowManager.isFocused() || @@ -693,7 +772,7 @@ class _MainAppContainerState extends State } String wcUri = Uri.decodeFull(uriRaw.split('wc?uri=').last); if (WalletConnectUri.tryParse(wcUri) != null) { - _updateWalletConnectUri(wcUri); + await _updateWalletConnectUri(wcUri); } return; } @@ -741,7 +820,7 @@ class _MainAppContainerState extends State if (context.mounted) { switch (uri.host) { case 'transfer': - sl().addNotification( + await sl().addNotification( WalletNotification( title: 'Transfer action detected', timestamp: DateTime.now().millisecondsSinceEpoch, @@ -783,7 +862,7 @@ class _MainAppContainerState extends State break; case 'stake': - sl().addNotification( + await sl().addNotification( WalletNotification( title: 'Stake action detected', timestamp: DateTime.now().millisecondsSinceEpoch, @@ -818,7 +897,7 @@ class _MainAppContainerState extends State break; case 'delegate': - sl().addNotification( + await sl().addNotification( WalletNotification( title: 'Delegate action detected', timestamp: DateTime.now().millisecondsSinceEpoch, @@ -851,7 +930,7 @@ class _MainAppContainerState extends State break; case 'fuse': - sl().addNotification( + await sl().addNotification( WalletNotification( title: 'Fuse ${kQsrCoin.symbol} action detected', timestamp: DateTime.now().millisecondsSinceEpoch, @@ -885,7 +964,7 @@ class _MainAppContainerState extends State break; case 'sentinel': - sl().addNotification( + await sl().addNotification( WalletNotification( title: 'Deploy Sentinel action detected', timestamp: DateTime.now().millisecondsSinceEpoch, @@ -900,7 +979,7 @@ class _MainAppContainerState extends State break; case 'pillar': - sl().addNotification( + await sl().addNotification( WalletNotification( title: 'Deploy Pillar action detected', timestamp: DateTime.now().millisecondsSinceEpoch, @@ -915,7 +994,7 @@ class _MainAppContainerState extends State break; default: - sl().addNotification( + await sl().addNotification( WalletNotification( title: 'Incoming link detected', timestamp: DateTime.now().millisecondsSinceEpoch, @@ -932,8 +1011,8 @@ class _MainAppContainerState extends State }, onDone: () { Logger('MainAppContainer') .log(Level.INFO, '_handleIncomingLinks', 'done'); - }, onError: (Object err) { - NotificationUtils.sendNotificationError( + }, onError: (Object err) async { + await NotificationUtils.sendNotificationError( err, 'Handle incoming link failed'); Logger('MainAppContainer') .log(Level.WARNING, '_handleIncomingLinks', err); @@ -975,11 +1054,11 @@ class _MainAppContainerState extends State } } - void _updateWalletConnectUri(String text) { + Future _updateWalletConnectUri(String text) async { kLastWalletConnectUriNotifier.value = text; if (!_isWalletLocked()) { if (kCurrentPage != Tabs.walletConnect) { - sl().addNotification( + await sl().addNotification( WalletNotification( title: 'WalletConnect link detected. Go to WalletConnect tab to connect.', diff --git a/lib/widgets/modular_widgets/accelerator_widgets/accelerator_donation_stepper.dart b/lib/widgets/modular_widgets/accelerator_widgets/accelerator_donation_stepper.dart index 774a4cae..9d512f7d 100644 --- a/lib/widgets/modular_widgets/accelerator_widgets/accelerator_donation_stepper.dart +++ b/lib/widgets/modular_widgets/accelerator_widgets/accelerator_donation_stepper.dart @@ -192,7 +192,10 @@ class _AcceleratorDonationStepperState crossAxisAlignment: CrossAxisAlignment.start, children: [ DisabledAddressField(_addressController), - StepperUtils.getBalanceWidget(kZnnCoin, accountInfo), + Row(children: [ + StepperUtils.getBalanceWidget(kZnnCoin, accountInfo), + StepperUtils.getBalanceWidget(kQsrCoin, accountInfo), + ]), const DottedBorderInfoWidget( text: 'All donated funds go directly into the Accelerator address', ), @@ -209,7 +212,8 @@ class _AcceleratorDonationStepperState width: 15.0, ), StepperButton( - onPressed: accountInfo.znn()! > BigInt.zero + onPressed: accountInfo.znn()! > BigInt.zero || + accountInfo.qsr()! > BigInt.zero ? () { setState(() { _lastCompletedStep = @@ -358,26 +362,37 @@ class _AcceleratorDonationStepperState } catch (_) {} return InputValidators.correctValue( - _znnAmountController.text, - accountInfo.znn()!, - coinDecimals, - BigInt.zero, - canBeEqualToMin: true, - canBeBlank: true, - ) == - null && - InputValidators.correctValue( - _qsrAmountController.text, - accountInfo.qsr()!, - coinDecimals, - BigInt.zero, - canBeEqualToMin: true, - canBeBlank: true, - ) == - null && - (_qsrAmountController.text.isNotEmpty || - _znnAmountController.text.isNotEmpty) && - (_znnAmount > BigInt.zero || _qsrAmount > BigInt.zero); + _znnAmountController.text, + accountInfo.znn()!, + coinDecimals, + BigInt.zero, + canBeEqualToMin: true, + canBeBlank: true, + ) == + null && + InputValidators.correctValue( + _qsrAmountController.text, + accountInfo.qsr()!, + coinDecimals, + BigInt.zero, + canBeEqualToMin: true, + canBeBlank: true, + ) == + null && + (_znnAmountController.text.isNotEmpty && + _qsrAmountController.text.isNotEmpty && + _znnAmount > BigInt.zero && + _znnAmount <= accountInfo.znn()! && + _qsrAmount > BigInt.zero && + _qsrAmount <= accountInfo.qsr()!) || + (_znnAmountController.text.isNotEmpty && + _qsrAmountController.text.isEmpty && + _znnAmount > BigInt.zero && + _znnAmount <= accountInfo.znn()!) || + (_znnAmountController.text.isEmpty && + !_qsrAmountController.text.isNotEmpty && + _qsrAmount > BigInt.zero && + _qsrAmount <= accountInfo.qsr()!); } Widget _getSubmitDonationStepContent() { @@ -435,9 +450,9 @@ class _AcceleratorDonationStepperState }); } }, - onError: (error) { + onError: (error) async { _submitButtonKey.currentState?.animateReverse(); - NotificationUtils.sendNotificationError( + await NotificationUtils.sendNotificationError( error, 'Error while submitting donation', ); diff --git a/lib/widgets/modular_widgets/accelerator_widgets/accelerator_project_list_item.dart b/lib/widgets/modular_widgets/accelerator_widgets/accelerator_project_list_item.dart index d0d03a39..52e038c5 100644 --- a/lib/widgets/modular_widgets/accelerator_widgets/accelerator_project_list_item.dart +++ b/lib/widgets/modular_widgets/accelerator_widgets/accelerator_project_list_item.dart @@ -72,6 +72,7 @@ class _AcceleratorProjectListItemState hash: widget.acceleratorProject.id, creationTimestamp: widget.acceleratorProject.creationTimestamp, acceleratorProjectStatus: widget.acceleratorProject.status, + isPhase: widget.acceleratorProject is Phase ), const SizedBox( height: 10.0, @@ -591,8 +592,8 @@ class _AcceleratorProjectListItemState widget.acceleratorProject.id, ); } - }, onError: (error) { - NotificationUtils.sendNotificationError( + }, onError: (error) async { + await NotificationUtils.sendNotificationError( error, 'Error while voting project', ); @@ -621,8 +622,8 @@ class _AcceleratorProjectListItemState onViewModelReady: (model) { model.getVoteBreakdown( widget.pillarInfo?.name, widget.acceleratorProject.id); - model.stream.listen((event) {}, onError: (error) { - NotificationUtils.sendNotificationError( + model.stream.listen((event) {}, onError: (error) async { + await NotificationUtils.sendNotificationError( error, 'Error while trying to get the vote breakdown', ); diff --git a/lib/widgets/modular_widgets/accelerator_widgets/phase_creation_stepper.dart b/lib/widgets/modular_widgets/accelerator_widgets/phase_creation_stepper.dart index 95fb217f..7056ab5b 100644 --- a/lib/widgets/modular_widgets/accelerator_widgets/phase_creation_stepper.dart +++ b/lib/widgets/modular_widgets/accelerator_widgets/phase_creation_stepper.dart @@ -447,9 +447,9 @@ class _PhaseCreationStepperState extends State { }); } }, - onError: (error) { + onError: (error) async { _submitButtonKey.currentState?.animateReverse(); - NotificationUtils.sendNotificationError( + await NotificationUtils.sendNotificationError( error, 'Error while creating phase', ); diff --git a/lib/widgets/modular_widgets/accelerator_widgets/project_creation_stepper.dart b/lib/widgets/modular_widgets/accelerator_widgets/project_creation_stepper.dart index bc31c31e..db1094cc 100644 --- a/lib/widgets/modular_widgets/accelerator_widgets/project_creation_stepper.dart +++ b/lib/widgets/modular_widgets/accelerator_widgets/project_creation_stepper.dart @@ -487,9 +487,9 @@ class _ProjectCreationStepperState extends State { }); } }, - onError: (error) { + onError: (error) async { _submitButtonKey.currentState?.animateReverse(); - NotificationUtils.sendNotificationError( + await NotificationUtils.sendNotificationError( error, 'Error while submitting project', ); diff --git a/lib/widgets/modular_widgets/accelerator_widgets/project_list.dart b/lib/widgets/modular_widgets/accelerator_widgets/project_list.dart index 3619d148..ede6c039 100644 --- a/lib/widgets/modular_widgets/accelerator_widgets/project_list.dart +++ b/lib/widgets/modular_widgets/accelerator_widgets/project_list.dart @@ -12,7 +12,7 @@ import 'package:znn_sdk_dart/znn_sdk_dart.dart'; enum AccProjectsFilterTag { myProjects, onlyAccepted, - votingOpened, + needsVoting, alreadyVoted, } @@ -168,7 +168,7 @@ class _AccProjectListState extends State { for (var tag in AccProjectsFilterTag.values) { if (widget.pillarInfo == null) { if ([ - AccProjectsFilterTag.votingOpened, + AccProjectsFilterTag.needsVoting, AccProjectsFilterTag.alreadyVoted ].contains(tag)) { continue; diff --git a/lib/widgets/modular_widgets/accelerator_widgets/projects_stats.dart b/lib/widgets/modular_widgets/accelerator_widgets/projects_stats.dart index 03c2a44b..9481379a 100644 --- a/lib/widgets/modular_widgets/accelerator_widgets/projects_stats.dart +++ b/lib/widgets/modular_widgets/accelerator_widgets/projects_stats.dart @@ -37,7 +37,7 @@ class ProjectsStats extends StatelessWidget { AcceleratorProjectDetails( owner: project.owner, hash: project.id, - creationTimestamp: null, + creationTimestamp: project.creationTimestamp ), ], ), diff --git a/lib/widgets/modular_widgets/accelerator_widgets/update_phase_stepper.dart b/lib/widgets/modular_widgets/accelerator_widgets/update_phase_stepper.dart index 2b7b7466..c419575d 100644 --- a/lib/widgets/modular_widgets/accelerator_widgets/update_phase_stepper.dart +++ b/lib/widgets/modular_widgets/accelerator_widgets/update_phase_stepper.dart @@ -401,8 +401,8 @@ class _UpdatePhaseStepperState extends State { }); } }, - onError: (error) { - NotificationUtils.sendNotificationError( + onError: (error) async { + await NotificationUtils.sendNotificationError( error, 'Error while updating phase', ); diff --git a/lib/widgets/modular_widgets/dashboard_widgets/realtime_statistics.dart b/lib/widgets/modular_widgets/dashboard_widgets/realtime_statistics.dart index d5d4b2a8..98b7b755 100644 --- a/lib/widgets/modular_widgets/dashboard_widgets/realtime_statistics.dart +++ b/lib/widgets/modular_widgets/dashboard_widgets/realtime_statistics.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import 'package:stacked/stacked.dart'; import 'package:zenon_syrius_wallet_flutter/blocs/blocs.dart'; import 'package:zenon_syrius_wallet_flutter/utils/color_utils.dart'; -import 'package:zenon_syrius_wallet_flutter/utils/constants.dart'; import 'package:zenon_syrius_wallet_flutter/utils/zts_utils.dart'; import 'package:zenon_syrius_wallet_flutter/widgets/widgets.dart'; import 'package:znn_sdk_dart/znn_sdk_dart.dart'; @@ -39,9 +38,9 @@ class _RealtimeStatisticsState extends State { } Widget _widgetBody(List list) { - return Column( - children: [ - kVerticalSpacing, + return Padding( + padding: const EdgeInsets.all(16.0), + child: Column(children: [ Row( mainAxisAlignment: MainAxisAlignment.center, children: [ @@ -60,10 +59,8 @@ class _RealtimeStatisticsState extends State { ), ], ), - Expanded( - child: RealtimeTxsChart(list), - ), - ], + Expanded(child: RealtimeTxsChart(list)), + ]), ); } } diff --git a/lib/widgets/modular_widgets/help_widgets/about_card.dart b/lib/widgets/modular_widgets/help_widgets/about_card.dart index 8522a87c..2f850b99 100644 --- a/lib/widgets/modular_widgets/help_widgets/about_card.dart +++ b/lib/widgets/modular_widgets/help_widgets/about_card.dart @@ -120,6 +120,11 @@ class AboutCardState extends State { _getGenericOpenButtonExpandedChild( znnDefaultPaths.wallet.absolute.path), ), + CustomExpandablePanel( + 'Syrius wallet type', + _getGenericOpenButtonExpandedChild( + kWalletFile!.walletType), + ), CustomExpandablePanel( 'Client hostname', _getGenericTextExpandedChild(Platform.localHostname), diff --git a/lib/widgets/modular_widgets/modular_widgets.dart b/lib/widgets/modular_widgets/modular_widgets.dart index 23ab6e71..bea35390 100644 --- a/lib/widgets/modular_widgets/modular_widgets.dart +++ b/lib/widgets/modular_widgets/modular_widgets.dart @@ -1,7 +1,7 @@ export 'accelerator_widgets/accelerator_widgets.dart'; export 'dashboard_widgets/dashboard_widgets.dart'; export 'help_widgets/help_widgets.dart'; -export 'pillars_widgets/pillars_widgets.dart'; +export 'pillar_widgets/pillar_widgets.dart'; export 'plasma_widgets/plasma_widgets.dart'; export 'sentinel_widgets/sentinel_widgets.dart'; export 'settings_widgets/settings_widgets.dart'; diff --git a/lib/widgets/modular_widgets/pillars_widgets/create_pillar.dart b/lib/widgets/modular_widgets/pillar_widgets/create_pillar.dart similarity index 98% rename from lib/widgets/modular_widgets/pillars_widgets/create_pillar.dart rename to lib/widgets/modular_widgets/pillar_widgets/create_pillar.dart index 5c3aa018..c05533ca 100644 --- a/lib/widgets/modular_widgets/pillars_widgets/create_pillar.dart +++ b/lib/widgets/modular_widgets/pillar_widgets/create_pillar.dart @@ -65,7 +65,7 @@ class _CreatePillarState extends State { context, MaterialPageRoute( builder: (context) => StepperScreen( - stepper: const PillarsStepperContainer(), + stepper: const PillarStepperContainer(), onStepperNotificationSeeMorePressed: widget.onStepperNotificationSeeMorePressed, ), diff --git a/lib/widgets/modular_widgets/pillars_widgets/pillar_collect.dart b/lib/widgets/modular_widgets/pillar_widgets/pillar_collect.dart similarity index 97% rename from lib/widgets/modular_widgets/pillars_widgets/pillar_collect.dart rename to lib/widgets/modular_widgets/pillar_widgets/pillar_collect.dart index 2e186519..d4662c73 100644 --- a/lib/widgets/modular_widgets/pillars_widgets/pillar_collect.dart +++ b/lib/widgets/modular_widgets/pillar_widgets/pillar_collect.dart @@ -112,8 +112,8 @@ class _PillarCollectState extends State { }, ); } catch (e) { - NotificationUtils.sendNotificationError( - e, 'Error while collecting rewards'); + await NotificationUtils.sendNotificationError( + e, 'Error while collecting Pillar rewards'); } finally { _collectButtonKey.currentState?.animateReverse(); } diff --git a/lib/widgets/modular_widgets/pillars_widgets/pillars_list_widget.dart b/lib/widgets/modular_widgets/pillar_widgets/pillar_list_widget.dart similarity index 97% rename from lib/widgets/modular_widgets/pillars_widgets/pillars_list_widget.dart rename to lib/widgets/modular_widgets/pillar_widgets/pillar_list_widget.dart index c8ef208f..ddb59ef4 100644 --- a/lib/widgets/modular_widgets/pillars_widgets/pillars_list_widget.dart +++ b/lib/widgets/modular_widgets/pillar_widgets/pillar_list_widget.dart @@ -14,16 +14,16 @@ import 'package:zenon_syrius_wallet_flutter/utils/zts_utils.dart'; import 'package:zenon_syrius_wallet_flutter/widgets/widgets.dart'; import 'package:znn_sdk_dart/znn_sdk_dart.dart'; -class PillarsListWidget extends StatefulWidget { +class PillarListWidget extends StatefulWidget { final String? title; - const PillarsListWidget({Key? key, this.title}) : super(key: key); + const PillarListWidget({Key? key, this.title}) : super(key: key); @override - State createState() => _PillarsListWidgetState(); + State createState() => _PillarListWidgetState(); } -class _PillarsListWidgetState extends State { +class _PillarListWidgetState extends State { final ScrollController _scrollController = ScrollController(); final PagingController _pagingController = PagingController( @@ -401,7 +401,7 @@ class _PillarsListWidgetState extends State { width: 5.0, ), ), - Expanded( + SizedBox( child: pillarItem.isRevocable ? CancelTimer( Duration( @@ -451,8 +451,8 @@ class _PillarsListWidgetState extends State { pillarsListModel.refreshResults(); } }, - onError: (error) { - NotificationUtils.sendNotificationError( + onError: (error) async { + await NotificationUtils.sendNotificationError( error, 'Error while disassembling Pillar', ); @@ -583,9 +583,9 @@ class _PillarsListWidgetState extends State { _delegationInfoBloc.updateStream(); } }, - onError: (error) { + onError: (error) async { undelegateButtonKey.currentState?.animateReverse(); - NotificationUtils.sendNotificationError( + await NotificationUtils.sendNotificationError( error, 'Error while undelegating', ); @@ -655,9 +655,9 @@ class _PillarsListWidgetState extends State { }); } }, - onError: (error) { + onError: (error) async { delegateButtonKey.currentState?.animateReverse(); - NotificationUtils.sendNotificationError( + await NotificationUtils.sendNotificationError( error, 'Pillar delegation error', ); @@ -679,6 +679,7 @@ class _PillarsListWidgetState extends State { @override void dispose() { + _scrollController.dispose(); _pillarsListBloc.dispose(); _blocListingStateSubscription.cancel(); super.dispose(); diff --git a/lib/widgets/modular_widgets/pillars_widgets/pillar_rewards.dart b/lib/widgets/modular_widgets/pillar_widgets/pillar_rewards.dart similarity index 100% rename from lib/widgets/modular_widgets/pillars_widgets/pillar_rewards.dart rename to lib/widgets/modular_widgets/pillar_widgets/pillar_rewards.dart diff --git a/lib/widgets/modular_widgets/pillars_widgets/pillars_stepper_container.dart b/lib/widgets/modular_widgets/pillar_widgets/pillar_stepper_container.dart similarity index 89% rename from lib/widgets/modular_widgets/pillars_widgets/pillars_stepper_container.dart rename to lib/widgets/modular_widgets/pillar_widgets/pillar_stepper_container.dart index 49aea9f4..b7b7c0c7 100644 --- a/lib/widgets/modular_widgets/pillars_widgets/pillars_stepper_container.dart +++ b/lib/widgets/modular_widgets/pillar_widgets/pillar_stepper_container.dart @@ -26,28 +26,27 @@ enum PillarType { regularPillar, } -enum PillarsStepperStep { +enum PillarStepperStep { checkPlasma, - selectPillarType, qsrManagement, znnManagement, deployPillar, } -class PillarsStepperContainer extends StatefulWidget { - const PillarsStepperContainer({Key? key}) : super(key: key); +class PillarStepperContainer extends StatefulWidget { + const PillarStepperContainer({Key? key}) : super(key: key); @override State createState() { - return _MainPillarsState(); + return _MainPillarState(); } } -class _MainPillarsState extends State { - late PillarsStepperStep _currentStep; - PillarsStepperStep? _lastCompletedStep; +class _MainPillarState extends State { + late PillarStepperStep _currentStep; + PillarStepperStep? _lastCompletedStep; - final int _numSteps = PillarsStepperStep.values.length; + final int _numSteps = PillarStepperStep.values.length; PillarType? _selectedPillarType = PillarType.regularPillar; @@ -59,7 +58,6 @@ class _MainPillarsState extends State { TextEditingController(); final TextEditingController _znnAmountController = TextEditingController(); final TextEditingController _addressController = TextEditingController(); - final TextEditingController _passwordController = TextEditingController(); final FocusNode _pillarNameNode = FocusNode(); final FocusNode _pillarRewardNode = FocusNode(); @@ -221,9 +219,11 @@ class _MainPillarsState extends State { _qsrAmountController.text, ), controller: _qsrAmountController, - validator: (value) => _qsrAmountValidator( + validator: (value) => InputValidators.correctValue( value, - qsrInfo, + _maxQsrAmount, + kQsrCoin.decimals, + BigInt.zero, ), suffixIcon: _getAmountSuffix(accountInfo), suffixIconConstraints: @@ -252,7 +252,7 @@ class _MainPillarsState extends State { children: [ Visibility( visible: qsrInfo.deposit < qsrInfo.cost, - child: _getDepositQsrViewModel(qsrInfo), + child: _getDepositQsrViewModel(accountInfo, qsrInfo), ), Visibility( visible: qsrInfo.deposit >= qsrInfo.cost, @@ -353,7 +353,8 @@ class _MainPillarsState extends State { ); } - Widget _getDepositQsrViewModel(PillarsQsrInfo qsrInfo) { + Widget _getDepositQsrViewModel( + AccountInfo accountInfo, PillarsQsrInfo qsrInfo) { return ViewModelBuilder.reactive( onViewModelReady: (model) { model.stream.listen( @@ -369,9 +370,9 @@ class _MainPillarsState extends State { setState(() {}); } }, - onError: (error) { + onError: (error) async { _depositQsrButtonKey.currentState?.animateReverse(); - NotificationUtils.sendNotificationError( + await NotificationUtils.sendNotificationError( error, 'Error while depositing ${kQsrCoin.symbol}', ); @@ -379,19 +380,22 @@ class _MainPillarsState extends State { }, ); }, - builder: (_, model, __) => _getDepositQsrButton(model, qsrInfo), + builder: (_, model, __) => + _getDepositQsrButton(model, accountInfo, qsrInfo), viewModelBuilder: () => PillarsDepositQsrBloc(), ); } Widget _getDepositQsrButton( PillarsDepositQsrBloc model, + AccountInfo accountInfo, PillarsQsrInfo qsrInfo, ) { return LoadingButton.stepper( key: _depositQsrButtonKey, text: 'Deposit', - onPressed: _qsrAmountValidator(_qsrAmountController.text, qsrInfo) == null + onPressed: _hasQsrBalance(accountInfo) && + _qsrAmountValidator(_qsrAmountController.text, qsrInfo) == null ? () => _onDepositButtonPressed(model, qsrInfo) : null, outlineColor: AppColors.qsrColor, @@ -408,7 +412,7 @@ class _MainPillarsState extends State { if (event != null) { _withdrawButtonKey.currentState?.animateReverse(); _saveProgressAndNavigateToNextStep( - PillarsStepperStep.checkPlasma, + PillarStepperStep.checkPlasma, ); _pillarsQsrInfoViewModel.getQsrManagementInfo( _selectedPillarType, @@ -416,9 +420,9 @@ class _MainPillarsState extends State { ); } }, - onError: (error) { + onError: (error) async { _withdrawButtonKey.currentState?.animateReverse(); - NotificationUtils.sendNotificationError( + await NotificationUtils.sendNotificationError( error, 'Error while withdrawing ${kQsrCoin.symbol}', ); @@ -461,17 +465,7 @@ class _MainPillarsState extends State { stepContent: _getPlasmaCheckFutureBuilder(), stepSubtitle: 'Sufficient Plasma', stepState: StepperUtils.getStepState( - PillarsStepperStep.checkPlasma.index, - _lastCompletedStep?.index, - ), - context: context, - ), - StepperUtils.getMaterialStep( - stepTitle: 'Pillar type', - stepContent: _getPillarTypeStepBody(), - stepSubtitle: _getPillarTypeStepSubtitle(), - stepState: StepperUtils.getStepState( - PillarsStepperStep.selectPillarType.index, + PillarStepperStep.checkPlasma.index, _lastCompletedStep?.index, ), context: context, @@ -479,9 +473,9 @@ class _MainPillarsState extends State { StepperUtils.getMaterialStep( stepTitle: '${kQsrCoin.symbol} management', stepContent: _getQsrManagementStep(context, accountInfo), - stepSubtitle: '${kQsrCoin.symbol} Deposited', + stepSubtitle: '${kQsrCoin.symbol} deposited', stepState: StepperUtils.getStepState( - PillarsStepperStep.qsrManagement.index, + PillarStepperStep.qsrManagement.index, _lastCompletedStep?.index, ), context: context, @@ -492,7 +486,7 @@ class _MainPillarsState extends State { stepContent: _getZnnManagementStepBody(context, accountInfo), stepSubtitle: '${kZnnCoin.symbol} locked', stepState: StepperUtils.getStepState( - PillarsStepperStep.znnManagement.index, + PillarStepperStep.znnManagement.index, _lastCompletedStep?.index, ), context: context, @@ -502,7 +496,7 @@ class _MainPillarsState extends State { stepContent: _getDeployPillarStepBody(context), stepSubtitle: 'Pillar registered', stepState: StepperUtils.getStepState( - PillarsStepperStep.deployPillar.index, + PillarStepperStep.deployPillar.index, _lastCompletedStep?.index, ), context: context, @@ -512,15 +506,6 @@ class _MainPillarsState extends State { ); } - String _getPillarTypeStepSubtitle() { - switch (_selectedPillarType) { - case PillarType.regularPillar: - return 'Pillar'; - default: - return 'No Pillar type selected'; - } - } - Widget _getAmountSuffix(AccountInfo accountInfo) { return Row( children: [ @@ -652,16 +637,16 @@ class _MainPillarsState extends State { if (response != null) { _registerButtonKey.currentState?.animateReverse(); _saveProgressAndNavigateToNextStep( - PillarsStepperStep.deployPillar, + PillarStepperStep.deployPillar, ); setState(() {}); } else { setState(() {}); } }, - onError: (error) { + onError: (error) async { _registerButtonKey.currentState?.animateReverse(); - NotificationUtils.sendNotificationError( + await NotificationUtils.sendNotificationError( error, 'Error while deploying a Pillar'); setState(() {}); }, @@ -695,7 +680,9 @@ class _MainPillarsState extends State { ), ], ), + kVerticalSpacing, StepperUtils.getBalanceWidget(kZnnCoin, accountInfo), + kVerticalSpacing, Row( children: [ Expanded( @@ -742,21 +729,21 @@ class _MainPillarsState extends State { } void _onNextPressed() { - if (_lastCompletedStep == PillarsStepperStep.qsrManagement) { - _saveProgressAndNavigateToNextStep(PillarsStepperStep.znnManagement); + if (_lastCompletedStep == PillarStepperStep.qsrManagement) { + _saveProgressAndNavigateToNextStep(PillarStepperStep.znnManagement); } else if (StepperUtils.getStepState( - PillarsStepperStep.qsrManagement.index, + PillarStepperStep.qsrManagement.index, _lastCompletedStep?.index, ) == custom_material_stepper.StepState.complete) { setState(() { - _currentStep = PillarsStepperStep.values[_currentStep.index + 1]; + _currentStep = PillarStepperStep.values[_currentStep.index + 1]; }); } } void _onDeployPressed(PillarsDeployBloc model) { - if (_lastCompletedStep == PillarsStepperStep.znnManagement) { + if (_lastCompletedStep == PillarStepperStep.znnManagement) { if (_pillarFormKeys .every((element) => element.currentState!.validate())) { _registerButtonKey.currentState?.animateForward(); @@ -929,69 +916,17 @@ class _MainPillarsState extends State { }); } - Widget _getPillarTypeStepBody() { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Text( - 'Please choose the type of Pillar you want to register', - style: Theme.of(context).textTheme.headlineSmall, - ), - ], - ), - _getPillarTypeListTile('Pillar', PillarType.regularPillar), - _getContinueButton(), - ], - ); - } - - Widget _getContinueButton() { - return StepperButton( - onPressed: _selectedPillarType != null - ? () => _onPillarTypeContinuePressed() - : null, - text: 'Continue', - ); - } - - Widget _getPillarTypeListTile(String text, PillarType value) { - return ListTile( - title: Text( - text, - style: Theme.of(context).textTheme.bodyLarge, - ), - leading: Radio( - activeColor: AppColors.znnColor, - value: value, - groupValue: _selectedPillarType, - onChanged: (PillarType? value) { - setState(() { - _selectedPillarType = value; - _pillarsQsrInfoViewModel.getQsrManagementInfo( - _selectedPillarType, _addressController.text); - }); - }, - ), - ); - } - - void _onPillarTypeContinuePressed() { - _saveProgressAndNavigateToNextStep(PillarsStepperStep.selectPillarType); - } - - void _saveProgressAndNavigateToNextStep(PillarsStepperStep completedStep) { + void _saveProgressAndNavigateToNextStep(PillarStepperStep completedStep) { setState(() { _lastCompletedStep = completedStep; if (_lastCompletedStep!.index + 1 < _numSteps) { - _currentStep = PillarsStepperStep.values[completedStep.index + 1]; + _currentStep = PillarStepperStep.values[completedStep.index + 1]; } }); } void _iniStepperControllers() { - _currentStep = PillarsStepperStep.values.first; + _currentStep = PillarStepperStep.values.first; _selectedPillarType = PillarType.values.first; } @@ -1015,6 +950,9 @@ class _MainPillarsState extends State { ) == null; + bool _hasQsrBalance(AccountInfo accountInfo) => + accountInfo.qsr()! > BigInt.zero; + String? _qsrAmountValidator(String? value, PillarsQsrInfo qsrInfo) => InputValidators.correctValue( value, @@ -1026,7 +964,7 @@ class _MainPillarsState extends State { void _onQsrNextPressed() { setState(() { - _saveProgressAndNavigateToNextStep(PillarsStepperStep.qsrManagement); + _saveProgressAndNavigateToNextStep(PillarStepperStep.qsrManagement); }); } @@ -1085,14 +1023,14 @@ class _MainPillarsState extends State { void _onPlasmaCheckNextPressed() { if (_lastCompletedStep == null) { - _saveProgressAndNavigateToNextStep(PillarsStepperStep.checkPlasma); + _saveProgressAndNavigateToNextStep(PillarStepperStep.checkPlasma); } else if (StepperUtils.getStepState( - PillarsStepperStep.checkPlasma.index, + PillarStepperStep.checkPlasma.index, _lastCompletedStep?.index, ) == custom_material_stepper.StepState.complete) { setState(() { - _currentStep = PillarsStepperStep.values[_currentStep.index + 1]; + _currentStep = PillarStepperStep.values[_currentStep.index + 1]; }); } } @@ -1181,7 +1119,6 @@ class _MainPillarsState extends State { _pillarMomentumController.dispose(); _znnAmountController.dispose(); _addressController.dispose(); - _passwordController.dispose(); _pillarNameNode.dispose(); _pillarRewardNode.dispose(); _pillarMomentumNode.dispose; diff --git a/lib/widgets/modular_widgets/pillars_widgets/pillar_update_stepper.dart b/lib/widgets/modular_widgets/pillar_widgets/pillar_update_stepper.dart similarity index 99% rename from lib/widgets/modular_widgets/pillars_widgets/pillar_update_stepper.dart rename to lib/widgets/modular_widgets/pillar_widgets/pillar_update_stepper.dart index b801345a..24ee9f7b 100644 --- a/lib/widgets/modular_widgets/pillars_widgets/pillar_update_stepper.dart +++ b/lib/widgets/modular_widgets/pillar_widgets/pillar_update_stepper.dart @@ -394,9 +394,9 @@ class _PillarUpdateStepperState extends State { }); } }, - onError: (error) { + onError: (error) async { _updateButtonKey.currentState?.animateReverse(); - NotificationUtils.sendNotificationError( + await NotificationUtils.sendNotificationError( error, 'Error while updating Pillar', ); diff --git a/lib/widgets/modular_widgets/pillars_widgets/pillars_widgets.dart b/lib/widgets/modular_widgets/pillar_widgets/pillar_widgets.dart similarity index 62% rename from lib/widgets/modular_widgets/pillars_widgets/pillars_widgets.dart rename to lib/widgets/modular_widgets/pillar_widgets/pillar_widgets.dart index b97ad09c..2975f3c2 100644 --- a/lib/widgets/modular_widgets/pillars_widgets/pillars_widgets.dart +++ b/lib/widgets/modular_widgets/pillar_widgets/pillar_widgets.dart @@ -1,6 +1,6 @@ export 'create_pillar.dart'; export 'pillar_collect.dart'; +export 'pillar_list_widget.dart'; export 'pillar_rewards.dart'; +export 'pillar_stepper_container.dart'; export 'pillar_update_stepper.dart'; -export 'pillars_list_widget.dart'; -export 'pillars_stepper_container.dart'; diff --git a/lib/widgets/modular_widgets/plasma_widgets/plasma_list/plasma_list.dart b/lib/widgets/modular_widgets/plasma_widgets/plasma_list/plasma_list.dart index d1b8af94..c725594f 100644 --- a/lib/widgets/modular_widgets/plasma_widgets/plasma_list/plasma_list.dart +++ b/lib/widgets/modular_widgets/plasma_widgets/plasma_list/plasma_list.dart @@ -120,9 +120,9 @@ class _PlasmaListState extends State { plasmaModel.refreshResults(); } }, - onError: (error) { + onError: (error) async { cancelButtonKey.currentState?.animateReverse(); - NotificationUtils.sendNotificationError( + await NotificationUtils.sendNotificationError( error, 'Error while cancelling plasma'); }, ); diff --git a/lib/widgets/modular_widgets/plasma_widgets/plasma_options/plasma_options.dart b/lib/widgets/modular_widgets/plasma_widgets/plasma_options/plasma_options.dart index 27b411fb..ca991821 100644 --- a/lib/widgets/modular_widgets/plasma_widgets/plasma_options/plasma_options.dart +++ b/lib/widgets/modular_widgets/plasma_widgets/plasma_options/plasma_options.dart @@ -132,7 +132,7 @@ class _PlasmaOptionsState extends State { _beneficiaryAddressController.text = _plasmaBeneficiaryAddress!.getBeneficiaryAddress()!; // Notify internal state has changed. - setState(() { }); + setState(() {}); } Widget _getWidgetBody(AccountInfo? accountInfo) { @@ -240,11 +240,13 @@ class _PlasmaOptionsState extends State { PlasmaInfo.fromJson( { 'currentPlasma': ((_qsrAmountController.text.isNotEmpty - ? int.parse((zenon!.embedded.plasma.getPlasmaByQsr( - _qsrAmountController.text.extractDecimals(coinDecimals), - )).addDecimals(coinDecimals)) - : 0) + - _getPlasmaForCurrentBeneficiary()), + ? BigInt.parse(zenon!.embedded.plasma + .getPlasmaByQsr(_qsrAmountController.text + .extractDecimals(coinDecimals)) + .addDecimals(coinDecimals)) + : BigInt.zero) + + BigInt.from(_getPlasmaForCurrentBeneficiary())) + .toInt(), 'maxPlasma': 0, 'qsrAmount': '0', }, @@ -340,9 +342,9 @@ class _PlasmaOptionsState extends State { widget.plasmaListBloc.refreshResults(); } }, - onError: (error) { + onError: (error) async { _fuseButtonKey.currentState?.animateReverse(); - NotificationUtils.sendNotificationError( + await NotificationUtils.sendNotificationError( error, 'Error while generating Plasma', ); diff --git a/lib/widgets/modular_widgets/sentinel_widgets/create_sentinel.dart b/lib/widgets/modular_widgets/sentinel_widgets/create_sentinel.dart index 64da18ce..65b759f2 100644 --- a/lib/widgets/modular_widgets/sentinel_widgets/create_sentinel.dart +++ b/lib/widgets/modular_widgets/sentinel_widgets/create_sentinel.dart @@ -29,11 +29,11 @@ class _CreateSentinelState extends State { title: 'Create Sentinel', description: 'Start the process of deploying a Sentinel Node in the ' 'network', - childBuilder: () => _widgetBody(), + childBuilder: () => _getStreamBuilder(context), ); } - Widget _widgetBody() { + Widget _getStreamBuilder(BuildContext context) { return StreamBuilder( stream: _getSentinelByOwnerBloc.stream, builder: (context, snapshot) { @@ -41,10 +41,10 @@ class _CreateSentinelState extends State { return SyriusErrorWidget(snapshot.error!); } if (snapshot.connectionState == ConnectionState.active) { - if (snapshot.hasData) { - return _getAlreadyCreatedSentinelBody(context); + if (snapshot.hasData && snapshot.data!.active) { + return _getAlreadyCreatedSentinelWidgetBody(context); } else { - return _getCreateSentinelBody(context); + return _getCreateSentinelWidgetBody(context); } } return const SyriusLoadingWidget(); @@ -52,7 +52,7 @@ class _CreateSentinelState extends State { ); } - Center _getAlreadyCreatedSentinelBody(BuildContext context) { + Center _getAlreadyCreatedSentinelWidgetBody(BuildContext context) { return Center( child: Row( mainAxisAlignment: MainAxisAlignment.center, @@ -71,7 +71,7 @@ class _CreateSentinelState extends State { ); } - Row _getCreateSentinelBody(BuildContext context) { + Row _getCreateSentinelWidgetBody(BuildContext context) { return Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ @@ -82,7 +82,7 @@ class _CreateSentinelState extends State { context, MaterialPageRoute( builder: (context) => StepperScreen( - stepper: const SentinelsStepperContainer(), + stepper: const SentinelStepperContainer(), onStepperNotificationSeeMorePressed: widget.onStepperNotificationSeeMorePressed, ), diff --git a/lib/widgets/modular_widgets/sentinel_widgets/sentinel_collect.dart b/lib/widgets/modular_widgets/sentinel_widgets/sentinel_collect.dart index 150e80d1..19487666 100644 --- a/lib/widgets/modular_widgets/sentinel_widgets/sentinel_collect.dart +++ b/lib/widgets/modular_widgets/sentinel_widgets/sentinel_collect.dart @@ -120,7 +120,7 @@ class _SentinelCollectState extends State { }, ); } catch (e) { - NotificationUtils.sendNotificationError( + await NotificationUtils.sendNotificationError( e, 'Error while collecting Sentinel rewards', ); diff --git a/lib/widgets/modular_widgets/sentinel_widgets/sentinels_list_widget.dart b/lib/widgets/modular_widgets/sentinel_widgets/sentinel_list_widget.dart similarity index 95% rename from lib/widgets/modular_widgets/sentinel_widgets/sentinels_list_widget.dart rename to lib/widgets/modular_widgets/sentinel_widgets/sentinel_list_widget.dart index 85d19eac..58455840 100644 --- a/lib/widgets/modular_widgets/sentinel_widgets/sentinels_list_widget.dart +++ b/lib/widgets/modular_widgets/sentinel_widgets/sentinel_list_widget.dart @@ -9,14 +9,14 @@ import 'package:zenon_syrius_wallet_flutter/utils/widget_utils.dart'; import 'package:zenon_syrius_wallet_flutter/widgets/widgets.dart'; import 'package:znn_sdk_dart/znn_sdk_dart.dart'; -class SentinelsListWidget extends StatefulWidget { - const SentinelsListWidget({Key? key}) : super(key: key); +class SentinelListWidget extends StatefulWidget { + const SentinelListWidget({Key? key}) : super(key: key); @override - State createState() => _SentinelsListWidgetState(); + State createState() => _SentinelListWidgetState(); } -class _SentinelsListWidgetState extends State { +class _SentinelListWidgetState extends State { late SentinelsListBloc _bloc; final List _sentinels = []; @@ -223,8 +223,8 @@ class _SentinelsListWidgetState extends State { sentinelsModel.refreshResults(); } }, - onError: (error) { - NotificationUtils.sendNotificationError( + onError: (error) async { + await NotificationUtils.sendNotificationError( error, 'Error while disassembling Sentinel', ); diff --git a/lib/widgets/modular_widgets/sentinel_widgets/sentinels_stepper_container.dart b/lib/widgets/modular_widgets/sentinel_widgets/sentinel_stepper_container.dart similarity index 58% rename from lib/widgets/modular_widgets/sentinel_widgets/sentinels_stepper_container.dart rename to lib/widgets/modular_widgets/sentinel_widgets/sentinel_stepper_container.dart index f0a10366..e183e75c 100644 --- a/lib/widgets/modular_widgets/sentinel_widgets/sentinels_stepper_container.dart +++ b/lib/widgets/modular_widgets/sentinel_widgets/sentinel_stepper_container.dart @@ -6,6 +6,7 @@ import 'package:lottie/lottie.dart'; import 'package:stacked/stacked.dart'; import 'package:zenon_syrius_wallet_flutter/blocs/blocs.dart'; import 'package:zenon_syrius_wallet_flutter/main.dart'; +import 'package:zenon_syrius_wallet_flutter/model/model.dart'; import 'package:zenon_syrius_wallet_flutter/utils/app_colors.dart'; import 'package:zenon_syrius_wallet_flutter/utils/constants.dart'; import 'package:zenon_syrius_wallet_flutter/utils/extensions.dart'; @@ -21,48 +22,44 @@ import 'package:zenon_syrius_wallet_flutter/widgets/reusable_widgets/custom_mate import 'package:zenon_syrius_wallet_flutter/widgets/widgets.dart'; import 'package:znn_sdk_dart/znn_sdk_dart.dart'; -enum SentinelsStepperStep { +enum SentinelStepperStep { checkPlasma, qsrManagement, znnManagement, deploySentinel, } -class SentinelsStepperContainer extends StatefulWidget { - const SentinelsStepperContainer({Key? key}) : super(key: key); +class SentinelStepperContainer extends StatefulWidget { + const SentinelStepperContainer({Key? key}) : super(key: key); @override State createState() { - return _MainSentinelsState(); + return _MainSentinelState(); } } -class _MainSentinelsState extends State { - late SentinelsStepperStep _currentStep; - SentinelsStepperStep? _lastCompletedStep; +class _MainSentinelState extends State { + late SentinelStepperStep _currentStep; + SentinelStepperStep? _lastCompletedStep; + + final int _numSteps = SentinelStepperStep.values.length; final TextEditingController _qsrAmountController = TextEditingController(); final TextEditingController _znnAmountController = TextEditingController(); final TextEditingController _addressController = TextEditingController(); + final GlobalKey _qsrFormKey = GlobalKey(); final GlobalKey _depositQsrButtonKey = GlobalKey(); final GlobalKey _withdrawButtonKey = GlobalKey(); final GlobalKey _registerButtonKey = GlobalKey(); - final GlobalKey _qsrFormKey = GlobalKey(); - BigInt _withdrawnQSR = BigInt.zero; BigInt _maxQsrAmount = BigInt.zero; - BigInt _qsrCost = BigInt.zero; - - final int _numSteps = SentinelsStepperStep.values.length; late SentinelsQsrInfoBloc _sentinelsQsrInfoViewModel; @override void initState() { super.initState(); - _qsrCost = sentinelRegisterQsrAmount; - _qsrAmountController.text = _qsrCost.addDecimals(coinDecimals); _znnAmountController.text = sentinelRegisterZnnAmount.addDecimals( coinDecimals, ); @@ -83,7 +80,7 @@ class _MainSentinelsState extends State { if (snapshot.hasData) { return _getWidgetBody( context, - snapshot.data![_addressController.text], + snapshot.data![_addressController.text]!, ); } return const SyriusLoadingWidget(); @@ -93,19 +90,35 @@ class _MainSentinelsState extends State { ); } - Widget _getDepositQsrStep(BuildContext context, AccountInfo? accountInfo) { + Widget _getQsrManagementStep(BuildContext context, AccountInfo accountInfo) { return ViewModelBuilder.reactive( onViewModelReady: (model) { _sentinelsQsrInfoViewModel = model; - model.getQsrDepositedAmount(_addressController.text); + model.getQsrManagementInfo(_addressController.text); + model.stream.listen( + (event) { + if (event != null) { + _maxQsrAmount = MathUtils.bigMin( + accountInfo.getBalance( + kQsrCoin.tokenStandard, + ), + MathUtils.bigMax(BigInt.zero, event.cost - event.deposit), + ); + setState(() { + _qsrAmountController.text = + _maxQsrAmount.addDecimals(coinDecimals); + }); + } + }, + ); }, - builder: (_, model, __) => StreamBuilder( + builder: (_, model, __) => StreamBuilder( stream: model.stream, builder: (_, snapshot) { if (snapshot.hasData) { - return _getDepositQsrStepBody( + return _getQsrManagementStepBody( context, - accountInfo!, + accountInfo, snapshot.data!, ); } else if (snapshot.hasError) { @@ -121,258 +134,239 @@ class _MainSentinelsState extends State { ); } - Widget _getDepositQsrStepBody( + Row _getQsrManagementStepBody( BuildContext context, AccountInfo accountInfo, - BigInt depositedQsr, + SentinelsQsrInfo qsrInfo, ) { - _maxQsrAmount = MathUtils.bigMin( - accountInfo.getBalance(kQsrCoin.tokenStandard), - MathUtils.bigMax( - BigInt.zero, _qsrCost - (depositedQsr - _withdrawnQSR))); - - return Column( + return Row( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Visibility( - visible: depositedQsr >= _qsrCost, - child: Container( - margin: const EdgeInsets.only( - bottom: 30.0, - ), - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.background, - borderRadius: BorderRadius.circular(6.0), - ), - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 30.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Column( - children: [ - Stack( - alignment: Alignment.center, - children: [ - SizedBox( - height: 150.0, - width: 150.0, - child: StandardPieChart( - sections: [ - PieChartSectionData( - showTitle: false, - radius: 7.0, - value: (_qsrCost - .addDecimals(coinDecimals) - .toNum() - - (depositedQsr - _withdrawnQSR) / - _qsrCost), - color: AppColors.qsrColor.withOpacity(0.3), - ), - PieChartSectionData( - showTitle: false, - radius: 7.0, - value: _qsrCost / _qsrCost, - color: AppColors.qsrColor, - ), - ], - ), - ), - Text( - 'Sentinel Slot value\n${_qsrCost.addDecimals(coinDecimals)} ${kQsrCoin.symbol}', - style: Theme.of(context).textTheme.bodyMedium, - textAlign: TextAlign.center, - ), - ], - ), - kVerticalSpacing, - Visibility( - visible: false, - child: Row( - children: [ - Container( - width: 5.0, - height: 5.0, - margin: const EdgeInsets.only(right: 5.0), - decoration: const BoxDecoration( - shape: BoxShape.circle, - color: AppColors.qsrColor, - ), - ), - Text( - 'Deposited ${kQsrCoin.symbol}', - style: Theme.of(context) - .textTheme - .bodyLarge! - .copyWith( - fontSize: 10.0, - ), - ), - Container( - width: 5.0, - height: 5.0, - margin: const EdgeInsets.only( - left: 10.0, - right: 5.0, - ), - decoration: BoxDecoration( - shape: BoxShape.circle, - color: AppColors.qsrColor.withOpacity(0.3), - ), - ), - Text( - 'Remaining ${kQsrCoin.symbol}', - style: Theme.of(context) - .textTheme - .bodyLarge! - .copyWith( - fontSize: 10.0, - ), - ) - ], - ), + Row( + children: [ + Expanded( + child: DisabledAddressField(_addressController), ), ], ), - Column( + const SizedBox( + height: 10.0, + ), + Padding( + padding: const EdgeInsets.only(left: 20.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + AvailableBalance( + kQsrCoin, + accountInfo, + ), + Text( + '${qsrInfo.cost.addDecimals(coinDecimals)} ${kQsrCoin.symbol} required for a Sentinel Node', + style: + Theme.of(context).inputDecorationTheme.hintStyle, + ), + ], + ), + ), + const SizedBox( + height: 10.0, + ), + Row( children: [ - SizedBox( - width: 130.0, - child: Text( - 'You have deposited ${depositedQsr.addDecimals(coinDecimals)} ${kQsrCoin.symbol}', - textAlign: TextAlign.center, - style: Theme.of(context).textTheme.bodyLarge, + Expanded( + child: Form( + key: _qsrFormKey, + autovalidateMode: AutovalidateMode.onUserInteraction, + child: InputField( + inputFormatters: + FormatUtils.getAmountTextInputFormatters( + _qsrAmountController.text, + ), + controller: _qsrAmountController, + validator: (value) => InputValidators.correctValue( + value, + _maxQsrAmount, + kQsrCoin.decimals, + BigInt.zero, + ), + suffixIcon: _getAmountSuffix(accountInfo), + suffixIconConstraints: + const BoxConstraints(maxWidth: 50.0), + hintText: 'Amount', + onChanged: (value) { + setState(() {}); + }, + ), ), ), - kVerticalSpacing, - _getWithdrawQsrButtonViewModel(depositedQsr) ], ), - ], - ), - ), - ), - ), - Visibility( - visible: depositedQsr < _qsrCost, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Expanded( - child: DisabledAddressField(_addressController), + Padding( + padding: const EdgeInsets.symmetric(vertical: 25.0), + child: DottedBorderInfoWidget( + text: + 'You will be able to unlock the ${kQsrCoin.symbol} if you ' + 'choose to disassemble the Sentinel', + borderColor: AppColors.qsrColor, + ), ), ], ), - StepperUtils.getBalanceWidget(kQsrCoin, accountInfo), Row( children: [ - Expanded( - child: Form( - key: _qsrFormKey, - autovalidateMode: AutovalidateMode.onUserInteraction, - child: InputField( - enabled: false, - onChanged: (value) { - setState(() {}); - }, - inputFormatters: - FormatUtils.getAmountTextInputFormatters( - _qsrAmountController.text, - ), - controller: _qsrAmountController, - validator: _qsrAmountValidator, - suffixIcon: _getAmountSuffix(accountInfo), - suffixIconConstraints: const BoxConstraints( - maxWidth: 50.0, - ), - hintText: 'Amount', - ), + Visibility( + visible: qsrInfo.deposit < qsrInfo.cost, + child: _getDepositQsrViewModel(accountInfo, qsrInfo), + ), + Visibility( + visible: qsrInfo.deposit >= qsrInfo.cost, + child: StepperButton( + text: 'Next', + onPressed: _onQsrNextPressed, ), ), ], - ), - Padding( - padding: const EdgeInsets.symmetric(vertical: 25.0), - child: DottedBorderInfoWidget( - text: - 'You will be able to unlock the ${kQsrCoin.symbol} if you ' - 'choose to disassemble the Sentinel', - borderColor: AppColors.qsrColor, - ), - ), + ) ], ), ), - Row( - children: [ - Visibility( - visible: depositedQsr < _qsrCost, - child: _getDepositButtonViewModel(accountInfo, depositedQsr), - ), - Visibility( - visible: depositedQsr >= _qsrCost, - child: StepperButton( - text: 'Next', - onPressed: _onQsrNextPressed, + const SizedBox( + width: 45.0, + ), + Expanded( + child: Visibility( + visible: qsrInfo.deposit > BigInt.zero, + child: Container( + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.background, + borderRadius: BorderRadius.circular(6), + ), + margin: const EdgeInsets.only( + bottom: 30.0, + ), + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 30.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Column( + children: [ + Stack( + alignment: Alignment.center, + children: [ + SizedBox( + width: 150.0, + height: 150.0, + child: AspectRatio( + aspectRatio: 1.0, + child: StandardPieChart( + sections: [ + PieChartSectionData( + showTitle: false, + radius: 7.0, + value: (qsrInfo.cost - qsrInfo.deposit) / + qsrInfo.cost, + color: + AppColors.qsrColor.withOpacity(0.3), + ), + PieChartSectionData( + showTitle: false, + radius: 7.0, + value: qsrInfo.deposit / qsrInfo.cost, + color: AppColors.qsrColor, + ), + ], + ), + ), + ), + Text( + 'Sentinel Slot value\n${qsrInfo.cost.addDecimals(coinDecimals)} ${kQsrCoin.symbol}', + style: Theme.of(context).textTheme.bodyMedium, + textAlign: TextAlign.center, + ), + ], + ), + kVerticalSpacing, + ], + ), + Column( + children: [ + SizedBox( + width: 130.0, + child: Text( + 'You have deposited ${qsrInfo.deposit.addDecimals(coinDecimals)} ' + '${kQsrCoin.symbol}', + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.bodyLarge, + ), + ), + kVerticalSpacing, + _getWithdrawQsrButtonViewModel( + qsrInfo.deposit, + ), + ], + ), + ], + ), ), ), - ], - ) + ), + ), ], ); } - String? _qsrAmountValidator(String? value) => InputValidators.correctValue( - value, - _maxQsrAmount, - kQsrCoin.decimals, - _maxQsrAmount, - canBeEqualToMin: true, - ); - - Widget _getDepositButtonViewModel( - AccountInfo accountInfo, BigInt depositedQsr) { + Widget _getDepositQsrViewModel( + AccountInfo accountInfo, SentinelsQsrInfo qsrInfo) { return ViewModelBuilder.reactive( onViewModelReady: (model) { model.stream.listen( (response) { if (response != null) { _depositQsrButtonKey.currentState?.animateReverse(); - _sentinelsQsrInfoViewModel.getQsrDepositedAmount( + _sentinelsQsrInfoViewModel.getQsrManagementInfo( _addressController.text, ); + setState(() {}); + } else { + setState(() {}); } }, - onError: (error) { + onError: (error) async { _depositQsrButtonKey.currentState?.animateReverse(); - NotificationUtils.sendNotificationError( + await NotificationUtils.sendNotificationError( error, - 'Error while depositing ${kQsrCoin.symbol} for Sentinel', + 'Error while depositing ${kQsrCoin.symbol}', ); + setState(() {}); }, ); }, - builder: (_, model, __) => _getDepositButton( - model, - accountInfo, - depositedQsr, - ), + builder: (_, model, __) => + _getDepositQsrButton(model, accountInfo, qsrInfo), viewModelBuilder: () => SentinelsDepositQsrBloc(), ); } - Widget _getDepositButton( + Widget _getDepositQsrButton( SentinelsDepositQsrBloc model, AccountInfo accountInfo, - BigInt depositedQsr, + SentinelsQsrInfo qsrInfo, ) { return LoadingButton.stepper( key: _depositQsrButtonKey, text: 'Deposit', - onPressed: _qsrAmountValidator(_qsrAmountController.text) == null - ? () => _onDepositButtonPressed(model, depositedQsr) + onPressed: _hasQsrBalance(accountInfo) && + _qsrAmountValidator(_qsrAmountController.text, qsrInfo) == null + ? () => _onDepositButtonPressed(model, qsrInfo) : null, outlineColor: AppColors.qsrColor, ); @@ -385,39 +379,31 @@ class _MainSentinelsState extends State { (event) { if (event != null) { _withdrawButtonKey.currentState?.animateReverse(); - setState(() { - _lastCompletedStep = SentinelsStepperStep.checkPlasma; - }); - _sentinelsQsrInfoViewModel.getQsrDepositedAmount( + _saveProgressAndNavigateToNextStep( + SentinelStepperStep.checkPlasma, + ); + _sentinelsQsrInfoViewModel.getQsrManagementInfo( _addressController.text, ); } }, - onError: (error) { + onError: (error) async { _withdrawButtonKey.currentState?.animateReverse(); - NotificationUtils.sendNotificationError( + await NotificationUtils.sendNotificationError( error, 'Error while withdrawing ${kQsrCoin.symbol}', ); }, ); }, - builder: (_, model, __) => StreamBuilder( - stream: model.stream, - builder: (_, snapshot) { - if (snapshot.hasData) { - _withdrawnQSR = qsrDeposit; - } - return _getWithdrawQsrButton(qsrDeposit, model); - }, - ), + builder: (_, model, __) => _getWithdrawQsrButton(model, qsrDeposit), viewModelBuilder: () => SentinelsWithdrawQsrBloc(), ); } Widget _getWithdrawQsrButton( - BigInt qsrDeposit, SentinelsWithdrawQsrBloc model, + BigInt qsrDeposit, ) { return Visibility( visible: qsrDeposit > BigInt.zero, @@ -430,7 +416,7 @@ class _MainSentinelsState extends State { ); } - Widget _getMaterialStepper(BuildContext context, AccountInfo? accountInfo) { + Widget _getMaterialStepper(BuildContext context, AccountInfo accountInfo) { return Theme( data: Theme.of(context).copyWith( highlightColor: Colors.transparent, @@ -446,27 +432,28 @@ class _MainSentinelsState extends State { stepContent: _getPlasmaCheckFutureBuilder(), stepSubtitle: 'Sufficient Plasma', stepState: StepperUtils.getStepState( - SentinelsStepperStep.checkPlasma.index, + SentinelStepperStep.checkPlasma.index, _lastCompletedStep?.index, ), context: context, ), StepperUtils.getMaterialStep( stepTitle: '${kQsrCoin.symbol} management', - stepContent: _getDepositQsrStep(context, accountInfo), + stepContent: _getQsrManagementStep(context, accountInfo), stepSubtitle: '${kQsrCoin.symbol} deposited', stepState: StepperUtils.getStepState( - SentinelsStepperStep.qsrManagement.index, + SentinelStepperStep.qsrManagement.index, _lastCompletedStep?.index, ), context: context, + expanded: true, ), StepperUtils.getMaterialStep( stepTitle: '${kZnnCoin.symbol} management', stepContent: _getZnnManagementStepBody(context, accountInfo), stepSubtitle: '${kZnnCoin.symbol} locked', stepState: StepperUtils.getStepState( - SentinelsStepperStep.znnManagement.index, + SentinelStepperStep.znnManagement.index, _lastCompletedStep?.index, ), context: context, @@ -476,7 +463,7 @@ class _MainSentinelsState extends State { stepContent: _getDeploySentinelStepBody(context), stepSubtitle: 'Sentinel registered', stepState: StepperUtils.getStepState( - SentinelsStepperStep.deploySentinel.index, + SentinelStepperStep.deploySentinel.index, _lastCompletedStep?.index, ), context: context, @@ -514,24 +501,28 @@ class _MainSentinelsState extends State { if (response != null) { _registerButtonKey.currentState?.animateReverse(); _saveProgressAndNavigateToNextStep( - SentinelsStepperStep.deploySentinel); + SentinelStepperStep.deploySentinel); + setState(() {}); + } else { + setState(() {}); } }, - onError: (error) { + onError: (error) async { _registerButtonKey.currentState?.animateReverse(); - NotificationUtils.sendNotificationError( + await NotificationUtils.sendNotificationError( error, 'Error while deploying the Sentinel Node', ); + setState(() {}); }, ); }, - builder: (_, model, __) => _getDeployButton(model), + builder: (_, model, __) => _getRegisterSentinelButton(model), viewModelBuilder: () => SentinelsDeployBloc(), ); } - Widget _getDeployButton(SentinelsDeployBloc model) { + Widget _getRegisterSentinelButton(SentinelsDeployBloc model) { return LoadingButton.stepper( text: 'Register', onPressed: () => _onDeployPressed(model), @@ -541,7 +532,7 @@ class _MainSentinelsState extends State { Widget _getZnnManagementStepBody( BuildContext context, - AccountInfo? accountInfo, + AccountInfo accountInfo, ) { return Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -555,7 +546,7 @@ class _MainSentinelsState extends State { ], ), kVerticalSpacing, - StepperUtils.getBalanceWidget(kZnnCoin, accountInfo!), + StepperUtils.getBalanceWidget(kZnnCoin, accountInfo), kVerticalSpacing, Row( children: [ @@ -585,45 +576,37 @@ class _MainSentinelsState extends State { } void _onDepositButtonPressed( - SentinelsDepositQsrBloc model, BigInt depositedQsr) { - if (_lastCompletedStep == SentinelsStepperStep.checkPlasma) { - if (depositedQsr >= _qsrCost) { - _depositQsrButtonKey.currentState?.animateForward(); - model.depositQsr( - _qsrAmountController.text.extractDecimals(coinDecimals), - justMarkStepCompleted: true, - ); - } else if (_maxQsrAmount + depositedQsr >= _qsrCost && - _qsrFormKey.currentState!.validate() && - _qsrAmountController.text.extractDecimals(coinDecimals) > - BigInt.zero) { - _depositQsrButtonKey.currentState?.animateForward(); - model.depositQsr( - _qsrAmountController.text.extractDecimals(coinDecimals)); - } - } else if (_lastCompletedStep == SentinelsStepperStep.qsrManagement) { - setState(() { - _currentStep = SentinelsStepperStep.values[_currentStep.index + 1]; - }); + SentinelsDepositQsrBloc model, SentinelsQsrInfo qsrInfo) { + if (qsrInfo.deposit >= qsrInfo.cost) { + _depositQsrButtonKey.currentState?.animateForward(); + model.depositQsr( + _qsrAmountController.text.extractDecimals(coinDecimals), + justMarkStepCompleted: true, + ); + } else if (qsrInfo.deposit + _maxQsrAmount <= qsrInfo.cost && + _qsrFormKey.currentState!.validate() && + _qsrAmountController.text.extractDecimals(coinDecimals) > BigInt.zero) { + _depositQsrButtonKey.currentState?.animateForward(); + model.depositQsr(_qsrAmountController.text.extractDecimals(coinDecimals)); } } void _onNextPressed() { - if (_lastCompletedStep == SentinelsStepperStep.qsrManagement) { - _saveProgressAndNavigateToNextStep(SentinelsStepperStep.znnManagement); + if (_lastCompletedStep == SentinelStepperStep.qsrManagement) { + _saveProgressAndNavigateToNextStep(SentinelStepperStep.znnManagement); } else if (StepperUtils.getStepState( - SentinelsStepperStep.qsrManagement.index, + SentinelStepperStep.qsrManagement.index, _lastCompletedStep?.index, ) == custom_material_stepper.StepState.complete) { setState(() { - _currentStep = SentinelsStepperStep.values[_currentStep.index + 1]; + _currentStep = SentinelStepperStep.values[_currentStep.index + 1]; }); } } void _onDeployPressed(SentinelsDeployBloc model) { - if (_lastCompletedStep == SentinelsStepperStep.znnManagement) { + if (_lastCompletedStep == SentinelStepperStep.znnManagement) { _registerButtonKey.currentState?.animateForward(); model.deploySentinel( _znnAmountController.text.extractDecimals(coinDecimals)); @@ -640,17 +623,17 @@ class _MainSentinelsState extends State { } } - Widget _getWidgetBody(BuildContext context, AccountInfo? accountInfo) { + Widget _getWidgetBody(BuildContext context, AccountInfo accountInfo) { return Stack( children: [ ListView( children: [ _getMaterialStepper(context, accountInfo), Visibility( - visible: - _lastCompletedStep == SentinelsStepperStep.deploySentinel, + visible: _lastCompletedStep == SentinelStepperStep.deploySentinel, child: Column( mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, children: [ Container( padding: const EdgeInsets.symmetric( @@ -733,7 +716,7 @@ class _MainSentinelsState extends State { ], ), Visibility( - visible: _lastCompletedStep == SentinelsStepperStep.deploySentinel, + visible: _lastCompletedStep == SentinelStepperStep.deploySentinel, child: Positioned( right: 50.0, child: SizedBox( @@ -763,31 +746,38 @@ class _MainSentinelsState extends State { ); } - void _iniStepperControllers() { - _currentStep = SentinelsStepperStep.values.first; - } - - void _saveProgressAndNavigateToNextStep(SentinelsStepperStep completedStep) { + void _saveProgressAndNavigateToNextStep(SentinelStepperStep completedStep) { setState(() { _lastCompletedStep = completedStep; if (_lastCompletedStep!.index + 1 < _numSteps) { - _currentStep = SentinelsStepperStep.values[completedStep.index + 1]; + _currentStep = SentinelStepperStep.values[completedStep.index + 1]; } }); } + void _iniStepperControllers() { + _currentStep = SentinelStepperStep.values.first; + } + + bool _hasEnoughZnn(AccountInfo accountInfo) => + accountInfo.znn()! >= sentinelRegisterZnnAmount; + + bool _hasQsrBalance(AccountInfo accountInfo) => + accountInfo.qsr()! > BigInt.zero; + + String? _qsrAmountValidator(String? value, SentinelsQsrInfo qsrInfo) => + InputValidators.correctValue( + value, + _maxQsrAmount, + kQsrCoin.decimals, + BigInt.one, + canBeEqualToMin: true, + ); + void _onQsrNextPressed() { - if (_lastCompletedStep == SentinelsStepperStep.checkPlasma) { - _saveProgressAndNavigateToNextStep(SentinelsStepperStep.qsrManagement); - } else if (StepperUtils.getStepState( - SentinelsStepperStep.qsrManagement.index, - _lastCompletedStep?.index, - ) == - custom_material_stepper.StepState.complete) { - setState(() { - _currentStep = SentinelsStepperStep.values[_currentStep.index + 1]; - }); - } + setState(() { + _saveProgressAndNavigateToNextStep(SentinelStepperStep.qsrManagement); + }); } Widget _getPlasmaCheckFutureBuilder() { @@ -796,25 +786,13 @@ class _MainSentinelsState extends State { builder: (_, snapshot) { if (snapshot.hasError) { return SyriusErrorWidget(snapshot.error!); + } else if (snapshot.hasData) { + return _getPlasmaCheckBody(snapshot.data!); } - switch (snapshot.connectionState) { - case ConnectionState.active: - return const Padding( - padding: EdgeInsets.all(8.0), - child: SyriusLoadingWidget(), - ); - case ConnectionState.done: - return _getPlasmaCheckBody(snapshot.data!); - case ConnectionState.none: - return Container(); - case ConnectionState.waiting: - return const Padding( - padding: EdgeInsets.all(8.0), - child: SyriusLoadingWidget(), - ); - default: - return Container(); - } + return const Padding( + padding: EdgeInsets.all(8.0), + child: SyriusLoadingWidget(), + ); }, ); } @@ -857,24 +835,18 @@ class _MainSentinelsState extends State { void _onPlasmaCheckNextPressed() { if (_lastCompletedStep == null) { - _saveProgressAndNavigateToNextStep(SentinelsStepperStep.checkPlasma); + _saveProgressAndNavigateToNextStep(SentinelStepperStep.checkPlasma); } else if (StepperUtils.getStepState( - SentinelsStepperStep.checkPlasma.index, + SentinelStepperStep.checkPlasma.index, _lastCompletedStep?.index, ) == custom_material_stepper.StepState.complete) { setState(() { - _currentStep = SentinelsStepperStep.values[_currentStep.index + 1]; + _currentStep = SentinelStepperStep.values[_currentStep.index + 1]; }); } } - bool _hasEnoughZnn(AccountInfo accountInfo) => - accountInfo.getBalance( - kZnnCoin.tokenStandard, - ) >= - sentinelRegisterZnnAmount; - @override void dispose() { _qsrAmountController.dispose(); diff --git a/lib/widgets/modular_widgets/sentinel_widgets/sentinel_widgets.dart b/lib/widgets/modular_widgets/sentinel_widgets/sentinel_widgets.dart index d07dd8b4..7f4e7c7a 100644 --- a/lib/widgets/modular_widgets/sentinel_widgets/sentinel_widgets.dart +++ b/lib/widgets/modular_widgets/sentinel_widgets/sentinel_widgets.dart @@ -1,5 +1,5 @@ export 'create_sentinel.dart'; export 'sentinel_collect.dart'; +export 'sentinel_list_widget.dart'; export 'sentinel_rewards.dart'; -export 'sentinels_list_widget.dart'; -export 'sentinels_stepper_container.dart'; +export 'sentinel_stepper_container.dart'; diff --git a/lib/widgets/modular_widgets/settings_widgets/addresses.dart b/lib/widgets/modular_widgets/settings_widgets/addresses.dart index dfbb91ad..eacabb3e 100644 --- a/lib/widgets/modular_widgets/settings_widgets/addresses.dart +++ b/lib/widgets/modular_widgets/settings_widgets/addresses.dart @@ -5,7 +5,6 @@ import 'package:hive/hive.dart'; import 'package:number_selector/number_selector.dart'; import 'package:provider/provider.dart'; import 'package:zenon_syrius_wallet_flutter/blocs/blocs.dart'; -import 'package:zenon_syrius_wallet_flutter/main.dart'; import 'package:zenon_syrius_wallet_flutter/utils/address_utils.dart'; import 'package:zenon_syrius_wallet_flutter/utils/app_colors.dart'; import 'package:zenon_syrius_wallet_flutter/utils/constants.dart'; @@ -71,9 +70,6 @@ class AddressesState extends State { ); widget.accountChainStatsBloc.updateStream(); _selectedAddress = newDefaultAddress; - zenon!.defaultKeyPair = kKeyStore!.getKeyPair( - kDefaultAddressList.indexOf(newDefaultAddress), - ); } catch (e) { rethrow; } diff --git a/lib/widgets/modular_widgets/settings_widgets/backup.dart b/lib/widgets/modular_widgets/settings_widgets/backup.dart index 7104df51..2055e3ab 100644 --- a/lib/widgets/modular_widgets/settings_widgets/backup.dart +++ b/lib/widgets/modular_widgets/settings_widgets/backup.dart @@ -3,6 +3,7 @@ import 'package:zenon_syrius_wallet_flutter/screens/screens.dart'; import 'package:zenon_syrius_wallet_flutter/utils/global.dart'; import 'package:zenon_syrius_wallet_flutter/utils/navigation_utils.dart'; import 'package:zenon_syrius_wallet_flutter/widgets/widgets.dart'; +import 'package:znn_sdk_dart/znn_sdk_dart.dart'; class BackupWidget extends StatefulWidget { const BackupWidget({Key? key}) : super(key: key); @@ -27,42 +28,50 @@ class _BackupWidgetState extends State { return ListView( shrinkWrap: true, children: [ - CustomExpandablePanel('Backup Wallet', _getBackupButton()), + CustomExpandablePanel('Backup Wallet', _getBackupWalletButton()), CustomExpandablePanel('Dump Mnemonic', _getDumpMnemonicButton()), ], ); } - Widget _getBackupButton() { + Widget _getBackupWalletButton() { return Center( child: SettingsButton( - onPressed: _onBackupWalletPressed, + onPressed: (!kWalletFile!.isHardwareWallet) + ? _onBackupWalletPressed + : null, text: 'Backup wallet', ), ); } - void _onBackupWalletPressed() { - NavigationUtils.push( - context, - ExportWalletInfoScreen( - kKeyStore!.mnemonic!, - backupWalletFlow: true, - ), - ); + void _onBackupWalletPressed() async { + kWalletFile! + .access((wallet) => Future.value((wallet as KeyStore).mnemonic!)) + .then((value) => NavigationUtils.push( + context, + ExportWalletInfoScreen( + value, + backupWalletFlow: true, + ), + )); } Widget _getDumpMnemonicButton() { return Center( child: SettingsButton( - onPressed: () { - NavigationUtils.push( - context, - const DumpMnemonicScreen(), - ); - }, + onPressed: (!kWalletFile!.isHardwareWallet) + ? _onDumpMnemonicPressed + : null, text: 'Dump Mnemonic', ), ); } + + void _onDumpMnemonicPressed() { + NavigationUtils.push( + context, + const DumpMnemonicScreen(), + ); + } } diff --git a/lib/widgets/modular_widgets/settings_widgets/display.dart b/lib/widgets/modular_widgets/settings_widgets/display.dart index 2045d3ae..b22dfd10 100644 --- a/lib/widgets/modular_widgets/settings_widgets/display.dart +++ b/lib/widgets/modular_widgets/settings_widgets/display.dart @@ -189,7 +189,7 @@ class _DisplayWidget extends State { kThemeModeKey, currentThemeMode.toString(), ); - sl.get().addNotification( + await sl.get().addNotification( WalletNotification( title: 'Theme mode changed', timestamp: DateTime.now().millisecondsSinceEpoch, @@ -204,7 +204,7 @@ class _DisplayWidget extends State { ), ); } catch (e) { - NotificationUtils.sendNotificationError(e, 'Theme mode change failed'); + await NotificationUtils.sendNotificationError(e, 'Theme mode change failed'); } finally { _confirmThemeButtonKey.currentState!.animateReverse(); } @@ -226,7 +226,7 @@ class _DisplayWidget extends State { ); } - void _onConfirmScaleButtonPressed() { + void _onConfirmScaleButtonPressed() async { try { _confirmScaleButtonKey.currentState!.animateForward(); @@ -240,7 +240,7 @@ class _DisplayWidget extends State { currentTextScaling.toString(), ); - sl.get().addNotification( + await sl.get().addNotification( WalletNotification( title: 'Text scale changed', timestamp: DateTime.now().millisecondsSinceEpoch, @@ -249,7 +249,7 @@ class _DisplayWidget extends State { type: NotificationType.paymentSent), ); } catch (e) { - NotificationUtils.sendNotificationError(e, 'Text scale change failed'); + await NotificationUtils.sendNotificationError(e, 'Text scale change failed'); } finally { _confirmScaleButtonKey.currentState!.animateReverse(); } diff --git a/lib/widgets/modular_widgets/settings_widgets/node_management.dart b/lib/widgets/modular_widgets/settings_widgets/node_management.dart index e57451e6..489f3684 100644 --- a/lib/widgets/modular_widgets/settings_widgets/node_management.dart +++ b/lib/widgets/modular_widgets/settings_widgets/node_management.dart @@ -9,7 +9,7 @@ import 'package:zenon_syrius_wallet_flutter/blocs/blocs.dart'; import 'package:zenon_syrius_wallet_flutter/embedded_node/embedded_node.dart'; import 'package:zenon_syrius_wallet_flutter/main.dart'; import 'package:zenon_syrius_wallet_flutter/model/model.dart'; -import 'package:zenon_syrius_wallet_flutter/services/wallet_connect_service.dart'; +import 'package:zenon_syrius_wallet_flutter/services/i_web3wallet_service.dart'; import 'package:zenon_syrius_wallet_flutter/utils/utils.dart'; import 'package:zenon_syrius_wallet_flutter/widgets/widgets.dart'; import 'package:znn_sdk_dart/znn_sdk_dart.dart'; @@ -44,6 +44,12 @@ class _NodeManagementState extends State { int get _newChainId => int.parse(_newChainIdController.text); + @override + void initState() { + super.initState(); + kDefaultCommunityNodes.shuffle(); + } + @override void didChangeDependencies() { super.didChangeDependencies(); @@ -65,7 +71,7 @@ class _NodeManagementState extends State { title: 'Node Management', description: 'This card allows one to set the ZNN Node used to connect to. ' - 'By default the wallet is connected to the embedded node. ' + 'By default the wallet is connected to the Embedded Node. ' 'If you are running a local ZNN Node, please use the localhost option', childBuilder: () => _getWidgetBody(), ); @@ -121,48 +127,31 @@ class _NodeManagementState extends State { try { _confirmNodeButtonKey.currentState?.animateForward(); - String url = _selectedNode == 'Embedded Node' - ? kLocalhostDefaultNodeUrl - : _selectedNode!; - bool isConnectionEstablished = - await NodeUtils.establishConnectionToNode(url); - if (_selectedNode == 'Embedded Node') { - // Check if node is already running - if (!isConnectionEstablished) { - // Initialize local full node - await Isolate.spawn(EmbeddedNode.runNode, [''], - onExit: sl(instanceName: 'embeddedStoppedPort') - .sendPort); - kEmbeddedNodeRunning = true; - // The node needs a couple of seconds to actually start - await Future.delayed(kEmbeddedConnectionDelay); - isConnectionEstablished = - await NodeUtils.establishConnectionToNode(url); - } - } else { - isConnectionEstablished = - await NodeUtils.establishConnectionToNode(url); - if (isConnectionEstablished) { - await NodeUtils.closeEmbeddedNode(); - } - } + var isConnectionEstablished = + await _establishConnectionToNode(_selectedNode); if (isConnectionEstablished) { kNodeChainId = await NodeUtils.getNodeChainIdentifier(); - await htlcSwapsService!.storeLastCheckedHtlcBlockHeight(0); if (await _checkForChainIdMismatch()) { + await htlcSwapsService!.storeLastCheckedHtlcBlockHeight(0); await sharedPrefsService!.put( kSelectedNodeKey, _selectedNode, ); kCurrentNode = _selectedNode!; - _sendChangingNodeSuccessNotification(); + await _sendChangingNodeSuccessNotification(); widget.onNodeChangedCallback(); + } else { + await _establishConnectionToNode(kCurrentNode); + kNodeChainId = await NodeUtils.getNodeChainIdentifier(); + setState(() { + _selectedNode = kCurrentNode!; + }); } } else { throw 'Connection could not be established to $_selectedNode'; } } catch (e) { - NotificationUtils.sendNotificationError( + await NotificationUtils.sendNotificationError( e, 'Connection failed', ); @@ -174,6 +163,33 @@ class _NodeManagementState extends State { } } + Future _establishConnectionToNode(String? url) async { + String targetUrl = url == kEmbeddedNode ? kLocalhostDefaultNodeUrl : url!; + bool isConnectionEstablished = + await NodeUtils.establishConnectionToNode(targetUrl); + if (url == kEmbeddedNode) { + // Check if node is already running + if (!isConnectionEstablished) { + // Initialize local full node + await Isolate.spawn(EmbeddedNode.runNode, [''], + onExit: + sl(instanceName: 'embeddedStoppedPort').sendPort); + kEmbeddedNodeRunning = true; + // The node needs a couple of seconds to actually start + await Future.delayed(kEmbeddedConnectionDelay); + isConnectionEstablished = + await NodeUtils.establishConnectionToNode(targetUrl); + } + } else { + isConnectionEstablished = + await NodeUtils.establishConnectionToNode(targetUrl); + if (isConnectionEstablished) { + await NodeUtils.closeEmbeddedNode(); + } + } + return isConnectionEstablished; + } + Widget _getAddNodeExpandableChild() { return Column( children: [ @@ -209,10 +225,12 @@ class _NodeManagementState extends State { bool _ifUserInputValid() => InputValidators.node(_newNodeController.text) == null; - void _onAddNodePressed() async { - if ([...kDbNodes, ...kDefaultNodes].contains(_newNodeController.text)) { - NotificationUtils.sendNotificationError( - 'Node already exists', 'Node already exists'); + Future _onAddNodePressed() async { + if ([...kDbNodes, ...kDefaultCommunityNodes, ...kDefaultNodes] + .contains(_newNodeController.text)) { + await NotificationUtils.sendNotificationError( + 'Node ${_newNodeController.text} already exists', + 'Node already exists'); } else { _addNodeToDb(); } @@ -226,11 +244,11 @@ class _NodeManagementState extends State { } Hive.box(kNodesBox).add(_newNodeController.text); await NodeUtils.loadDbNodes(); - _sendAddNodeSuccessNotification(); + await _sendAddNodeSuccessNotification(); _newNodeController = TextEditingController(); _newNodeKey = GlobalKey(); } catch (e) { - NotificationUtils.sendNotificationError(e, 'Error while adding new node'); + await NotificationUtils.sendNotificationError(e, 'Error while adding new node'); } finally { _addNodeButtonKey.currentState?.animateReverse(); } @@ -238,8 +256,11 @@ class _NodeManagementState extends State { Widget _getNodeTiles() { return Column( - children: - [...kDefaultNodes, ...kDbNodes].map((e) => _getNodeTile(e)).toList(), + children: { + ...kDefaultNodes, + ...kDefaultCommunityNodes, + ...kDbNodes + }.toList().map((e) => _getNodeTile(e)).toList(), ); } @@ -274,8 +295,8 @@ class _NodeManagementState extends State { ); } - void _sendChangingNodeSuccessNotification() { - sl.get().addNotification( + Future _sendChangingNodeSuccessNotification() async { + await sl.get().addNotification( WalletNotification( title: 'Successfully connected to $_selectedNode', timestamp: DateTime.now().millisecondsSinceEpoch, @@ -292,8 +313,8 @@ class _NodeManagementState extends State { super.dispose(); } - void _sendAddNodeSuccessNotification() { - sl.get().addNotification( + Future _sendAddNodeSuccessNotification() async { + await sl.get().addNotification( WalletNotification( title: 'Successfully added node ${_newNodeController.text}', timestamp: DateTime.now().millisecondsSinceEpoch, @@ -370,13 +391,13 @@ class _NodeManagementState extends State { _confirmChainIdButtonKey.currentState?.animateForward(); setChainIdentifier(chainIdentifier: _newChainId); await sharedPrefsService!.put(kChainIdKey, _newChainId); - sl().emitChainIdChangeEvent(_newChainId.toString()); - _sendSuccessfullyChangedChainIdNotification(_newChainId); + await sl().emitChainIdChangeEvent(_newChainId.toString()); + await _sendSuccessfullyChangedChainIdNotification(_newChainId); _initCurrentChainId(); _newChainIdController = TextEditingController(); _newChainIdKey = GlobalKey(); } catch (e) { - NotificationUtils.sendNotificationError( + await NotificationUtils.sendNotificationError( e, 'Error while setting the new client chain identifier', ); @@ -385,8 +406,8 @@ class _NodeManagementState extends State { } } - void _sendSuccessfullyChangedChainIdNotification(int newChainId) { - sl.get().addNotification( + Future _sendSuccessfullyChangedChainIdNotification(int newChainId) async { + await sl.get().addNotification( WalletNotification( title: 'Successfully changed client chain identifier to $newChainId', diff --git a/lib/widgets/modular_widgets/settings_widgets/security.dart b/lib/widgets/modular_widgets/settings_widgets/security.dart index 50071648..aee0b33d 100644 --- a/lib/widgets/modular_widgets/settings_widgets/security.dart +++ b/lib/widgets/modular_widgets/settings_widgets/security.dart @@ -84,24 +84,7 @@ class _SecurityWidgetState extends State { return CardScaffold( title: 'Security', description: 'Change the security parameters of the wallet', - childBuilder: () => _getFutureBuilder(context), - ); - } - - Widget _getFutureBuilder(BuildContext context) { - return FutureBuilder>( - future: zenon!.defaultKeyPair!.getPublicKey(), - builder: (_, snapshot) { - if (snapshot.hasError) { - return SyriusErrorWidget(snapshot.error.toString()); - } else if (snapshot.hasData) { - _publicKeyController.text = BytesUtils.bytesToHex(snapshot.data!); - _publicKeySignFileController.text = - BytesUtils.bytesToHex(snapshot.data!); - return _getWidgetBody(context); - } - return const SyriusLoadingWidget(); - }, + childBuilder: () => _getWidgetBody(context), ); } @@ -206,7 +189,7 @@ class _SecurityWidgetState extends State { }, ); } catch (e) { - NotificationUtils.sendNotificationError( + await NotificationUtils.sendNotificationError( e, 'Error while confirming auto-lock interval', ); @@ -223,7 +206,7 @@ class _SecurityWidgetState extends State { ); } - void _onConfirmAutoEraseButtonPressed() async { + Future _onConfirmAutoEraseButtonPressed() async { try { _autoEraseButtonKey.currentState?.animateForward(); await sharedPrefsService! @@ -232,9 +215,9 @@ class _SecurityWidgetState extends State { _autoEraseWalletLimit, ) .then( - (value) { + (value) async { kAutoEraseWalletLimit = _autoEraseWalletLimit; - sl.get().addNotification( + await sl.get().addNotification( WalletNotification( title: 'Auto-erase attempts limit successfully changed', details: 'The auto-erase limit has now ' @@ -247,7 +230,7 @@ class _SecurityWidgetState extends State { }, ); } catch (e) { - NotificationUtils.sendNotificationError( + await NotificationUtils.sendNotificationError( e, 'Error while confirming auto-erase limit', ); @@ -330,14 +313,15 @@ class _SecurityWidgetState extends State { Future _onSignButtonPressed() async { try { _signButtonKey.currentState?.animateForward(); - final signedMessage = await walletSign( + final signature = await walletSign( _textToBeSignedController.text.codeUnits, ); setState(() { - _signedTextController.text = signedMessage; + _signedTextController.text = signature.signature; + _publicKeyController.text = signature.publicKey; }); } catch (e) { - NotificationUtils.sendNotificationError(e, 'Error while signing message'); + await NotificationUtils.sendNotificationError(e, 'Error while signing message'); } finally { _signButtonKey.currentState?.animateReverse(); } @@ -462,7 +446,7 @@ class _SecurityWidgetState extends State { FormatUtils.decodeHexString(_publicKeyToBeFilledController.text), ); if (verified) { - sl.get().addNotification( + await sl.get().addNotification( WalletNotification( title: 'Message verified successfully', timestamp: DateTime.now().millisecondsSinceEpoch, @@ -481,7 +465,7 @@ class _SecurityWidgetState extends State { throw 'Message or signature invalid'; } } catch (e) { - NotificationUtils.sendNotificationError( + await NotificationUtils.sendNotificationError( e, 'Error while verifying message'); } finally { _verifyButtonKey.currentState?.animateReverse(); @@ -572,12 +556,13 @@ class _SecurityWidgetState extends State { await droppedFile.readAsBytes(), )); setState(() { - _fileHashController.text = fileSignature; + _fileHashController.text = fileSignature.signature; + _publicKeySignFileController.text = fileSignature.publicKey; _toBeSignedFilePath = null; _signSelectFileWidgetKey.currentState!.resetMessageToUser(); }); } catch (e) { - NotificationUtils.sendNotificationError(e, 'Error while signing message'); + await NotificationUtils.sendNotificationError(e, 'Error while signing message'); } finally { _signFileButtonKey.currentState?.animateReverse(); } @@ -682,7 +667,7 @@ class _SecurityWidgetState extends State { ); } - void _onVerifyFileButtonPressed() async { + Future _onVerifyFileButtonPressed() async { try { _verifyFileButtonKey.currentState?.animateForward(); bool verified = await Crypto.verify( @@ -693,7 +678,7 @@ class _SecurityWidgetState extends State { FormatUtils.decodeHexString(_publicKeyVerifyFileController.text), ); if (verified) { - sl.get().addNotification( + await sl.get().addNotification( WalletNotification( title: 'File hash verified successfully', timestamp: DateTime.now().millisecondsSinceEpoch, @@ -713,7 +698,7 @@ class _SecurityWidgetState extends State { throw 'Hash or public key invalid'; } } catch (e) { - NotificationUtils.sendNotificationError( + await NotificationUtils.sendNotificationError( e, 'Error while verifying file hash:'); } finally { _verifyFileButtonKey.currentState?.animateReverse(); diff --git a/lib/widgets/modular_widgets/settings_widgets/wallet_options.dart b/lib/widgets/modular_widgets/settings_widgets/wallet_options.dart index 86284f9d..296b6f1d 100644 --- a/lib/widgets/modular_widgets/settings_widgets/wallet_options.dart +++ b/lib/widgets/modular_widgets/settings_widgets/wallet_options.dart @@ -2,21 +2,17 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:launch_at_startup/launch_at_startup.dart'; +import 'package:logging/logging.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:zenon_syrius_wallet_flutter/blocs/blocs.dart'; import 'package:zenon_syrius_wallet_flutter/main.dart'; import 'package:zenon_syrius_wallet_flutter/model/model.dart'; import 'package:zenon_syrius_wallet_flutter/screens/screens.dart'; -import 'package:zenon_syrius_wallet_flutter/utils/clipboard_utils.dart'; -import 'package:zenon_syrius_wallet_flutter/utils/constants.dart'; -import 'package:zenon_syrius_wallet_flutter/utils/navigation_utils.dart'; -import 'package:zenon_syrius_wallet_flutter/utils/notification_utils.dart'; +import 'package:zenon_syrius_wallet_flutter/utils/utils.dart'; import 'package:zenon_syrius_wallet_flutter/widgets/widgets.dart'; class WalletOptions extends StatefulWidget { - final VoidCallback onResyncWalletPressed; - - const WalletOptions(this.onResyncWalletPressed, {Key? key}) : super(key: key); + const WalletOptions({Key? key}) : super(key: key); @override State createState() => _WalletOptionsState(); @@ -26,6 +22,7 @@ class _WalletOptionsState extends State { bool? _launchAtStartup; bool? _enableDesktopNotifications; bool? _enabledClipboardWatcher; + bool? _autoReceive; @override void initState() { @@ -42,6 +39,10 @@ class _WalletOptionsState extends State { kEnableClipboardWatcherKey, defaultValue: kEnableClipboardWatcherDefaultValue, ); + _autoReceive = sharedPrefsService!.get( + kAutoReceiveKey, + defaultValue: kAutoReceiveDefaultValue, + ); } @override @@ -119,6 +120,7 @@ class _WalletOptionsState extends State { _getLaunchAtStartupWidget(), _getEnableDesktopNotifications(), _buildEnableClipboardWatcher(), + _getAutoReceiveWidget() ], ); } @@ -131,11 +133,11 @@ class _WalletOptionsState extends State { style: Theme.of(context).textTheme.bodyMedium, ), SyriusCheckbox( - onChanged: (value) { + onChanged: (value) async { setState(() { _launchAtStartup = value; - _changeLaunchAtStartupStatus(value ?? false); }); + await _changeLaunchAtStartupStatus(value ?? false); }, value: _launchAtStartup, context: context, @@ -144,6 +146,42 @@ class _WalletOptionsState extends State { ); } + Widget _getAutoReceiveWidget() { + return Row( + children: [ + Text( + 'Auto-receiver', + style: Theme.of(context).textTheme.bodyMedium, + ), + SyriusCheckbox( + onChanged: (value) async { + if (value == true) { + NodeUtils.getUnreceivedTransactions().then((value) { + sl().autoReceive(); + }).onError((error, stackTrace) { + Logger('MainAppContainer').log( + Level.WARNING, '_getAutoReceiveWidget', error, stackTrace); + }); + } else if (value == false && + sl().pool.isNotEmpty) { + sl().pool.clear(); + } + setState(() { + _autoReceive = value; + }); + await _changeAutoReceiveStatus(value ?? false); + }, + value: _autoReceive, + context: context, + ), + const StandardTooltipIcon( + 'Uncheck to disable the auto-receiver and receive transactions manually', + Icons.help, + ), + ], + ); + } + Future _setupLaunchAtStartup() async { PackageInfo packageInfo = await PackageInfo.fromPlatform(); launchAtStartup.setup( @@ -152,6 +190,37 @@ class _WalletOptionsState extends State { ); } + Future _changeAutoReceiveStatus(bool enabled) async { + try { + await _saveAutoReceiveValueToCache(enabled); + await _sendAutoReceiveNotification(enabled); + } on Exception catch (e) { + await NotificationUtils.sendNotificationError( + e, + 'Something went wrong while setting automatic receive preference', + ); + } + } + + Future _saveAutoReceiveValueToCache(bool enabled) async { + await sharedPrefsService!.put( + kAutoReceiveKey, + enabled, + ); + } + + Future _sendAutoReceiveNotification(bool enabled) async { + await sl.get().addNotification( + WalletNotification( + title: 'Auto-receiver ${enabled ? 'enabled' : 'disabled'}', + details: + 'Auto-receiver preference was ${enabled ? 'enabled' : 'disabled'}', + timestamp: DateTime.now().millisecondsSinceEpoch, + type: NotificationType.paymentSent, + ), + ); + } + Future _changeLaunchAtStartupStatus(bool enabled) async { try { await _setupLaunchAtStartup(); @@ -161,9 +230,9 @@ class _WalletOptionsState extends State { await launchAtStartup.disable(); } await _saveLaunchAtStartupValueToCache(enabled); - _sendLaunchAtStartupStatusNotification(enabled); + await _sendLaunchAtStartupStatusNotification(enabled); } on Exception catch (e) { - NotificationUtils.sendNotificationError( + await NotificationUtils.sendNotificationError( e, 'Something went wrong while setting launch at startup preference', ); @@ -177,8 +246,8 @@ class _WalletOptionsState extends State { ); } - void _sendLaunchAtStartupStatusNotification(bool enabled) { - sl.get().addNotification( + Future _sendLaunchAtStartupStatusNotification(bool enabled) async { + await sl.get().addNotification( WalletNotification( title: 'Launch startup ${enabled ? 'enabled' : 'disabled'}', details: @@ -239,9 +308,9 @@ class _WalletOptionsState extends State { Future _changeEnableDesktopNotificationsStatus(bool enabled) async { try { await sharedPrefsService!.put(kEnableDesktopNotificationsKey, enabled); - _sendEnabledDesktopNotificationsStatusNotification(enabled); + await _sendEnabledDesktopNotificationsStatusNotification(enabled); } on Exception catch (e) { - NotificationUtils.sendNotificationError( + await NotificationUtils.sendNotificationError( e, 'Something went wrong while setting desktop notifications preference', ); @@ -252,17 +321,18 @@ class _WalletOptionsState extends State { try { await sharedPrefsService!.put(kEnableClipboardWatcherKey, enabled); ClipboardUtils.toggleClipboardWatcherStatus(); - _sendEnableClipboardWatcherStatusNotification(enabled); + await _sendEnableClipboardWatcherStatusNotification(enabled); } on Exception catch (e) { - NotificationUtils.sendNotificationError( + await NotificationUtils.sendNotificationError( e, 'Something went wrong while changing clipboard watcher preference', ); } } - void _sendEnabledDesktopNotificationsStatusNotification(bool enabled) { - sl.get().addNotification( + Future _sendEnabledDesktopNotificationsStatusNotification( + bool enabled) async { + await sl.get().addNotification( WalletNotification( title: 'Desktop notifications ${enabled ? 'enabled' : 'disabled'}', details: @@ -273,8 +343,9 @@ class _WalletOptionsState extends State { ); } - void _sendEnableClipboardWatcherStatusNotification(bool enabled) { - sl.get().addNotification( + Future _sendEnableClipboardWatcherStatusNotification( + bool enabled) async { + await sl.get().addNotification( WalletNotification( title: 'Clipboard watcher ${enabled ? 'enabled' : 'disabled'}', details: diff --git a/lib/widgets/modular_widgets/staking_widgets/stake_collect.dart b/lib/widgets/modular_widgets/staking_widgets/stake_collect.dart index b77c895b..9c2ce185 100644 --- a/lib/widgets/modular_widgets/staking_widgets/stake_collect.dart +++ b/lib/widgets/modular_widgets/staking_widgets/stake_collect.dart @@ -105,7 +105,7 @@ class _StakeCollectState extends State { }, ); } catch (e) { - NotificationUtils.sendNotificationError( + await NotificationUtils.sendNotificationError( e, 'Error while collecting staking rewards', ); diff --git a/lib/widgets/modular_widgets/staking_widgets/staking_list/staking_list.dart b/lib/widgets/modular_widgets/staking_widgets/staking_list/staking_list.dart index 7cb4af25..acb29063 100644 --- a/lib/widgets/modular_widgets/staking_widgets/staking_list/staking_list.dart +++ b/lib/widgets/modular_widgets/staking_widgets/staking_list/staking_list.dart @@ -135,9 +135,9 @@ class _StakingListState extends State { bloc.refreshResults(); } }, - onError: (error) { + onError: (error) async { cancelButtonKey.currentState?.animateReverse(); - NotificationUtils.sendNotificationError( + await NotificationUtils.sendNotificationError( error, 'Error while cancelling stake', ); diff --git a/lib/widgets/modular_widgets/staking_widgets/staking_options/staking_options.dart b/lib/widgets/modular_widgets/staking_widgets/staking_options/staking_options.dart index 3b052030..69d5f896 100644 --- a/lib/widgets/modular_widgets/staking_widgets/staking_options/staking_options.dart +++ b/lib/widgets/modular_widgets/staking_widgets/staking_options/staking_options.dart @@ -164,9 +164,9 @@ class _StakingOptionsState extends State { }); } }, - onError: (error) { + onError: (error) async { _stakeButtonKey.currentState?.animateReverse(); - NotificationUtils.sendNotificationError( + await NotificationUtils.sendNotificationError( error, 'Error while generating stake', ); diff --git a/lib/widgets/modular_widgets/token_widgets/token_card.dart b/lib/widgets/modular_widgets/token_widgets/token_card.dart index 04f8dd3e..a3c7b7b3 100644 --- a/lib/widgets/modular_widgets/token_widgets/token_card.dart +++ b/lib/widgets/modular_widgets/token_widgets/token_card.dart @@ -1,7 +1,7 @@ import 'package:fl_chart/fl_chart.dart'; -import 'package:flip_card/flip_card.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:flutter_flip_card/flutter_flip_card.dart'; import 'package:marquee_widget/marquee_widget.dart'; import 'package:stacked/stacked.dart'; import 'package:zenon_syrius_wallet_flutter/blocs/blocs.dart'; @@ -46,6 +46,7 @@ class _TokenCardState extends State { final GlobalKey _mintAmountKey = GlobalKey(); GlobalKey _newOwnerAddressKey = GlobalKey(); + final FlipCardController _flipCardController = FlipCardController(); final TextEditingController _beneficiaryAddressController = TextEditingController(); final TextEditingController _burnAmountController = TextEditingController(); @@ -71,31 +72,47 @@ class _TokenCardState extends State { @override Widget build(BuildContext context) { return FlipCard( - direction: FlipDirection.HORIZONTAL, - speed: 500, - flipOnTouch: false, key: _cardKey, - front: _getFrontOfCard(), - back: _getBackOfCard(), + rotateSide: RotateSide.right, + animationDuration: const Duration(milliseconds: 500), + axis: FlipAxis.vertical, + disableSplashEffect: false, + onTapFlipping: false, + controller: _flipCardController, + frontWidget: _getFrontOfCard(), + backWidget: _getBackOfCard(), ); } + Future _flipCard() async { + await _flipCardController.flipcard(); + } + Widget _getBackOfCard() { - return StreamBuilder?>( - stream: sl.get().stream, - builder: (_, snapshot) { - if (snapshot.hasError) { - return SyriusErrorWidget(snapshot.error!); - } - if (snapshot.connectionState == ConnectionState.active) { - if (snapshot.hasData) { - return _getBackVersionOfCard(snapshot.data!); - } - return const SyriusLoadingWidget(); - } - return const SyriusLoadingWidget(); - }, - ); + return Container( + padding: const EdgeInsets.symmetric( + horizontal: 20.0, + vertical: 10.0, + ), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10.0), + color: Theme.of(context).colorScheme.primaryContainer, + ), + child: StreamBuilder?>( + stream: sl.get().stream, + builder: (_, snapshot) { + if (snapshot.hasError) { + return SyriusErrorWidget(snapshot.error!); + } + if (snapshot.connectionState == ConnectionState.active) { + if (snapshot.hasData) { + return _getBackVersionOfCard(snapshot.data!); + } + return const SyriusLoadingWidget(); + } + return const SyriusLoadingWidget(); + }, + )); } Container _getFrontOfCard() { @@ -188,7 +205,7 @@ class _TokenCardState extends State { onPressed: kDefaultAddressList .contains(widget.token.owner.toString()) ? () { - _cardKey.currentState!.toggleCard(); + _flipCard(); _backOfCardVersion = TokenCardBackVersion.mint; sl @@ -207,7 +224,7 @@ class _TokenCardState extends State { widget.token.owner.toString(), ) ? () { - _cardKey.currentState!.toggleCard(); + _flipCard(); _backOfCardVersion = TokenCardBackVersion.burn; sl @@ -412,7 +429,7 @@ class _TokenCardState extends State { StepperButton( text: 'Go back', onPressed: () { - _cardKey.currentState!.toggleCard(); + _flipCard(); }, ), ], @@ -436,9 +453,9 @@ class _TokenCardState extends State { _sendBurnSuccessfulNotification(event); sl.get().getBalanceForAllAddresses(); }, - onError: (error) { + onError: (error) async { _burnButtonKey.currentState?.animateReverse(); - NotificationUtils.sendNotificationError( + await NotificationUtils.sendNotificationError( error, 'Error while trying to burn ZTS', ); @@ -450,8 +467,9 @@ class _TokenCardState extends State { ); } - void _sendBurnSuccessfulNotification(AccountBlockTemplate event) { - sl.get().addNotification( + Future _sendBurnSuccessfulNotification( + AccountBlockTemplate event) async { + await sl.get().addNotification( WalletNotification( title: 'Successfully burned ${event.amount.addDecimals( widget.token.decimals, @@ -571,7 +589,7 @@ class _TokenCardState extends State { StepperButton( text: 'Go back', onPressed: () { - _cardKey.currentState!.toggleCard(); + _flipCard(); }, ), ], @@ -588,15 +606,14 @@ class _TokenCardState extends State { model.stream.listen((event) { setState(() { _beneficiaryAddressKey.currentState!.reset(); - _beneficiaryAddressController.clear(); _mintAmountKey.currentState!.reset(); _mintAmountController.clear(); }); _mintButtonKey.currentState!.animateReverse(); _sendMintSuccessfulNotification(event); sl.get().getBalanceForAllAddresses(); - }, onError: (error) { - NotificationUtils.sendNotificationError( + }, onError: (error) async { + await NotificationUtils.sendNotificationError( error, 'Error while trying to mint ${widget.token.symbol}}', ); @@ -608,8 +625,9 @@ class _TokenCardState extends State { ); } - void _sendMintSuccessfulNotification(AccountBlockTemplate event) { - sl.get().addNotification( + Future _sendMintSuccessfulNotification( + AccountBlockTemplate event) async { + await sl.get().addNotification( WalletNotification( title: 'Successfully minted ${event.amount.addDecimals( widget.token.decimals, @@ -627,21 +645,24 @@ class _TokenCardState extends State { Widget _getMintButton(MintTokenBloc model) { return LoadingButton.stepper( text: 'Mint', - onPressed: _mintMaxAmount > BigInt.zero && - _mintAmountController.text.isNotEmpty && - InputValidators.correctValue(_mintAmountController.text, - _mintMaxAmount, widget.token.decimals, BigInt.zero) == - null - ? () { - _mintButtonKey.currentState!.animateForward(); - model.mintToken( - widget.token, - _mintAmountController.text - .extractDecimals(widget.token.decimals), - Address.parse(_beneficiaryAddressController.text), - ); - } - : null, + onPressed: + InputValidators.checkAddress(_beneficiaryAddressController.text) == + null && + _mintMaxAmount > BigInt.zero && + _mintAmountController.text.isNotEmpty && + InputValidators.correctValue(_mintAmountController.text, + _mintMaxAmount, widget.token.decimals, BigInt.zero) == + null + ? () { + _mintButtonKey.currentState!.animateForward(); + model.mintToken( + widget.token, + _mintAmountController.text + .extractDecimals(widget.token.decimals), + Address.parse(_beneficiaryAddressController.text), + ); + } + : null, key: _mintButtonKey, ); } @@ -649,7 +670,7 @@ class _TokenCardState extends State { void _onTransferOwnershipIconPressed() { setState(() { _backOfCardVersion = TokenCardBackVersion.transferOwnership; - _cardKey.currentState!.toggleCard(); + _flipCard(); }); } @@ -687,7 +708,7 @@ class _TokenCardState extends State { StepperButton( text: 'Go back', onPressed: () { - _cardKey.currentState!.toggleCard(); + _flipCard(); }, ), ], @@ -721,9 +742,9 @@ class _TokenCardState extends State { }); } _transferButtonKey.currentState?.animateReverse(); - }, onError: (error) { + }, onError: (error) async { _transferButtonKey.currentState?.animateReverse(); - NotificationUtils.sendNotificationError( + await NotificationUtils.sendNotificationError( error, 'Error while trying to transfer token ownership', ); @@ -748,8 +769,8 @@ class _TokenCardState extends State { ); } - void _sendTransferSuccessfulNotification() { - sl.get().addNotification( + Future _sendTransferSuccessfulNotification() async { + await sl.get().addNotification( WalletNotification( title: 'Successfully transferred ownership of ' '${widget.token.name} token', diff --git a/lib/widgets/modular_widgets/token_widgets/token_favorite.dart b/lib/widgets/modular_widgets/token_widgets/token_favorite.dart index 8e990094..85c238d2 100644 --- a/lib/widgets/modular_widgets/token_widgets/token_favorite.dart +++ b/lib/widgets/modular_widgets/token_widgets/token_favorite.dart @@ -63,8 +63,8 @@ class _TokenFavoriteState extends State { _showLoading = true; }); _favoriteTokensBox.add(widget.token.tokenStandard.toString()).then( - (value) { - sl.get().addNotification( + (value) async { + await sl.get().addNotification( WalletNotification( title: '${widget.token.name} token has been added to favorites', details: 'Token ${widget.token.name} with symbol ' @@ -79,8 +79,8 @@ class _TokenFavoriteState extends State { }); widget._tokenFavoritesCallback(); }, - onError: (error) { - NotificationUtils.sendNotificationError( + onError: (error) async { + await NotificationUtils.sendNotificationError( error, 'Error adding ${widget.token.name} token to favorites', ); @@ -99,8 +99,8 @@ class _TokenFavoriteState extends State { ), ) .then( - (value) { - sl.get().addNotification( + (value) async { + await sl.get().addNotification( WalletNotification( title: '${widget.token.name} token has been removed ' 'from favorites', @@ -116,8 +116,8 @@ class _TokenFavoriteState extends State { }); widget._tokenFavoritesCallback(); }, - onError: (error) { - NotificationUtils.sendNotificationError( + onError: (error) async { + await NotificationUtils.sendNotificationError( error, 'Error removing ${widget.token.name} token from favorites', ); diff --git a/lib/widgets/modular_widgets/token_widgets/token_stepper.dart b/lib/widgets/modular_widgets/token_widgets/token_stepper.dart index 5a1ad753..83d03515 100644 --- a/lib/widgets/modular_widgets/token_widgets/token_stepper.dart +++ b/lib/widgets/modular_widgets/token_widgets/token_stepper.dart @@ -718,9 +718,9 @@ class _TokenStepperState extends State { _createButtonKey.currentState?.animateReverse(); _saveProgressAndNavigateToNextStep(TokenStepperStep.issueToken); }, - onError: (error) { + onError: (error) async { _createButtonKey.currentState?.animateReverse(); - NotificationUtils.sendNotificationError( + await NotificationUtils.sendNotificationError( error, 'Error while creating a new ZTS token', ); diff --git a/lib/widgets/modular_widgets/transfer_widgets/latest_transactions/latest_transactions.dart b/lib/widgets/modular_widgets/transfer_widgets/latest_transactions/latest_transactions.dart new file mode 100644 index 00000000..ab690491 --- /dev/null +++ b/lib/widgets/modular_widgets/transfer_widgets/latest_transactions/latest_transactions.dart @@ -0,0 +1,380 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_vector_icons/flutter_vector_icons.dart'; +import 'package:marquee_widget/marquee_widget.dart'; +import 'package:zenon_syrius_wallet_flutter/blocs/blocs.dart'; +import 'package:zenon_syrius_wallet_flutter/utils/app_colors.dart'; +import 'package:zenon_syrius_wallet_flutter/utils/color_utils.dart'; +import 'package:zenon_syrius_wallet_flutter/utils/extensions.dart'; +import 'package:zenon_syrius_wallet_flutter/utils/format_utils.dart'; +import 'package:zenon_syrius_wallet_flutter/utils/widget_utils.dart'; +import 'package:zenon_syrius_wallet_flutter/widgets/widgets.dart'; +import 'package:znn_sdk_dart/znn_sdk_dart.dart'; + +enum LatestTransactionsVersion { standard, dashboard, token } + +class LatestTransactions extends StatefulWidget { + final LatestTransactionsVersion version; + + const LatestTransactions({ + Key? key, + this.version = LatestTransactionsVersion.standard, + }) : super(key: key); + + @override + State createState() => _LatestTransactionsState(); +} + +class _LatestTransactionsState extends State { + late LatestTransactionsBloc _bloc; + + List? _transactions; + + bool _sortAscending = true; + + @override + Widget build(BuildContext context) { + return CardScaffold( + title: _getWidgetTitle(), + description: 'This card displays the latest transactions (including ZTS ' + 'tokens) of your selected address', + childBuilder: () { + _bloc = LatestTransactionsBloc(); + return _getTable(); + }, + onRefreshPressed: () => _bloc.refreshResults(), + ); + } + + Widget _getTable() { + return InfiniteScrollTable( + bloc: _bloc, + headerColumns: widget.version == LatestTransactionsVersion.dashboard + ? _getHeaderColumnsForDashboardWidget() + : _getHeaderColumnsForTransferWidget(), + generateRowCells: _rowCellsGenerator, + ); + } + + List _rowCellsGenerator( + AccountBlock transaction, + bool isSelected, { + LatestTransactions? model, + }) => + widget.version == LatestTransactionsVersion.dashboard + ? _getCellsForDashboardWidget(isSelected, transaction) + : _getCellsForTransferWidget(isSelected, transaction); + + List _getCellsForTransferWidget( + bool isSelected, + AccountBlock transactionBlock, + ) { + AccountBlock infoBlock = + BlockUtils.isReceiveBlock(transactionBlock.blockType) + ? transactionBlock.pairedAccountBlock! + : transactionBlock; + return [ + isSelected + ? WidgetUtils.getMarqueeAddressTableCell(infoBlock.address, context) + : WidgetUtils.getTextAddressTableCell(infoBlock.address, context), + isSelected + ? WidgetUtils.getMarqueeAddressTableCell(infoBlock.toAddress, context) + : WidgetUtils.getTextAddressTableCell(infoBlock.toAddress, context), + isSelected + ? InfiniteScrollTableCell.withMarquee(infoBlock.hash.toString(), + flex: 2) + : InfiniteScrollTableCell.withText( + context, + infoBlock.hash.toShortString(), + flex: 2, + ), + InfiniteScrollTableCell(Padding( + padding: const EdgeInsets.only(right: 10), + child: Marquee( + animationDuration: const Duration(milliseconds: 1000), + backDuration: const Duration(milliseconds: 1000), + child: FormattedAmountWithTooltip( + amount: infoBlock.amount.addDecimals( + infoBlock.token?.decimals ?? 0, + ), + tokenSymbol: infoBlock.token?.symbol ?? '', + builder: (formattedAmount, tokenSymbol) => Text( + formattedAmount, + style: Theme.of(context).textTheme.titleMedium!.copyWith( + color: AppColors.subtitleColor, + ), + ), + ), + ), + )), + InfiniteScrollTableCell.withText( + context, + infoBlock.confirmationDetail?.momentumTimestamp == null + ? 'Pending' + : FormatUtils.formatData( + infoBlock.confirmationDetail!.momentumTimestamp * 1000), + ), + InfiniteScrollTableCell(Align( + alignment: Alignment.centerLeft, + child: _getTransactionTypeIcon(transactionBlock))), + InfiniteScrollTableCell( + Align( + alignment: Alignment.centerLeft, + child: infoBlock.token != null + ? _showTokenSymbol(infoBlock) + : Container()), + ), + ]; + } + + List _getHeaderColumnsForTransferWidget() { + return [ + InfiniteScrollTableHeaderColumn( + columnName: 'Sender', + onSortArrowsPressed: _onSortArrowsPressed, + flex: 2, + ), + InfiniteScrollTableHeaderColumn( + columnName: 'Receiver', + onSortArrowsPressed: _onSortArrowsPressed, + flex: 2, + ), + InfiniteScrollTableHeaderColumn( + columnName: 'Hash', + onSortArrowsPressed: _onSortArrowsPressed, + flex: 2, + ), + InfiniteScrollTableHeaderColumn( + columnName: 'Amount', + onSortArrowsPressed: _onSortArrowsPressed, + ), + InfiniteScrollTableHeaderColumn( + columnName: 'Date', + onSortArrowsPressed: _onSortArrowsPressed, + ), + InfiniteScrollTableHeaderColumn( + columnName: 'Type', + onSortArrowsPressed: _onSortArrowsPressed, + ), + InfiniteScrollTableHeaderColumn( + columnName: 'Assets', + onSortArrowsPressed: _onSortArrowsPressed, + ), + ]; + } + + Widget _getTransactionTypeIcon(AccountBlock block) { + if (BlockUtils.isSendBlock(block.blockType)) { + return const Icon( + MaterialCommunityIcons.arrow_up, + color: AppColors.darkHintTextColor, + size: 20.0, + ); + } + if (BlockUtils.isReceiveBlock(block.blockType)) { + return const Icon( + MaterialCommunityIcons.arrow_down, + color: AppColors.lightHintTextColor, + size: 20.0, + ); + } + return Text( + FormatUtils.extractNameFromEnum( + BlockTypeEnum.values[block.blockType], + ), + textAlign: TextAlign.start, + style: Theme.of(context).textTheme.titleSmall, + ); + } + + Widget _showTokenSymbol(AccountBlock block) { + return Transform( + transform: Matrix4.identity()..scale(0.8), + alignment: Alignment.bottomCenter, + child: Chip( + backgroundColor: ColorUtils.getTokenColor(block.tokenStandard), + label: Text(block.token?.symbol ?? ''), + side: BorderSide.none, + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap)); + } + + void _onSortArrowsPressed(String columnName) { + switch (columnName) { + case 'Sender': + _sortAscending + ? _transactions!.sort( + (a, b) => a.address.toString().compareTo( + b.address.toString(), + ), + ) + : _transactions!.sort( + (a, b) => b.address.toString().compareTo( + a.address.toString(), + ), + ); + break; + case 'Receiver': + _sortAscending + ? _transactions!.sort( + (a, b) => a.toAddress.toString().compareTo( + b.toAddress.toString(), + ), + ) + : _transactions!.sort((a, b) => + b.toAddress.toString().compareTo(a.toAddress.toString())); + break; + case 'Hash': + _sortAscending + ? _transactions!.sort( + (a, b) => a.hash.toString().compareTo( + b.hash.toString(), + ), + ) + : _transactions!.sort( + (a, b) => b.hash.toString().compareTo( + a.hash.toString(), + ), + ); + break; + case 'Amount': + _sortAscending + ? _transactions!.sort((a, b) => a.amount.compareTo(b.amount)) + : _transactions!.sort((a, b) => b.amount.compareTo(a.amount)); + break; + case 'Date': + _sortAscending + ? _transactions!.sort( + (a, b) => a.confirmationDetail!.momentumTimestamp.compareTo( + b.confirmationDetail!.momentumTimestamp, + )) + : _transactions!.sort( + (a, b) => b.confirmationDetail!.momentumTimestamp.compareTo( + a.confirmationDetail!.momentumTimestamp, + )); + break; + case 'Type': + _sortAscending + ? _transactions!.sort((a, b) => a.blockType.compareTo(b.blockType)) + : _transactions!.sort((a, b) => b.blockType.compareTo(a.blockType)); + break; + case 'Assets': + _sortAscending + ? _transactions!.sort( + (a, b) => a.token!.symbol.compareTo(b.token!.symbol), + ) + : _transactions!.sort( + (a, b) => b.token!.symbol.compareTo(a.token!.symbol), + ); + break; + default: + _sortAscending + ? _transactions!.sort( + (a, b) => a.tokenStandard.toString().compareTo( + b.tokenStandard.toString(), + ), + ) + : _transactions!.sort( + (a, b) => b.tokenStandard.toString().compareTo( + a.tokenStandard.toString(), + ), + ); + break; + } + + setState(() { + _sortAscending = !_sortAscending; + }); + } + + @override + void dispose() { + _bloc.dispose(); + super.dispose(); + } + + List _getHeaderColumnsForDashboardWidget() { + return [ + InfiniteScrollTableHeaderColumn( + columnName: 'Sender', + onSortArrowsPressed: _onSortArrowsPressed, + flex: 2, + ), + InfiniteScrollTableHeaderColumn( + columnName: 'Amount', + onSortArrowsPressed: _onSortArrowsPressed, + ), + InfiniteScrollTableHeaderColumn( + columnName: 'Date', + onSortArrowsPressed: _onSortArrowsPressed, + ), + InfiniteScrollTableHeaderColumn( + columnName: 'Type', + onSortArrowsPressed: _onSortArrowsPressed, + ), + InfiniteScrollTableHeaderColumn( + columnName: 'Assets', + onSortArrowsPressed: _onSortArrowsPressed, + ), + ]; + } + + List _getCellsForDashboardWidget( + bool isSelected, + AccountBlock transactionBlock, + ) { + AccountBlock infoBlock = + BlockUtils.isReceiveBlock(transactionBlock.blockType) + ? transactionBlock.pairedAccountBlock! + : transactionBlock; + + return [ + isSelected + ? WidgetUtils.getMarqueeAddressTableCell(infoBlock.address, context) + : WidgetUtils.getTextAddressTableCell(infoBlock.address, context), + InfiniteScrollTableCell( + Padding( + padding: const EdgeInsets.only(right: 10), + child: Marquee( + animationDuration: const Duration(milliseconds: 1000), + backDuration: const Duration(milliseconds: 1000), + child: FormattedAmountWithTooltip( + amount: infoBlock.amount.addDecimals( + infoBlock.token?.decimals ?? 0, + ), + tokenSymbol: infoBlock.token?.symbol ?? '', + builder: (formattedAmount, tokenSymbol) => Text( + formattedAmount, + style: Theme.of(context).textTheme.titleMedium!.copyWith( + color: AppColors.subtitleColor, + ), + ), + ), + ), + ), + ), + InfiniteScrollTableCell.withText( + context, + infoBlock.confirmationDetail?.momentumTimestamp == null + ? 'Pending' + : FormatUtils.formatData( + infoBlock.confirmationDetail!.momentumTimestamp * 1000, + ), + ), + InfiniteScrollTableCell( + Align( + alignment: Alignment.center, + child: _getTransactionTypeIcon(transactionBlock)), + ), + InfiniteScrollTableCell( + Align( + alignment: Alignment.centerLeft, + child: infoBlock.token != null + ? _showTokenSymbol(infoBlock) + : Container()), + ) + ]; + } + + String _getWidgetTitle() => widget.version == LatestTransactionsVersion.token + ? 'Token Transactions' + : 'Latest Transactions'; +} diff --git a/lib/widgets/modular_widgets/transfer_widgets/pending_transactions/pending_transactions.dart b/lib/widgets/modular_widgets/transfer_widgets/pending_transactions/pending_transactions.dart new file mode 100644 index 00000000..f72c973d --- /dev/null +++ b/lib/widgets/modular_widgets/transfer_widgets/pending_transactions/pending_transactions.dart @@ -0,0 +1,304 @@ +import 'package:flutter/material.dart'; +import 'package:marquee_widget/marquee_widget.dart'; +import 'package:stacked/stacked.dart'; +import 'package:zenon_syrius_wallet_flutter/blocs/transfer/pending_transactions_bloc.dart'; +import 'package:zenon_syrius_wallet_flutter/blocs/transfer/receive_transaction_bloc.dart'; +import 'package:zenon_syrius_wallet_flutter/utils/app_colors.dart'; +import 'package:zenon_syrius_wallet_flutter/utils/color_utils.dart'; +import 'package:zenon_syrius_wallet_flutter/utils/extensions.dart'; +import 'package:zenon_syrius_wallet_flutter/utils/format_utils.dart'; +import 'package:zenon_syrius_wallet_flutter/utils/notification_utils.dart'; +import 'package:zenon_syrius_wallet_flutter/utils/widget_utils.dart'; +import 'package:zenon_syrius_wallet_flutter/widgets/widgets.dart'; +import 'package:znn_sdk_dart/znn_sdk_dart.dart'; + +class PendingTransactions extends StatefulWidget { + const PendingTransactions({ + Key? key, + }) : super(key: key); + + @override + State createState() => _PendingTransactionsState(); +} + +class _PendingTransactionsState extends State { + late PendingTransactionsBloc _bloc; + List? _transactions; + + bool _sortAscending = true; + + @override + Widget build(BuildContext context) { + return CardScaffold( + title: _getWidgetTitle(), + description: 'This card displays the pending transactions (including ZTS ' + 'tokens) for the selected address', + childBuilder: () { + _bloc = PendingTransactionsBloc(); + return _getTable(); + }, + onRefreshPressed: () => _bloc.refreshResults(), + ); + } + + Widget _getTable() { + return InfiniteScrollTable( + bloc: _bloc, + generateRowCells: _rowCellsGenerator, + headerColumns: _getHeaderColumnsForPendingTransactions(), + ); + } + + List _rowCellsGenerator(AccountBlock transaction, bool isSelected) => + _getCellsForPendingTransactions(isSelected, transaction); + + List _getCellsForPendingTransactions( + bool isSelected, AccountBlock transaction) { + AccountBlock infoBlock = BlockUtils.isReceiveBlock(transaction.blockType) + ? transaction.pairedAccountBlock! + : transaction; + return [ + isSelected + ? WidgetUtils.getMarqueeAddressTableCell(infoBlock.address, context) + : WidgetUtils.getTextAddressTableCell(infoBlock.address, context), + isSelected + ? WidgetUtils.getMarqueeAddressTableCell(infoBlock.toAddress, context) + : WidgetUtils.getTextAddressTableCell(infoBlock.toAddress, context), + isSelected + ? InfiniteScrollTableCell.withMarquee(infoBlock.hash.toString(), + flex: 2) + : InfiniteScrollTableCell.withText( + context, + infoBlock.hash.toShortString(), + flex: 2, + ), + InfiniteScrollTableCell(Padding( + padding: const EdgeInsets.only(right: 10), + child: Marquee( + animationDuration: const Duration(milliseconds: 1000), + backDuration: const Duration(milliseconds: 1000), + child: FormattedAmountWithTooltip( + amount: infoBlock.amount.addDecimals( + infoBlock.token?.decimals ?? 0, + ), + tokenSymbol: infoBlock.token?.symbol ?? '', + builder: (formattedAmount, tokenSymbol) => Text( + formattedAmount, + style: Theme.of(context).textTheme.titleMedium!.copyWith( + color: AppColors.subtitleColor, + ), + ), + ), + ), + )), + InfiniteScrollTableCell.withText( + context, + infoBlock.confirmationDetail?.momentumTimestamp == null + ? 'Pending' + : FormatUtils.formatData( + infoBlock.confirmationDetail!.momentumTimestamp * 1000), + ), + InfiniteScrollTableCell( + Align( + alignment: Alignment.centerLeft, + child: infoBlock.token != null + ? _showTokenSymbol(infoBlock) + : Container()), + ), + InfiniteScrollTableCell( + _getReceiveContainer(isSelected, infoBlock, _bloc)), + ]; + } + + List + _getHeaderColumnsForPendingTransactions() { + return [ + InfiniteScrollTableHeaderColumn( + columnName: 'Sender', + onSortArrowsPressed: _onSortArrowsPressed, + flex: 2, + ), + InfiniteScrollTableHeaderColumn( + columnName: 'Receiver', + onSortArrowsPressed: _onSortArrowsPressed, + flex: 2, + ), + InfiniteScrollTableHeaderColumn( + columnName: 'Hash', + onSortArrowsPressed: _onSortArrowsPressed, + flex: 2, + ), + InfiniteScrollTableHeaderColumn( + columnName: 'Amount', + onSortArrowsPressed: _onSortArrowsPressed, + ), + InfiniteScrollTableHeaderColumn( + columnName: 'Date', + onSortArrowsPressed: _onSortArrowsPressed, + ), + InfiniteScrollTableHeaderColumn( + columnName: 'Assets', + onSortArrowsPressed: _onSortArrowsPressed, + ), + const InfiniteScrollTableHeaderColumn( + columnName: '', + ), + ]; + } + + void _onSortArrowsPressed(String columnName) { + switch (columnName) { + case 'Sender': + _sortAscending + ? _transactions!.sort( + (a, b) => a.address.toString().compareTo( + b.address.toString(), + ), + ) + : _transactions!.sort( + (a, b) => b.address.toString().compareTo( + a.address.toString(), + ), + ); + break; + case 'Receiver': + _sortAscending + ? _transactions!.sort( + (a, b) => a.toAddress.toString().compareTo( + b.toAddress.toString(), + ), + ) + : _transactions!.sort((a, b) => + b.toAddress.toString().compareTo(a.toAddress.toString())); + break; + case 'Hash': + _sortAscending + ? _transactions!.sort( + (a, b) => a.hash.toString().compareTo( + b.hash.toString(), + ), + ) + : _transactions!.sort( + (a, b) => b.hash.toString().compareTo( + a.hash.toString(), + ), + ); + break; + case 'Amount': + _sortAscending + ? _transactions!.sort((a, b) => a.amount.compareTo(b.amount)) + : _transactions!.sort((a, b) => b.amount.compareTo(a.amount)); + break; + case 'Date': + _sortAscending + ? _transactions!.sort( + (a, b) => a.confirmationDetail!.momentumTimestamp.compareTo( + b.confirmationDetail!.momentumTimestamp, + )) + : _transactions!.sort( + (a, b) => b.confirmationDetail!.momentumTimestamp.compareTo( + a.confirmationDetail!.momentumTimestamp, + )); + break; + case 'Assets': + _sortAscending + ? _transactions!.sort( + (a, b) => a.token!.symbol.compareTo(b.token!.symbol), + ) + : _transactions!.sort( + (a, b) => b.token!.symbol.compareTo(a.token!.symbol), + ); + break; + default: + _sortAscending + ? _transactions!.sort( + (a, b) => a.tokenStandard.toString().compareTo( + b.tokenStandard.toString(), + ), + ) + : _transactions!.sort( + (a, b) => b.tokenStandard.toString().compareTo( + a.tokenStandard.toString(), + ), + ); + break; + } + + setState(() { + _sortAscending = !_sortAscending; + }); + } + + Widget _getReceiveContainer( + bool isSelected, + AccountBlock transaction, + PendingTransactionsBloc model, + ) { + return Align( + alignment: Alignment.centerLeft, + child: _getReceiveButtonViewModel(model, isSelected, transaction)); + } + + Widget _getReceiveButtonViewModel( + PendingTransactionsBloc transactionModel, + bool isSelected, + AccountBlock transactionItem, + ) { + return ViewModelBuilder.reactive( + onViewModelReady: (model) { + model.stream.listen( + (event) { + if (event != null) { + transactionModel.refreshResults(); + } + }, + onError: (error) async { + await NotificationUtils.sendNotificationError( + error, 'Error while receiving transaction'); + }, + ); + }, + builder: (_, model, __) => _getReceiveButton( + model, + transactionItem.hash.toString(), + ), + viewModelBuilder: () => ReceiveTransactionBloc(), + ); + } + + Widget _getReceiveButton( + ReceiveTransactionBloc model, + String transactionHash, + ) { + return MaterialIconButton( + size: 25.0, + iconData: Icons.download_for_offline, + onPressed: () { + _onReceivePressed(model, transactionHash); + }, + ); + } + + void _onReceivePressed(ReceiveTransactionBloc model, String id) { + model.receiveTransaction(id, context); + } + + Widget _showTokenSymbol(AccountBlock block) { + return Transform( + transform: Matrix4.identity()..scale(0.8), + alignment: Alignment.bottomCenter, + child: Chip( + backgroundColor: ColorUtils.getTokenColor(block.tokenStandard), + label: Text(block.token?.symbol ?? ''), + side: BorderSide.none, + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap)); + } + + @override + void dispose() { + _bloc.dispose(); + super.dispose(); + } + + String _getWidgetTitle() => 'Pending Transactions'; +} diff --git a/lib/widgets/modular_widgets/transfer_widgets/receive/receive_large.dart b/lib/widgets/modular_widgets/transfer_widgets/receive/receive_large.dart index 8dceacb5..88adeb04 100644 --- a/lib/widgets/modular_widgets/transfer_widgets/receive/receive_large.dart +++ b/lib/widgets/modular_widgets/transfer_widgets/receive/receive_large.dart @@ -100,7 +100,7 @@ class _ReceiveLargeCardState extends State { ), ReceiveQrImage( data: _getQrString(), - size: 150.0, + size: 150, tokenStandard: _selectedToken.tokenStandard, context: context, ), @@ -109,41 +109,8 @@ class _ReceiveLargeCardState extends State { ), Expanded( child: Column( + mainAxisAlignment: MainAxisAlignment.start, children: [ - Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Expanded( - flex: 7, - child: Form( - key: _amountKey, - autovalidateMode: - AutovalidateMode.onUserInteraction, - child: InputField( - validator: InputValidators.validateAmount, - onChanged: (value) => setState(() {}), - inputFormatters: - FormatUtils.getAmountTextInputFormatters( - _amountController.text, - ), - controller: _amountController, - suffixIcon: Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.end, - children: [ - _getCoinDropdown(), - const SizedBox( - width: 15.0, - ), - ], - ), - hintText: 'Amount', - ), - ), - ), - ], - ), - kVerticalSpacing, Row( children: [ Expanded( @@ -156,6 +123,35 @@ class _ReceiveLargeCardState extends State { ), ], ), + kVerticalSpacing, + Form( + key: _amountKey, + autovalidateMode: AutovalidateMode.onUserInteraction, + child: InputField( + validator: (value) => InputValidators.correctValue( + value, + kBigP255m1, + _selectedToken.decimals, + BigInt.zero), + onChanged: (value) => setState(() {}), + inputFormatters: + FormatUtils.getAmountTextInputFormatters( + _amountController.text, + ), + controller: _amountController, + suffixIcon: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.end, + children: [ + _getCoinDropdown(), + const SizedBox( + width: 15.0, + ), + ], + ), + hintText: 'Amount', + ), + ), ], ), ), @@ -180,8 +176,8 @@ class _ReceiveLargeCardState extends State { } String _getQrString() { - return '${_selectedToken.symbol.toLowerCase()}:$_selectedSelfAddress?zts=' - '${_selectedToken.tokenStandard}' + return '${_selectedToken.symbol.toLowerCase()}:' + '$_selectedSelfAddress?zts=${_selectedToken.tokenStandard}' '&amount=${_getAmount()}'; } diff --git a/lib/widgets/modular_widgets/transfer_widgets/receive/receive_medium.dart b/lib/widgets/modular_widgets/transfer_widgets/receive/receive_medium.dart index ddc25ee9..75031601 100644 --- a/lib/widgets/modular_widgets/transfer_widgets/receive/receive_medium.dart +++ b/lib/widgets/modular_widgets/transfer_widgets/receive/receive_medium.dart @@ -96,7 +96,7 @@ class _ReceiveMediumCardState extends State { ), ReceiveQrImage( data: _getQrString(), - size: 110.0, + size: 110, tokenStandard: _selectedToken.tokenStandard, context: context, ), @@ -123,7 +123,11 @@ class _ReceiveMediumCardState extends State { key: _amountKey, autovalidateMode: AutovalidateMode.onUserInteraction, child: InputField( - validator: InputValidators.validateAmount, + validator: (value) => InputValidators.correctValue( + value, + kBigP255m1, + _selectedToken.decimals, + BigInt.zero), onChanged: (value) => setState(() {}), inputFormatters: FormatUtils.getAmountTextInputFormatters( diff --git a/lib/widgets/modular_widgets/transfer_widgets/send/send_large.dart b/lib/widgets/modular_widgets/transfer_widgets/send/send_large.dart index 3b4bb16a..536422a6 100644 --- a/lib/widgets/modular_widgets/transfer_widgets/send/send_large.dart +++ b/lib/widgets/modular_widgets/transfer_widgets/send/send_large.dart @@ -226,7 +226,7 @@ class _SendLargeCardState extends State { showDialogWithNoAndYesOptions( isBarrierDismissible: false, context: context, - title: 'Send Payment', + title: 'Send', description: 'Are you sure you want to transfer ' '${_amountController.text} ${_selectedToken.symbol} to ' '${ZenonAddressUtils.getLabel(_recipientController.text)} ?', @@ -314,9 +314,9 @@ class _SendLargeCardState extends State { return ViewModelBuilder.reactive( onViewModelReady: (model) { model.stream.listen( - (event) { + (event) async { if (event is AccountBlockTemplate) { - _sendConfirmationNotification(); + await _sendConfirmationNotification(); setState(() { _sendPaymentButtonKey.currentState?.animateReverse(); _amountController = TextEditingController(); @@ -326,9 +326,9 @@ class _SendLargeCardState extends State { }); } }, - onError: (error) { + onError: (error) async { _sendPaymentButtonKey.currentState?.animateReverse(); - _sendErrorNotification(error); + await _sendErrorNotification(error); }, ); }, @@ -343,8 +343,8 @@ class _SendLargeCardState extends State { ); } - void _sendErrorNotification(error) { - NotificationUtils.sendNotificationError( + Future _sendErrorNotification(error) async { + await NotificationUtils.sendNotificationError( error, 'Couldn\'t send ${_amountController.text} ' '${_selectedToken.symbol} ' @@ -352,8 +352,8 @@ class _SendLargeCardState extends State { ); } - void _sendConfirmationNotification() { - sl.get().addNotification( + Future _sendConfirmationNotification() async { + await sl.get().addNotification( WalletNotification( title: 'Sent ${_amountController.text} ${_selectedToken.symbol} ' 'to ${ZenonAddressUtils.getLabel(_recipientController.text)}', diff --git a/lib/widgets/modular_widgets/transfer_widgets/send/send_medium.dart b/lib/widgets/modular_widgets/transfer_widgets/send/send_medium.dart index 82029410..ed4dc6e5 100644 --- a/lib/widgets/modular_widgets/transfer_widgets/send/send_medium.dart +++ b/lib/widgets/modular_widgets/transfer_widgets/send/send_medium.dart @@ -180,7 +180,7 @@ class _SendMediumCardState extends State { showDialogWithNoAndYesOptions( context: context, isBarrierDismissible: true, - title: 'Send Payment', + title: 'Send', description: 'Are you sure you want to transfer ' '${_amountController.text} ${_selectedToken.symbol} to ' '${ZenonAddressUtils.getLabel(_recipientController.text)} ?', @@ -254,9 +254,9 @@ class _SendMediumCardState extends State { fireOnViewModelReadyOnce: true, onViewModelReady: (model) { model.stream.listen( - (event) { + (event) async { if (event is AccountBlockTemplate) { - _sendConfirmationNotification(); + await _sendConfirmationNotification(); setState(() { _sendPaymentButtonKey.currentState?.animateReverse(); _amountController = TextEditingController(); @@ -266,9 +266,9 @@ class _SendMediumCardState extends State { }); } }, - onError: (error) { + onError: (error) async { _sendPaymentButtonKey.currentState?.animateReverse(); - _sendErrorNotification(error); + await _sendErrorNotification(error); }, ); }, @@ -282,16 +282,16 @@ class _SendMediumCardState extends State { ); } - void _sendErrorNotification(error) { - NotificationUtils.sendNotificationError( + Future _sendErrorNotification(error) async { + await NotificationUtils.sendNotificationError( error, 'Couldn\'t send ${_amountController.text} ${_selectedToken.symbol} ' 'to ${_recipientController.text}', ); } - void _sendConfirmationNotification() { - sl.get().addNotification( + Future _sendConfirmationNotification() async { + await sl.get().addNotification( WalletNotification( title: 'Sent ${_amountController.text} ${_selectedToken.symbol} ' 'to ${ZenonAddressUtils.getLabel(_recipientController.text)}', diff --git a/lib/widgets/modular_widgets/transfer_widgets/transfer_widgets.dart b/lib/widgets/modular_widgets/transfer_widgets/transfer_widgets.dart index 7e7f8887..d9b1dcb2 100644 --- a/lib/widgets/modular_widgets/transfer_widgets/transfer_widgets.dart +++ b/lib/widgets/modular_widgets/transfer_widgets/transfer_widgets.dart @@ -1,4 +1,5 @@ -export 'latest_transactions/latest_transactions_transfer_widget.dart'; +export 'latest_transactions/latest_transactions.dart'; +export 'pending_transactions/pending_transactions.dart'; export 'receive/receive_large.dart'; export 'receive/receive_medium.dart'; export 'receive/receive_small.dart'; diff --git a/lib/widgets/modular_widgets/wallet_connect_widgets/wallet_connect_camera_card.dart b/lib/widgets/modular_widgets/wallet_connect_widgets/wallet_connect_camera_card.dart index 202fd9b0..b69bd5ba 100644 --- a/lib/widgets/modular_widgets/wallet_connect_widgets/wallet_connect_camera_card.dart +++ b/lib/widgets/modular_widgets/wallet_connect_widgets/wallet_connect_camera_card.dart @@ -1,11 +1,13 @@ +import 'dart:async'; import 'dart:io'; import 'package:ai_barcode_scanner/ai_barcode_scanner.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_vector_icons/flutter_vector_icons.dart'; import 'package:logging/logging.dart'; import 'package:wallet_connect_uri_validator/wallet_connect_uri_validator.dart'; import 'package:zenon_syrius_wallet_flutter/main.dart'; -import 'package:zenon_syrius_wallet_flutter/services/wallet_connect_service.dart'; +import 'package:zenon_syrius_wallet_flutter/services/i_web3wallet_service.dart'; import 'package:zenon_syrius_wallet_flutter/utils/utils.dart'; import 'package:zenon_syrius_wallet_flutter/widgets/widgets.dart'; @@ -59,22 +61,60 @@ class _WalletConnectCameraCardState extends State { MaterialPageRoute( builder: (context) => AiBarcodeScanner( validator: (value) { - return canParseWalletConnectUri(value); + return (canParseWalletConnectUri(value)); }, canPop: true, onScan: (String value) async { - final wcService = sl.get(); + final wcService = sl.get(); final pairingInfo = await wcService.pair(Uri.parse(value)); Logger('WalletConnectCameraCard').log(Level.INFO, 'pairing info', pairingInfo.toJson()); setState(() {}); }, - onDetect: (p0) {}, - onDispose: () {}, + onScannerStarted: (p0) { + // Pop navigator and close camera after 10 seconds + Timer(const Duration(seconds: 30), () { + Navigator.pop(context); + }); + Logger('WalletConnectCameraCard') + .log(Level.INFO, 'onScannerStarted'); + }, + onDetect: (p0) { + Logger('WalletConnectCameraCard') + .log(Level.INFO, 'onDetect', p0.toString()); + }, + onDispose: () { + Logger('WalletConnectCameraCard') + .log(Level.INFO, 'onDispose'); + }, controller: MobileScannerController( + facing: CameraFacing.front, detectionSpeed: DetectionSpeed.noDuplicates, ), + errorBuilder: (p0, p1, p2) { + // Pop navigator and close camera after 10 seconds + Timer(const Duration(seconds: 10), () { + Navigator.pop(context); + }); + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text('${p1.errorCode}', + style: Theme.of(context) + .textTheme + .bodyMedium), + Container(height: 16), + const Icon( + MaterialCommunityIcons.camera_off, + size: 32, + color: AppColors.errorColor, + ), + ], + ), + ); + }, ), ), ); @@ -90,6 +130,11 @@ class _WalletConnectCameraCardState extends State { ); } + @override + void dispose() { + super.dispose(); + } + bool canParseWalletConnectUri(String wcUri) { WalletConnectUri? walletConnectUri; walletConnectUri = WalletConnectUri.tryParse(wcUri); diff --git a/lib/widgets/modular_widgets/wallet_connect_widgets/wallet_connect_pairing_list_card.dart b/lib/widgets/modular_widgets/wallet_connect_widgets/wallet_connect_pairing_list_card.dart index d1648013..4a87a9da 100644 --- a/lib/widgets/modular_widgets/wallet_connect_widgets/wallet_connect_pairing_list_card.dart +++ b/lib/widgets/modular_widgets/wallet_connect_widgets/wallet_connect_pairing_list_card.dart @@ -5,7 +5,7 @@ import 'package:zenon_syrius_wallet_flutter/blocs/blocs.dart'; import 'package:zenon_syrius_wallet_flutter/blocs/wallet_connect/wallet_connect_pairings_bloc.dart'; import 'package:zenon_syrius_wallet_flutter/blocs/wallet_connect/wallet_connect_sessions_bloc.dart'; import 'package:zenon_syrius_wallet_flutter/main.dart'; -import 'package:zenon_syrius_wallet_flutter/services/wallet_connect_service.dart'; +import 'package:zenon_syrius_wallet_flutter/services/i_web3wallet_service.dart'; import 'package:zenon_syrius_wallet_flutter/utils/extensions.dart'; import 'package:zenon_syrius_wallet_flutter/widgets/reusable_widgets/icons/clear_icon.dart'; import 'package:zenon_syrius_wallet_flutter/widgets/widgets.dart'; @@ -30,8 +30,6 @@ class _WalletConnectPairingsCardState extends State { @override void initState() { - // Initialize WalletConnect client - sl.get().initClient(); super.initState(); } @@ -138,13 +136,13 @@ class _WalletConnectPairingsCardState extends State { Future _onDeactivatePairingIconPressed(PairingInfo pairingInfo) async { try { - await sl().deactivatePairing( + await sl().deactivatePairing( topic: pairingInfo.topic, ); _pairingsBloc.refreshResults(); sl().refreshResults(); } catch (e) { - sl().addErrorNotification( + await sl().addErrorNotification( e, 'Error while deactivating pair', ); diff --git a/lib/widgets/modular_widgets/wallet_connect_widgets/wallet_connect_qr_card.dart b/lib/widgets/modular_widgets/wallet_connect_widgets/wallet_connect_qr_card.dart index 5ba1f628..c6fa0f36 100644 --- a/lib/widgets/modular_widgets/wallet_connect_widgets/wallet_connect_qr_card.dart +++ b/lib/widgets/modular_widgets/wallet_connect_widgets/wallet_connect_qr_card.dart @@ -12,7 +12,7 @@ import 'package:zenon_syrius_wallet_flutter/blocs/blocs.dart'; import 'package:zenon_syrius_wallet_flutter/main.dart'; import 'package:zenon_syrius_wallet_flutter/model/database/notification_type.dart'; import 'package:zenon_syrius_wallet_flutter/model/database/wallet_notification.dart'; -import 'package:zenon_syrius_wallet_flutter/services/wallet_connect_service.dart'; +import 'package:zenon_syrius_wallet_flutter/services/i_web3wallet_service.dart'; import 'package:zenon_syrius_wallet_flutter/utils/utils.dart'; import 'package:zenon_syrius_wallet_flutter/widgets/widgets.dart'; import 'package:znn_sdk_dart/znn_sdk_dart.dart'; @@ -56,20 +56,26 @@ class _WalletConnectQrCardState extends State { mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Container( + height: 130, + width: 130, padding: const EdgeInsets.all(10.0), decoration: BoxDecoration( color: Theme.of(context).colorScheme.background, borderRadius: BorderRadius.circular(15.0), ), - child: PrettyQr( + child: PrettyQrView.data( data: 'Scan the WalletConnect QR from the dApp', - size: 100.0, - elementColor: AppColors.znnColor, - image: - const AssetImage('assets/images/qr_code_child_image_znn.png'), - typeNumber: 7, - errorCorrectLevel: QrErrorCorrectLevel.M, - roundEdges: true, + decoration: const PrettyQrDecoration( + shape: PrettyQrSmoothSymbol( + roundFactor: 0, + color: AppColors.znnColor, + ), + image: PrettyQrDecorationImage( + scale: 0.3, + padding: EdgeInsets.only(top: 10, bottom: 10), + image: AssetImage( + 'assets/images/qr_code_child_image_znn.png'))), + errorCorrectLevel: QrErrorCorrectLevel.H, ), ), MyOutlinedButton( @@ -92,7 +98,7 @@ class _WalletConnectQrCardState extends State { Future _pairWithDapp(Uri uri) async { try { - final wcService = sl.get(); + final wcService = sl.get(); final pairingInfo = await wcService.pair(uri); Logger('WalletConnectPairingCard') .log(Level.INFO, 'pairing info', pairingInfo.toJson()); @@ -100,7 +106,7 @@ class _WalletConnectQrCardState extends State { _uriKey.currentState?.reset(); setState(() {}); } catch (e) { - NotificationUtils.sendNotificationError(e, 'Pairing failed'); + await NotificationUtils.sendNotificationError(e, 'Pairing failed'); } } @@ -139,35 +145,35 @@ class _WalletConnectQrCardState extends State { if (result.rawBytes!.isNotEmpty) { if (result.text.isNotEmpty && WalletConnectUri.tryParse(result.text) != null) { - windowManager.show(); + await windowManager.show(); _uriController.text = result.text; } else { - windowManager.show(); - sl().addNotification(WalletNotification( + await windowManager.show(); + await sl().addNotification(WalletNotification( title: 'Invalid QR code', timestamp: DateTime.now().millisecondsSinceEpoch, details: 'Please scan a valid WalletConnect QR code', type: NotificationType.error)); } } else { - windowManager.show(); - sl().addNotification(WalletNotification( + await windowManager.show(); + await sl().addNotification(WalletNotification( title: 'QR code scan failed', timestamp: DateTime.now().millisecondsSinceEpoch, details: 'Please scan a valid WalletConnect QR code', type: NotificationType.error)); } - _pairWithDapp(Uri.parse(result.text)); + await _pairWithDapp(Uri.parse(result.text)); } else { - windowManager.show(); - sl().addErrorNotification( + await windowManager.show(); + await sl().addErrorNotification( 'User canceled the QR scanning operation', 'User QR scan canceled', ); } } on Exception catch (e) { - windowManager.show(); - sl() + await windowManager.show(); + await sl() .addErrorNotification(e, 'Invalid QR code exception'); } } @@ -175,7 +181,7 @@ class _WalletConnectQrCardState extends State { Future checkPermissionForMacOS() async { if (Platform.isMacOS) { if (!await _requestAccessForMacOS()) { - sl().addNotification(WalletNotification( + await sl().addNotification(WalletNotification( title: 'Permission required', timestamp: DateTime.now().millisecondsSinceEpoch, details: diff --git a/lib/widgets/modular_widgets/wallet_connect_widgets/wallet_connect_uri_card.dart b/lib/widgets/modular_widgets/wallet_connect_widgets/wallet_connect_uri_card.dart index 39369c71..a4268130 100644 --- a/lib/widgets/modular_widgets/wallet_connect_widgets/wallet_connect_uri_card.dart +++ b/lib/widgets/modular_widgets/wallet_connect_widgets/wallet_connect_uri_card.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:logging/logging.dart'; import 'package:wallet_connect_uri_validator/wallet_connect_uri_validator.dart'; import 'package:zenon_syrius_wallet_flutter/main.dart'; -import 'package:zenon_syrius_wallet_flutter/services/wallet_connect_service.dart'; +import 'package:zenon_syrius_wallet_flutter/services/i_web3wallet_service.dart'; import 'package:zenon_syrius_wallet_flutter/utils/utils.dart'; import 'package:zenon_syrius_wallet_flutter/widgets/widgets.dart'; @@ -118,15 +118,16 @@ class _WalletConnectUriCardState extends State { Future _pairWithDapp(Uri uri) async { try { - final wcService = sl.get(); - final pairingInfo = await wcService.pair(uri); + final pairingInfo = await sl.get().pair(uri); Logger('WalletConnectPairingCard') .log(Level.INFO, 'pairing info', pairingInfo.toJson()); _uriController = TextEditingController(); _uriKey.currentState?.reset(); setState(() {}); - } catch (e) { - NotificationUtils.sendNotificationError(e, 'Pairing failed'); + } catch (e, stackTrace) { + Logger('WalletConnectPairingCard') + .log(Level.INFO, 'pairing failed', e, stackTrace); + await NotificationUtils.sendNotificationError(e, 'Pairing failed'); } } } diff --git a/lib/widgets/reusable_widgets/accelerator_project_details.dart b/lib/widgets/reusable_widgets/accelerator_project_details.dart index 5dfdca76..fcb4fd3b 100644 --- a/lib/widgets/reusable_widgets/accelerator_project_details.dart +++ b/lib/widgets/reusable_widgets/accelerator_project_details.dart @@ -10,12 +10,14 @@ class AcceleratorProjectDetails extends StatelessWidget { final Hash? hash; final int? creationTimestamp; final AcceleratorProjectStatus? acceleratorProjectStatus; + final bool isPhase; const AcceleratorProjectDetails({ this.owner, this.hash, this.creationTimestamp, this.acceleratorProjectStatus, + this.isPhase = false, Key? key, }) : super(key: key); @@ -44,7 +46,8 @@ class AcceleratorProjectDetails extends StatelessWidget { 'Created ${_formatData(creationTimestamp! * 1000)}', style: Theme.of(context).inputDecorationTheme.hintStyle, )); - if (acceleratorProjectStatus != null && + if (!isPhase && + acceleratorProjectStatus != null && acceleratorProjectStatus == AcceleratorProjectStatus.voting) { children.add(Text( _getTimeUntilVotingCloses(), diff --git a/lib/widgets/reusable_widgets/buttons/material_icon_button.dart b/lib/widgets/reusable_widgets/buttons/material_icon_button.dart index 4a05ec33..5f67b3d6 100644 --- a/lib/widgets/reusable_widgets/buttons/material_icon_button.dart +++ b/lib/widgets/reusable_widgets/buttons/material_icon_button.dart @@ -8,10 +8,12 @@ class MaterialIconButton extends StatelessWidget { final MaterialTapTargetSize materialTapTargetSize; final VoidCallback onPressed; final IconData iconData; + final double size; const MaterialIconButton({ required this.onPressed, required this.iconData, + required this.size, this.iconColor = AppColors.znnColor, this.hoverColor, this.padding = 8.0, @@ -31,7 +33,7 @@ class MaterialIconButton extends StatelessWidget { child: Icon( iconData, color: iconColor, - size: 15.0, + size: size, ), ); } diff --git a/lib/widgets/reusable_widgets/buttons/send_payment_button.dart b/lib/widgets/reusable_widgets/buttons/send_payment_button.dart index e69d58bd..7241e5d5 100644 --- a/lib/widgets/reusable_widgets/buttons/send_payment_button.dart +++ b/lib/widgets/reusable_widgets/buttons/send_payment_button.dart @@ -6,9 +6,9 @@ class SendPaymentButton extends LoadingButton { const SendPaymentButton({ required VoidCallback? onPressed, required Key key, - String text = 'Send payment', + String text = 'Send', Color? outlineColor, - Size minimumSize = const Size(150.0, 40.0), + Size minimumSize = const Size(100.0, 40.0), }) : super( onPressed: onPressed, text: text, diff --git a/lib/widgets/reusable_widgets/chart/standard_chart.dart b/lib/widgets/reusable_widgets/chart/standard_chart.dart index f51c7086..31a29737 100644 --- a/lib/widgets/reusable_widgets/chart/standard_chart.dart +++ b/lib/widgets/reusable_widgets/chart/standard_chart.dart @@ -65,7 +65,7 @@ class StandardChart extends StatelessWidget { drawVerticalLine: false, drawHorizontalLine: true, getDrawingHorizontalLine: (_) { - return FlLine( + return const FlLine( strokeWidth: 1.0, color: Colors.black87, dashArray: [3, 3], @@ -109,12 +109,12 @@ class StandardChart extends StatelessWidget { reservedSize: 26.0, ), ), - rightTitles: AxisTitles( + rightTitles: const AxisTitles( sideTitles: SideTitles( showTitles: false, ), ), - topTitles: AxisTitles( + topTitles: const AxisTitles( sideTitles: SideTitles( showTitles: false, )), @@ -126,7 +126,6 @@ class StandardChart extends StatelessWidget { minY: 0.0, lineBarsData: lineBarsData, ), - swapAnimationDuration: const Duration(milliseconds: 250), ), ); } diff --git a/lib/widgets/reusable_widgets/chart/standard_line_chart_bar_data.dart b/lib/widgets/reusable_widgets/chart/standard_line_chart_bar_data.dart index 63fd1add..92e34146 100644 --- a/lib/widgets/reusable_widgets/chart/standard_line_chart_bar_data.dart +++ b/lib/widgets/reusable_widgets/chart/standard_line_chart_bar_data.dart @@ -1,29 +1,14 @@ import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; -import 'package:zenon_syrius_wallet_flutter/utils/app_colors.dart'; class StandardLineChartBarData extends LineChartBarData { StandardLineChartBarData({ required Color color, required List? spots, }) : super( - spots: spots, - isCurved: false, color: color, barWidth: 3.0, isStrokeCapRound: true, - dotData: FlDotData( - show: true, - ), - belowBarData: BarAreaData( - show: true, - gradient: LinearGradient( - colors: [ - AppColors.znnColor.withOpacity(0.5), - AppColors.znnColor.withOpacity(0.0), - ], - stops: const [0.1, 1.0], - ), - ), + spots: spots ?? const [] ); } diff --git a/lib/widgets/reusable_widgets/infinite_scroll_table.dart b/lib/widgets/reusable_widgets/infinite_scroll_table.dart index 438fbb5e..b9b9cd8e 100644 --- a/lib/widgets/reusable_widgets/infinite_scroll_table.dart +++ b/lib/widgets/reusable_widgets/infinite_scroll_table.dart @@ -172,6 +172,7 @@ class _InfiniteScrollTableState extends State> { @override void dispose() { + _scrollController.dispose(); _pagingController.dispose(); _blocListingStateSubscription.cancel(); if (widget.disposeBloc) { diff --git a/lib/widgets/reusable_widgets/receive_qr_image.dart b/lib/widgets/reusable_widgets/receive_qr_image.dart index 638985f2..8d2fac35 100644 --- a/lib/widgets/reusable_widgets/receive_qr_image.dart +++ b/lib/widgets/reusable_widgets/receive_qr_image.dart @@ -1,12 +1,13 @@ import 'dart:io'; import 'dart:typed_data'; +import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:flutter_vector_icons/flutter_vector_icons.dart'; +import 'package:lottie/lottie.dart'; import 'package:open_filex/open_filex.dart'; import 'package:path/path.dart' as path; import 'package:pretty_qr_code/pretty_qr_code.dart'; -import 'package:screenshot/screenshot.dart'; import 'package:share_plus/share_plus.dart'; import 'package:zenon_syrius_wallet_flutter/utils/color_utils.dart'; import 'package:zenon_syrius_wallet_flutter/utils/utils.dart'; @@ -15,13 +16,18 @@ import 'package:znn_sdk_dart/znn_sdk_dart.dart'; class ReceiveQrImage extends StatelessWidget { final String data; - final double size; + final int size; final TokenStandard tokenStandard; final BuildContext context; - final ScreenshotController screenshotController = ScreenshotController(); + static const decorationImage = PrettyQrDecorationImage( + scale: 0.3, + padding: EdgeInsets.only(top: 10, bottom: 10), + image: AssetImage('assets/images/qr_code_child_image_znn.png'), + position: PrettyQrDecorationImagePosition.embedded, + ); - ReceiveQrImage({ + const ReceiveQrImage({ required this.data, required this.size, required this.tokenStandard, @@ -31,128 +37,162 @@ class ReceiveQrImage extends StatelessWidget { @override Widget build(BuildContext context) { - return Screenshot( - controller: screenshotController, - child: ClipRRect( - borderRadius: BorderRadius.circular( - 15.0, - ), - child: Container( - padding: const EdgeInsets.all( - 10.0, - ), - color: Theme.of(context).colorScheme.background, - child: ContextMenuRegion( - contextMenuBuilder: (context, offset) { - return AdaptiveTextSelectionToolbar( - anchors: TextSelectionToolbarAnchors( - primaryAnchor: offset, - ), - children: [ - Row( - children: [ - Expanded( - child: Directionality( - textDirection: TextDirection.rtl, - child: TextButton.icon( - icon: Icon( - MaterialCommunityIcons.share, - color: - Theme.of(context).colorScheme.onBackground, - size: 14, - ), - onPressed: () { - ContextMenuController.removeAny(); - _shareQR(); - }, - style: TextButton.styleFrom( - shape: const RoundedRectangleBorder(), - ), - label: Text( - AdaptiveTextSelectionToolbar.getButtonLabel( - context, - ContextMenuButtonItem( - label: 'Share QR', onPressed: () {})), - style: - Theme.of(context).textTheme.bodyMedium), + return ClipRRect( + borderRadius: BorderRadius.circular( + 15.0, + ), + child: Container( + height: size + 20, + width: size + 20, + padding: const EdgeInsets.all( + 10.0, + ), + color: Theme.of(context).colorScheme.background, + child: ContextMenuRegion( + contextMenuBuilder: (context, offset) { + return AdaptiveTextSelectionToolbar( + anchors: TextSelectionToolbarAnchors( + primaryAnchor: offset, + ), + children: [ + Row( + children: [ + Expanded( + child: Directionality( + textDirection: TextDirection.rtl, + child: TextButton.icon( + icon: Icon( + MaterialCommunityIcons.share, + color: Theme.of(context).colorScheme.onBackground, + size: 14, ), + onPressed: () { + ContextMenuController.removeAny(); + _shareQR(); + }, + style: TextButton.styleFrom( + shape: const RoundedRectangleBorder(), + ), + label: Text( + AdaptiveTextSelectionToolbar.getButtonLabel( + context, + ContextMenuButtonItem( + label: 'Share QR', onPressed: () {})), + style: Theme.of(context).textTheme.bodyMedium), ), ), - ], - ), - Row( - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - Expanded( - flex: 1, - child: Directionality( - textDirection: TextDirection.rtl, - child: TextButton.icon( - icon: Icon( - Icons.save_alt, - color: - Theme.of(context).colorScheme.onBackground, - size: 14, - ), - onPressed: () { - ContextMenuController.removeAny(); - _saveQR(); - }, - style: TextButton.styleFrom( - shape: const RoundedRectangleBorder(), - ), - label: Text( - AdaptiveTextSelectionToolbar.getButtonLabel( - context, - ContextMenuButtonItem( - label: 'Save QR', onPressed: () {})), - style: - Theme.of(context).textTheme.bodyMedium), + ), + ], + ), + Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Expanded( + flex: 1, + child: Directionality( + textDirection: TextDirection.rtl, + child: TextButton.icon( + icon: Icon( + Icons.save_alt, + color: Theme.of(context).colorScheme.onBackground, + size: 14, + ), + onPressed: () { + ContextMenuController.removeAny(); + _saveQR(); + }, + style: TextButton.styleFrom( + shape: const RoundedRectangleBorder(), ), + label: Text( + AdaptiveTextSelectionToolbar.getButtonLabel( + context, + ContextMenuButtonItem( + label: 'Save QR', onPressed: () {})), + style: Theme.of(context).textTheme.bodyMedium), ), ), - ], - ), - ], - ); - }, - child: PrettyQr( + ), + ], + ), + ], + ); + }, + child: PrettyQrView.data( data: data, - size: size, - elementColor: ColorUtils.getTokenColor(tokenStandard), - image: const AssetImage( - 'assets/images/qr_code_child_image_znn.png'), - typeNumber: 7, + decoration: PrettyQrDecoration( + shape: PrettyQrSmoothSymbol( + roundFactor: 0, + color: ColorUtils.getTokenColor(tokenStandard), + ), + image: decorationImage), errorCorrectLevel: QrErrorCorrectLevel.M, - roundEdges: true, - ), + errorBuilder: (context, error, stack) => Center( + child: Padding( + padding: const EdgeInsets.all(5.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Lottie.asset( + 'assets/lottie/ic_anim_no_data.json', + width: 32.0, + height: 32.0, + ), + Tooltip( + message: error.toString(), + child: Text( + 'Failed to create QR code', + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.bodyMedium, + )), + ], + ), + ), + ))), + ), + ); + } + + Future _getQRImageData() async { + final qr = QrImage(QrCode.fromData( + data: data, + errorCorrectLevel: QrErrorCorrectLevel.M, + )); + + final b = await qr.toImageAsBytes( + size: size, + format: ImageByteFormat.png, + decoration: PrettyQrDecoration( + shape: PrettyQrSmoothSymbol( + roundFactor: 0, + color: ColorUtils.getTokenColor(tokenStandard), ), - ), - )); + image: decorationImage)); + + if (b != null) return b.buffer.asUint8List(); + return null; } void _saveQR() async { - Uint8List? capture = await screenshotController.capture( - delay: const Duration(milliseconds: 20)); - if (capture != null) { + final imageData = await _getQRImageData(); + if (imageData != null) { String fileName = DateTime.now().millisecondsSinceEpoch.toString(); final imagePath = await File( '${znnDefaultPaths.cache.path}${path.separator}$fileName.png') .create(); - await imagePath.writeAsBytes(capture); + await imagePath.writeAsBytes(imageData); await OpenFilex.open(imagePath.path); } } void _shareQR() async { - Uint8List? capture = await screenshotController.capture( - delay: const Duration(milliseconds: 20)); - if (capture != null) { + final imageData = await _getQRImageData(); + if (imageData != null) { String fileName = DateTime.now().millisecondsSinceEpoch.toString(); final imagePath = await File( '${znnDefaultPaths.cache.path}${path.separator}$fileName.png') .create(); - await imagePath.writeAsBytes(capture); + await imagePath.writeAsBytes(imageData); await Share.shareXFiles([XFile(imagePath.path)]); } } diff --git a/lib/widgets/reusable_widgets/settings_address.dart b/lib/widgets/reusable_widgets/settings_address.dart index 4912b508..97acf695 100644 --- a/lib/widgets/reusable_widgets/settings_address.dart +++ b/lib/widgets/reusable_widgets/settings_address.dart @@ -83,6 +83,7 @@ class _SettingsAddressState extends State { width: 5.0, ), MaterialIconButton( + size: 15.0, iconData: Icons.edit, onPressed: () { setState(() { @@ -173,6 +174,7 @@ class _SettingsAddressState extends State { key: _changeButtonKey, ), MaterialIconButton( + size: 15.0, onPressed: () { setState(() { _labelController.text = kAddressLabelMap[widget.address]!; @@ -210,25 +212,25 @@ class _SettingsAddressState extends State { _editable = false; }); } else if (_labelController.text.isEmpty) { - NotificationUtils.sendNotificationError( + await NotificationUtils.sendNotificationError( 'Label can\'t be empty', 'Label can\'t be empty', ); } else if (_labelController.text.length > kAddressLabelMaxLength) { - NotificationUtils.sendNotificationError( + await NotificationUtils.sendNotificationError( 'The label ${_labelController.text} is ${_labelController.text.length} ' 'characters long, which is more than the $kAddressLabelMaxLength limit.', 'The label has more than $kAddressLabelMaxLength characters', ); } else { - NotificationUtils.sendNotificationError( + await NotificationUtils.sendNotificationError( 'Label ${_labelController.text}' ' already exists in the database', 'Label already exists', ); } } catch (e) { - NotificationUtils.sendNotificationError( + await NotificationUtils.sendNotificationError( e, 'Something went wrong while changing the address label', ); diff --git a/lib/widgets/reusable_widgets/settings_node.dart b/lib/widgets/reusable_widgets/settings_node.dart index 34d89de2..0984c228 100644 --- a/lib/widgets/reusable_widgets/settings_node.dart +++ b/lib/widgets/reusable_widgets/settings_node.dart @@ -4,6 +4,7 @@ import 'package:hive/hive.dart'; import 'package:zenon_syrius_wallet_flutter/utils/app_colors.dart'; import 'package:zenon_syrius_wallet_flutter/utils/constants.dart'; import 'package:zenon_syrius_wallet_flutter/utils/global.dart'; +import 'package:zenon_syrius_wallet_flutter/utils/input_validators.dart'; import 'package:zenon_syrius_wallet_flutter/utils/node_utils.dart'; import 'package:zenon_syrius_wallet_flutter/utils/notification_utils.dart'; import 'package:zenon_syrius_wallet_flutter/widgets/widgets.dart'; @@ -29,6 +30,7 @@ class SettingsNode extends StatefulWidget { class _SettingsNodeState extends State { bool _editable = false; + String? _nodeError; final TextEditingController _nodeController = TextEditingController(); @@ -103,9 +105,6 @@ class _SettingsNodeState extends State { (getChainIdentifier() == connectedNodeChainIdentifier) ? AppColors.znnColor : AppColors.errorColor)), - const SizedBox( - width: 8.0, - ), Visibility( visible: widget.node.contains('wss://'), child: const StandardTooltipIcon('Encrypted connection', Icons.lock), @@ -131,28 +130,49 @@ class _SettingsNodeState extends State { 'The Embedded Node validates all network transactions\n' 'It may take several hours to fully sync with the network', MaterialCommunityIcons.clock, + iconColor: Colors.amber, ), ), - const SizedBox( - width: 5.0, - ), Visibility( - visible: !kDefaultNodes.contains(widget.node), - child: MaterialIconButton( - iconData: Icons.edit, - onPressed: () { - setState(() { - _editable = true; - }); - }, - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + visible: kDefaultCommunityNodes.contains(widget.node), + child: const StandardTooltipIcon( + 'Community Node', + MaterialCommunityIcons.vector_link, + iconColor: Colors.amber, ), ), Visibility( - visible: !kDefaultNodes.contains(widget.node), - child: MaterialIconButton( - onPressed: () { - showDialogWithNoAndYesOptions( + visible: !kDefaultNodes.contains(widget.node) && + !kDefaultCommunityNodes.contains(widget.node), + child: IconButton( + hoverColor: Colors.transparent, + padding: const EdgeInsets.all(4.0), + constraints: const BoxConstraints(), + iconSize: 15.0, + icon: const Icon( + Icons.edit, + color: AppColors.znnColor, + ), + onPressed: () { + setState(() { + _editable = true; + }); + }, + tooltip: 'Edit node', + )), + Visibility( + visible: !kDefaultNodes.contains(widget.node) && + !kDefaultCommunityNodes.contains(widget.node), + child: IconButton( + hoverColor: Colors.transparent, + padding: const EdgeInsets.all(4.0), + constraints: const BoxConstraints(), + iconSize: 15.0, + icon: const Icon( + Icons.delete_forever, + color: AppColors.znnColor, + ), + onPressed: () => showDialogWithNoAndYesOptions( isBarrierDismissible: true, context: context, title: 'Node Management', @@ -162,12 +182,9 @@ class _SettingsNodeState extends State { onYesButtonPressed: () { _deleteNodeFromDb(widget.node); }, - ); - }, - iconData: Icons.delete_forever, - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - ), - ), + ), + tooltip: 'Delete node', + )), ], ); } @@ -180,50 +197,27 @@ class _SettingsNodeState extends State { children: [ Expanded( child: SizedBox( - height: 40.0, - child: InputField( - controller: _nodeController, - onSubmitted: (value) { - if (_nodeController.text != widget.node) { - _onChangeButtonPressed(); - } - }, - onChanged: (value) { - setState(() {}); - }, - inputtedTextStyle: - Theme.of(context).textTheme.bodyMedium!.copyWith( - color: AppColors.znnColor, - ), - enabledBorder: OutlineInputBorder( - borderSide: BorderSide.none, - borderRadius: BorderRadius.circular(10.0), - ), - contentLeftPadding: 5.0, - disabledBorder: OutlineInputBorder( - borderSide: BorderSide( - color: Colors.white.withOpacity(0.1), - ), - borderRadius: BorderRadius.circular(10.0), - ), - focusedBorder: OutlineInputBorder( - borderSide: const BorderSide(color: AppColors.znnColor), - borderRadius: BorderRadius.circular(10.0), - ), - errorBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(10.0), - borderSide: const BorderSide( - color: AppColors.errorColor, - width: 2.0, - ), - ), - focusedErrorBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(10.0), - borderSide: const BorderSide( - color: AppColors.errorColor, - width: 2.0, - ), - ), + child: Form( + key: widget.key, + autovalidateMode: AutovalidateMode.onUserInteraction, + child: InputField( + controller: _nodeController, + hintText: 'Node address with port', + onSubmitted: (value) { + if (_nodeController.text != widget.node && + _ifUserInputValid()) { + _onChangeButtonPressed(); + } + }, + onChanged: (String value) { + if (value.isNotEmpty) { + setState(() { + _nodeError = null; + }); + } + }, + validator: (value) => + InputValidators.node(value) ?? _nodeError), ), ), ), @@ -231,17 +225,20 @@ class _SettingsNodeState extends State { width: 15.0, ), SettingsButton( - onPressed: _nodeController.text != widget.node - ? _onChangeButtonPressed - : null, + onPressed: + _nodeController.text != widget.node && _ifUserInputValid() + ? _onChangeButtonPressed + : null, text: 'Change', key: _changeButtonKey, ), MaterialIconButton( + size: 15.0, onPressed: () { setState(() { _nodeController.text = widget.node; _editable = false; + _nodeError = null; }); }, iconData: Icons.clear, @@ -252,40 +249,47 @@ class _SettingsNodeState extends State { ); } + bool _ifUserInputValid() => + InputValidators.node(_nodeController.text) == null; + void _onChangeButtonPressed() async { try { _changeButtonKey.currentState!.showLoadingIndicator(true); if (_nodeController.text.isNotEmpty && _nodeController.text.length <= kAddressLabelMaxLength && - ![...kDefaultNodes, ...kDbNodes].contains(_nodeController.text)) { - Box nodesBox = await Hive.openBox(kNodesBox); - dynamic key = nodesBox.keys.firstWhere( + ![...kDefaultNodes, ...kDefaultCommunityNodes, ...kDbNodes] + .contains(_nodeController.text)) { + if (!Hive.isBoxOpen(kNodesBox)) { + await Hive.openBox(kNodesBox); + } + Box nodesBox = Hive.box(kNodesBox); + var nodeKey = nodesBox.keys.firstWhere( (key) => nodesBox.get(key) == widget.node, ); - await nodesBox.put(key, _nodeController.text); + await nodesBox.put(nodeKey, _nodeController.text); await NodeUtils.loadDbNodes(); setState(() { _editable = false; + _nodeError = null; }); + if (!mounted) return; + widget.onChangedOrDeletedNode(); } else if (_nodeController.text.isEmpty) { - NotificationUtils.sendNotificationError( - 'Node address can\'t be empty', - 'Node error', - ); + setState(() { + _nodeError = 'Node address can\'t be empty'; + }); } else if (_nodeController.text.length > kAddressLabelMaxLength) { - NotificationUtils.sendNotificationError( - 'The node ${_nodeController.text} is ${_nodeController.text.length} ' - 'characters long, which is more than the $kAddressLabelMaxLength limit.', - 'The node has more than $kAddressLabelMaxLength characters', - ); + setState(() { + _nodeError = + 'The node has more than $kAddressLabelMaxLength characters'; + }); } else { - NotificationUtils.sendNotificationError( - 'Node ${_nodeController.text} already exists in the database', - 'Node already exists', - ); + setState(() { + _nodeError = 'Node already exists'; + }); } } catch (e) { - NotificationUtils.sendNotificationError( + await NotificationUtils.sendNotificationError( e, 'Something went wrong', ); @@ -308,7 +312,7 @@ class _SettingsNodeState extends State { if (!mounted) return; widget.onChangedOrDeletedNode(); } catch (e) { - NotificationUtils.sendNotificationError( + await NotificationUtils.sendNotificationError( e, 'Error during deleting node $node from the database', ); diff --git a/lib/widgets/reusable_widgets/stepper_utils.dart b/lib/widgets/reusable_widgets/stepper_utils.dart index d3cecca6..928174b4 100644 --- a/lib/widgets/reusable_widgets/stepper_utils.dart +++ b/lib/widgets/reusable_widgets/stepper_utils.dart @@ -68,12 +68,15 @@ class StepperUtils { static Widget getBalanceWidget(Token token, AccountInfo accountInfo) { return Row( children: [ - Padding( - padding: const EdgeInsets.only(left: 20.0, top: 10.0, bottom: 10.0), - child: AvailableBalance( - token, - accountInfo, - ), + Expanded( + child: + Padding( + padding: const EdgeInsets.only(left: 20.0, top: 10.0, bottom: 10.0), + child: AvailableBalance( + token, + accountInfo, + ), + ), ), ], ); diff --git a/lib/widgets/tab_children_widgets/lock_tab_child.dart b/lib/widgets/tab_children_widgets/lock_tab_child.dart index b0c3d627..8f23c260 100644 --- a/lib/widgets/tab_children_widgets/lock_tab_child.dart +++ b/lib/widgets/tab_children_widgets/lock_tab_child.dart @@ -1,15 +1,19 @@ +import 'dart:convert'; + import 'package:flutter/material.dart'; import 'package:flutter_vector_icons/flutter_vector_icons.dart'; import 'package:zenon_syrius_wallet_flutter/main.dart'; import 'package:zenon_syrius_wallet_flutter/screens/screens.dart'; import 'package:zenon_syrius_wallet_flutter/utils/app_colors.dart'; import 'package:zenon_syrius_wallet_flutter/utils/constants.dart'; +import 'package:zenon_syrius_wallet_flutter/utils/extensions.dart'; import 'package:zenon_syrius_wallet_flutter/utils/global.dart'; import 'package:zenon_syrius_wallet_flutter/utils/init_utils.dart'; -import 'package:zenon_syrius_wallet_flutter/utils/keystore_utils.dart'; +import 'package:zenon_syrius_wallet_flutter/utils/wallet_utils.dart'; import 'package:zenon_syrius_wallet_flutter/utils/navigation_utils.dart'; import 'package:zenon_syrius_wallet_flutter/utils/notification_utils.dart'; import 'package:zenon_syrius_wallet_flutter/widgets/widgets.dart'; +import 'package:znn_ledger_dart/znn_ledger_dart.dart'; import 'package:znn_sdk_dart/znn_sdk_dart.dart'; class LockTabChild extends StatefulWidget { @@ -130,16 +134,16 @@ class _LockTabChildState extends State { ); } - void _onError(String errorMessage, Object error) { + Future _onError(String errorMessage, Object error) async { if (_messageToUser.isNotEmpty) { setState(() { _messageToUser = ''; }); } - NotificationUtils.sendNotificationError(error, errorMessage); + await NotificationUtils.sendNotificationError(error, errorMessage); if (error is IncorrectPasswordException) { kNumFailedUnlockAttempts = kNumFailedUnlockAttempts! + 1; - _saveNumFailedUnlockAttempts(kNumFailedUnlockAttempts); + await _saveNumFailedUnlockAttempts(kNumFailedUnlockAttempts); } if (kNumFailedUnlockAttempts == kAutoEraseWalletLimit) { NavigationUtils.pushReplacement( @@ -156,15 +160,16 @@ class _LockTabChildState extends State { _actionButtonKey.currentState!.btnState == ButtonState.idle) { try { _actionButtonKey.currentState!.animateForward(); - await KeyStoreUtils.decryptKeyStoreFile( - kKeyStorePath!, + kWalletFile = await WalletUtils.decryptWalletFile( + kWalletPath!, _passwordController.text, - ).then((keyStore) => kKeyStore = keyStore); + ); if (kWalletInitCompleted == false) { setState(() { _messageToUser = 'Initializing wallet, please wait'; }); - await InitUtils.initWalletAfterDecryption(); + await InitUtils.initWalletAfterDecryption( + Crypto.digest(utf8.encode(_passwordController.text))); widget.afterInitCallback(); } else { await widget.afterUnlockCallback(_passwordController.text); @@ -176,6 +181,8 @@ class _LockTabChildState extends State { kIncorrectPasswordNotificationTitle, IncorrectPasswordException(), ); + } on LedgerError catch (e) { + _onError('Ledger: ${e.toFriendlyString()}', e); } catch (e) { _onError(kUnlockFailedNotificationTitle, e); } finally { diff --git a/lib/widgets/tab_children_widgets/pillars_tab_child.dart b/lib/widgets/tab_children_widgets/pillars_tab_child.dart index e383791f..4e1b589d 100644 --- a/lib/widgets/tab_children_widgets/pillars_tab_child.dart +++ b/lib/widgets/tab_children_widgets/pillars_tab_child.dart @@ -60,7 +60,7 @@ class _PillarsTabChildState extends State { ), ), const FluidCell( - child: PillarsListWidget(), + child: PillarListWidget(), width: kStaggeredNumOfColumns, height: kStaggeredNumOfColumns / 2, ), diff --git a/lib/widgets/tab_children_widgets/sentinels_tab_child.dart b/lib/widgets/tab_children_widgets/sentinels_tab_child.dart index 3ba19c8c..018f2196 100644 --- a/lib/widgets/tab_children_widgets/sentinels_tab_child.dart +++ b/lib/widgets/tab_children_widgets/sentinels_tab_child.dart @@ -60,7 +60,7 @@ class _SentinelsTabChildState extends State { ), ), const FluidCell( - child: SentinelsListWidget(), + child: SentinelListWidget(), width: kStaggeredNumOfColumns, height: kStaggeredNumOfColumns / 2, ), diff --git a/lib/widgets/tab_children_widgets/settings_tab_child.dart b/lib/widgets/tab_children_widgets/settings_tab_child.dart index 88aee2de..2a67efd2 100644 --- a/lib/widgets/tab_children_widgets/settings_tab_child.dart +++ b/lib/widgets/tab_children_widgets/settings_tab_child.dart @@ -5,13 +5,11 @@ import 'package:zenon_syrius_wallet_flutter/widgets/widgets.dart'; class SettingsTabChild extends StatefulWidget { final VoidCallback _onChangeAutoLockTime; - final VoidCallback _onResyncWalletPressed; final VoidCallback onStepperNotificationSeeMorePressed; final VoidCallback onNodeChangedCallback; const SettingsTabChild( - this._onChangeAutoLockTime, - this._onResyncWalletPressed, { + this._onChangeAutoLockTime, { required this.onStepperNotificationSeeMorePressed, required this.onNodeChangedCallback, Key? key, @@ -47,6 +45,14 @@ class _SettingsTabChildState extends State { accountChainStatsBloc: _accountChainStatsBloc, ), ), + const FluidCell( + child: GeneralWidget(), + ), + FluidCell( + child: AccountChainStatsWidget( + accountChainStatsBloc: _accountChainStatsBloc, + ), + ), FluidCell( width: context.layout.value( xl: kStaggeredNumOfColumns ~/ 2, @@ -55,31 +61,47 @@ class _SettingsTabChildState extends State { sm: kStaggeredNumOfColumns, xs: kStaggeredNumOfColumns, ), - child: const PeersWidget(), - ), - const FluidCell( - child: GeneralWidget(), - height: kStaggeredNumOfColumns / 3, - ), - FluidCell( - child: AccountChainStatsWidget( - accountChainStatsBloc: _accountChainStatsBloc, + child: NodeManagement( + onNodeChangedCallback: widget.onNodeChangedCallback, ), - height: kStaggeredNumOfColumns / 3, + height: kStaggeredNumOfColumns / 4, ), FluidCell( + width: context.layout.value( + xl: kStaggeredNumOfColumns ~/ 6, + lg: kStaggeredNumOfColumns ~/ 6, + md: kStaggeredNumOfColumns ~/ 6, + sm: kStaggeredNumOfColumns, + xs: kStaggeredNumOfColumns, + ), child: SecurityWidget( widget._onChangeAutoLockTime, onStepperNotificationSeeMorePressed: widget.onStepperNotificationSeeMorePressed, ), - height: kStaggeredNumOfColumns / 3, + height: kStaggeredNumOfColumns / 2 ), FluidCell( - child: WalletOptions( - widget._onResyncWalletPressed, + width: context.layout.value( + xl: kStaggeredNumOfColumns ~/ 6, + lg: kStaggeredNumOfColumns ~/ 6, + md: kStaggeredNumOfColumns ~/ 6, + sm: kStaggeredNumOfColumns, + xs: kStaggeredNumOfColumns, ), - height: kStaggeredNumOfColumns / 3, + child: const DisplayWidget(), + height: kStaggeredNumOfColumns / 2 + ), + FluidCell( + child: const WalletOptions(), + width: context.layout.value( + xl: kStaggeredNumOfColumns ~/ 6, + lg: kStaggeredNumOfColumns ~/ 6, + md: kStaggeredNumOfColumns ~/ 6, + sm: kStaggeredNumOfColumns, + xs: kStaggeredNumOfColumns, + ), + height: kStaggeredNumOfColumns / 4, ), FluidCell( width: context.layout.value( @@ -89,18 +111,18 @@ class _SettingsTabChildState extends State { sm: kStaggeredNumOfColumns, xs: kStaggeredNumOfColumns, ), - child: NodeManagement( - onNodeChangedCallback: widget.onNodeChangedCallback, - ), - height: kStaggeredNumOfColumns / 6, - ), - const FluidCell( - child: DisplayWidget(), - height: kStaggeredNumOfColumns / 6, + child: const PeersWidget(), ), - const FluidCell( - child: BackupWidget(), - height: kStaggeredNumOfColumns / 6, + FluidCell( + child: const BackupWidget(), + width: context.layout.value( + xl: kStaggeredNumOfColumns ~/ 6, + lg: kStaggeredNumOfColumns ~/ 6, + md: kStaggeredNumOfColumns ~/ 6, + sm: kStaggeredNumOfColumns, + xs: kStaggeredNumOfColumns, + ), + height: kStaggeredNumOfColumns / 4, ), ], ); diff --git a/lib/widgets/tab_children_widgets/transfer_tab_child.dart b/lib/widgets/tab_children_widgets/transfer_tab_child.dart index f16c61b9..5e7ad643 100644 --- a/lib/widgets/tab_children_widgets/transfer_tab_child.dart +++ b/lib/widgets/tab_children_widgets/transfer_tab_child.dart @@ -26,9 +26,14 @@ class _TransferTabChildState extends State { _getSendCard(), _getReceiveCard(), const FluidCell( - width: kStaggeredNumOfColumns, child: LatestTransactions(), - height: kStaggeredNumOfColumns / 2, + width: kStaggeredNumOfColumns ~/ 2, + height: kStaggeredNumOfColumns / 3, + ), + const FluidCell( + child: PendingTransactions(), + width: kStaggeredNumOfColumns ~/ 2, + height: kStaggeredNumOfColumns / 3, ), ], ); diff --git a/lib/widgets/tab_children_widgets/wallet_connect_tab_child.dart b/lib/widgets/tab_children_widgets/wallet_connect_tab_child.dart index c99d8f31..f82a8584 100644 --- a/lib/widgets/tab_children_widgets/wallet_connect_tab_child.dart +++ b/lib/widgets/tab_children_widgets/wallet_connect_tab_child.dart @@ -41,6 +41,7 @@ class WalletConnectTabChild extends StatelessWidget { ), FluidCell( width: context.layout.value( + xl: kStaggeredNumOfColumns ~/ 2, xs: kStaggeredNumOfColumns, ), height: kStaggeredNumOfColumns / 4, @@ -48,6 +49,7 @@ class WalletConnectTabChild extends StatelessWidget { ), FluidCell( width: context.layout.value( + xl: kStaggeredNumOfColumns ~/ 2, xs: kStaggeredNumOfColumns, ), height: kStaggeredNumOfColumns / 4, diff --git a/linux/CMakeLists.txt b/linux/CMakeLists.txt index 16b029a0..2c99e53e 100644 --- a/linux/CMakeLists.txt +++ b/linux/CMakeLists.txt @@ -157,10 +157,27 @@ else() string(STRIP "${ZNN_SDK_DART_PATH}" ZNN_SDK_DART_PATH) endif() +file(STRINGS "${SYRIUS_PROJECT_DIRECTORY}/.dart_tool/package_config.json" + ZNN_LEDGER_DART_PATH REGEX "(file:).*(znn_ledger_dart).*\\/\"" ) + +if (ZNN_LEDGER_DART_PATH STREQUAL "") + file(STRINGS "${SYRIUS_PROJECT_DIRECTORY}/.dart_tool/package_config.json" + ZNN_LEDGER_DART_PATH REGEX "(rootUri).*(znn_ledger_dart).*\"" ) + string(REPLACE "\"rootUri\": \"" "" ZNN_LEDGER_DART_PATH "${ZNN_LEDGER_DART_PATH}") + string(REPLACE "/\"," "" ZNN_LEDGER_DART_PATH "${ZNN_LEDGER_DART_PATH}") + string(REPLACE "\"," "" ZNN_LEDGER_DART_PATH "${ZNN_LEDGER_DART_PATH}") + string(STRIP "${ZNN_LEDGER_DART_PATH}" ZNN_LEDGER_DART_PATH) +else() + string(REPLACE "\"rootUri\": \"file://" "" ZNN_LEDGER_DART_PATH "${ZNN_LEDGER_DART_PATH}") + string(REPLACE "/\"," "" ZNN_LEDGER_DART_PATH "${ZNN_LEDGER_DART_PATH}") + string(STRIP "${ZNN_LEDGER_DART_PATH}" ZNN_LEDGER_DART_PATH) +endif() + list(APPEND SYRIUS_LIBRARIES "${SYRIUS_PROJECT_DIRECTORY}/lib/embedded_node/blobs/libznn.so" "${ZNN_SDK_DART_PATH}/lib/src/argon2/blobs/libargon2_ffi_plugin.so" "${ZNN_SDK_DART_PATH}/lib/src/pow/blobs/libpow_links.so" + "${ZNN_LEDGER_DART_PATH}/lib/src/ledger/blobs/libledger_ffi.so" ) foreach(znn_library ${SYRIUS_LIBRARIES}) diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj index 42db3b89..5859d9af 100644 --- a/macos/Runner.xcodeproj/project.pbxproj +++ b/macos/Runner.xcodeproj/project.pbxproj @@ -31,6 +31,7 @@ 9631FB752956060A007ED711 /* ../lib/embedded_node/blobs/libznn.dylib in Resources */ = {isa = PBXBuildFile; fileRef = 04CC766027C81F7E00DBC357 /* ../lib/embedded_node/blobs/libznn.dylib */; }; 9631FB86295610FB007ED711 /* libargon2_ffi.dylib in Resources */ = {isa = PBXBuildFile; fileRef = 9631FB7A29560ECB007ED711 /* libargon2_ffi.dylib */; }; 9631FB87295610FF007ED711 /* libpow_links.dylib in Resources */ = {isa = PBXBuildFile; fileRef = 9631FB8229560F53007ED711 /* libpow_links.dylib */; }; + 9631FB88295610FF007ED711 /* libledger_ffi.dylib in Resources */ = {isa = PBXBuildFile; fileRef = 9631FB8A29561054007ED711 /* libledger_ffi.dylib */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -77,6 +78,7 @@ 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; 9631FB7A29560ECB007ED711 /* libargon2_ffi.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; path = libargon2_ffi.dylib; sourceTree = ""; }; 9631FB8229560F53007ED711 /* libpow_links.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; path = libpow_links.dylib; sourceTree = ""; }; + 9631FB8A29561054007ED711 /* libledger_ffi.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; path = libledger_ffi.dylib; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; DDCA1E2CBCCC96B9436CF502 /* 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 = ""; }; E5DFBCCB2F3B416824983EAB /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -129,6 +131,7 @@ children = ( 9631FB7A29560ECB007ED711 /* libargon2_ffi.dylib */, 9631FB8229560F53007ED711 /* libpow_links.dylib */, + 9631FB8A29561054007ED711 /* libledger_ffi.dylib */, 04CC766027C81F7E00DBC357 /* ../lib/embedded_node/blobs/libznn.dylib */, 33CC10F22044A3C60003C045 /* Assets.xcassets */, 33CC10F42044A3C60003C045 /* MainMenu.xib */, @@ -189,6 +192,7 @@ buildPhases = ( 26152DF600C95C6136D5E940 /* [CP] Check Pods Manifest.lock */, 96DA24F0296EB70E00545E88 /* ShellScript */, + 96DA2500296EB70F00545E88 /* ShellScript */, 33CC10E92044A3C60003C045 /* Sources */, 33CC10EA2044A3C60003C045 /* Frameworks */, 33CC10EB2044A3C60003C045 /* Resources */, @@ -258,6 +262,7 @@ files = ( 9631FB87295610FF007ED711 /* libpow_links.dylib in Resources */, 9631FB86295610FB007ED711 /* libargon2_ffi.dylib in Resources */, + 9631FB88295610FF007ED711 /* libledger_ffi.dylib in Resources */, 9631FB752956060A007ED711 /* ../lib/embedded_node/blobs/libznn.dylib in Resources */, 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, @@ -379,6 +384,23 @@ shellPath = /bin/sh; shellScript = "OUTPUT_DIRECTORY=\"./\"\n\nPACKAGE_CONFIG=\"../.dart_tool/package_config.json\"\nZNN_SDK_DART_PATH=`cat $PACKAGE_CONFIG | grep git/znn_sdk_dart | sed 's/\"rootUri\": \"file:\\/\\///' | sed 's/\\/\",//' | xargs` \n\nSYRIUS_LIBRARIES=(\n \"$ZNN_SDK_DART_PATH/lib/src/argon2/blobs/libargon2_ffi.dylib\"\n \"$ZNN_SDK_DART_PATH/lib/src/pow/blobs/libpow_links.dylib\"\n)\n\necho \"$OUTPUT_DIRECTORY\"\n\nfor znn_library in ${SYRIUS_LIBRARIES[@]}; do\n echo \"Copy ZNN library: $znn_library\"\n cp $znn_library \"$OUTPUT_DIRECTORY\"\ndone\n"; }; + 96DA2500296EB70F00545E88 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 12; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "OUTPUT_DIRECTORY=\"./\"\n\nPACKAGE_CONFIG=\"../.dart_tool/package_config.json\"\nZNN_LEDGER_DART_PATH=`cat $PACKAGE_CONFIG | grep git/znn_ledger_dart | sed 's/\"rootUri\": \"file:\\/\\///' | sed 's/\\/\",//' | xargs` \n\nSYRIUS_LIBRARIES=(\n \"$ZNN_LEDGER_DART_PATH/lib/src/ledger/blobs/libledger_ffi.dylib\"\n)\n\necho \"$OUTPUT_DIRECTORY\"\n\nfor znn_library in ${SYRIUS_LIBRARIES[@]}; do\n echo \"Copy ZNN library: $znn_library\"\n cp $znn_library \"$OUTPUT_DIRECTORY\"\ndone\n"; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ diff --git a/macos/Runner/DebugProfile.entitlements b/macos/Runner/DebugProfile.entitlements index da56afa9..2fce61da 100644 --- a/macos/Runner/DebugProfile.entitlements +++ b/macos/Runner/DebugProfile.entitlements @@ -10,5 +10,7 @@ com.apple.security.network.server + com.apple.security.device.camera + diff --git a/macos/Runner/Release.entitlements b/macos/Runner/Release.entitlements index da56afa9..2fce61da 100644 --- a/macos/Runner/Release.entitlements +++ b/macos/Runner/Release.entitlements @@ -10,5 +10,7 @@ com.apple.security.network.server + com.apple.security.device.camera + diff --git a/pubspec.lock b/pubspec.lock index b9f0c1c6..af64c854 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,10 +5,10 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: ae92f5d747aee634b87f89d9946000c2de774be1d6ac3e58268224348cd0101a + sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7" url: "https://pub.dev" source: hosted - version: "61.0.0" + version: "67.0.0" adaptive_number: dependency: transitive description: @@ -21,34 +21,34 @@ packages: dependency: "direct main" description: name: ai_barcode_scanner - sha256: e77a90d9a953ba8d797cf4ff9f705c1842133fffd596f7ccc845dd08650e0543 + sha256: "1d6ce239d84a40c0d586891291eee9b958c748f7de3de75daaa7be79ee511dee" url: "https://pub.dev" source: hosted - version: "0.0.7" + version: "3.4.3" analyzer: dependency: transitive description: name: analyzer - sha256: ea3d8652bda62982addfd92fdc2d0214e5f82e43325104990d4f4c4a2a313562 + sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d" url: "https://pub.dev" source: hosted - version: "5.13.0" + version: "6.4.1" app_links: dependency: "direct main" description: name: app_links - sha256: "16725e716afd0634a5441654b1dda2b6c5557aa230884b5e1f41a5aa546a4cb6" + sha256: "3ced568a5d9e309e99af71285666f1f3117bddd0bd5b3317979dccc1a40cada4" url: "https://pub.dev" source: hosted - version: "3.4.3" + version: "3.5.1" archive: dependency: transitive description: name: archive - sha256: "0c8368c9b3f0abbc193b9d6133649a614204b528982bebc7026372d61677ce3a" + sha256: "22600aa1e926be775fa5fe7e6894e7fb3df9efda8891c73f70fb3262399a432d" url: "https://pub.dev" source: hosted - version: "3.3.7" + version: "3.4.10" argon2_ffi_base: dependency: transitive description: @@ -85,10 +85,10 @@ packages: dependency: transitive description: name: base_x - sha256: "3f1043679659f1759c651f900da6f24f0a8062c28daa6f9625e8d580002e187b" + sha256: "519abcdafd637d4b6bd7e72fabd8f9264935f804b9b9f6c5d8411c7d52cbf8fd" url: "https://pub.dev" source: hosted - version: "2.0.0" + version: "2.0.1" bech32: dependency: transitive description: @@ -165,34 +165,34 @@ packages: dependency: transitive description: name: build_daemon - sha256: "5f02d73eb2ba16483e693f80bee4f088563a820e47d1027d4cdfe62b5bb43e65" + sha256: "0343061a33da9c5810b2d6cee51945127d8f4c060b7fbdd9d54917f0a3feaaa1" url: "https://pub.dev" source: hosted - version: "4.0.0" + version: "4.0.1" build_resolvers: dependency: transitive description: name: build_resolvers - sha256: "6c4dd11d05d056e76320b828a1db0fc01ccd376922526f8e9d6c796a5adbac20" + sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a" url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.4.2" build_runner: dependency: "direct dev" description: name: build_runner - sha256: "10c6bcdbf9d049a0b666702cf1cee4ddfdc38f02a19d35ae392863b47519848b" + sha256: "581bacf68f89ec8792f5e5a0b2c4decd1c948e97ce659dc783688c8a88fbec21" url: "https://pub.dev" source: hosted - version: "2.4.6" + version: "2.4.8" build_runner_core: dependency: transitive description: name: build_runner_core - sha256: "6d6ee4276b1c5f34f21fdf39425202712d2be82019983d52f351c94aafbc2c41" + sha256: "4ae8ffe5ac758da294ecf1802f2aff01558d8b1b00616aa7538ea9a8a5d50799" url: "https://pub.dev" source: hosted - version: "7.2.10" + version: "7.3.0" built_collection: dependency: transitive description: @@ -205,10 +205,10 @@ packages: dependency: transitive description: name: built_value - sha256: "598a2a682e2a7a90f08ba39c0aaa9374c5112340f0a2e275f61b59389543d166" + sha256: fedde275e0a6b798c3296963c5cd224e3e1b55d0e478d5b7e65e6b540f363a0e url: "https://pub.dev" source: hosted - version: "8.6.1" + version: "8.9.1" characters: dependency: transitive description: @@ -237,10 +237,10 @@ packages: dependency: "direct main" description: name: clipboard_watcher - sha256: "2fa9c728f46962668dbc1c07870695c9163524f8dbea25dc176277d1ef1cc2bd" + sha256: a6c98d9a30c47d1c5655ed48c46f554b04545da018f28992fb2540d1d14d863a url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.2.1" clock: dependency: transitive description: @@ -253,18 +253,18 @@ packages: dependency: transitive description: name: code_builder - sha256: "4ad01d6e56db961d29661561effde45e519939fdaeb46c351275b182eac70189" + sha256: f692079e25e7869c14132d39f223f8eec9830eb76131925143b2129c4bb01b37 url: "https://pub.dev" source: hosted - version: "4.5.0" + version: "4.10.0" collection: dependency: "direct main" description: name: collection - sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a url: "https://pub.dev" source: hosted - version: "1.17.2" + version: "1.18.0" convert: dependency: transitive description: @@ -277,10 +277,10 @@ packages: dependency: transitive description: name: cross_file - sha256: "0b0036e8cccbfbe0555fd83c1d31a6f30b77a96b598b35a5d36dd41f718695e9" + sha256: "55d7b444feb71301ef6b8838dbc1ae02e63dd48c8773f3810ff53bb1e2945b32" url: "https://pub.dev" source: hosted - version: "0.3.3+4" + version: "0.3.4+1" crypto: dependency: transitive description: @@ -293,50 +293,50 @@ packages: dependency: transitive description: name: cryptography - sha256: df156c5109286340817d21fa7b62f9140f17915077127dd70f8bd7a2a0997a35 + sha256: d146b76d33d94548cf035233fbc2f4338c1242fa119013bead807d033fc4ae05 url: "https://pub.dev" source: hosted - version: "2.5.0" + version: "2.7.0" dart_style: dependency: transitive description: name: dart_style - sha256: "1efa911ca7086affd35f463ca2fc1799584fb6aa89883cf0af8e3664d6a02d55" + sha256: "99e066ce75c89d6b29903d788a7bb9369cf754f7b24bf70bf4b6d6d6b26853b9" url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.3.6" dbus: dependency: transitive description: name: dbus - sha256: "6f07cba3f7b3448d42d015bfd3d53fe12e5b36da2423f23838efc1d5fb31a263" + sha256: "365c771ac3b0e58845f39ec6deebc76e3276aa9922b0cc60840712094d9047ac" url: "https://pub.dev" source: hosted - version: "0.7.8" + version: "0.7.10" dependency_validator: dependency: "direct dev" description: name: dependency_validator - sha256: "08349175533ed0bd06eb9b6043cde66c45b2bfc7ebc222a7542cdb1324f1bf03" + sha256: f727a5627aa405965fab4aef4f468e50a9b632ba0737fd2f98c932fec6d712b9 url: "https://pub.dev" source: hosted - version: "3.2.2" + version: "3.2.3" desktop_drop: dependency: "direct main" description: name: desktop_drop - sha256: "4ca4d960f4b11c032e9adfd2a0a8ac615bc3fddb4cbe73dcf840dd8077582186" + sha256: d55a010fe46c8e8fcff4ea4b451a9ff84a162217bdb3b2a0aa1479776205e15d url: "https://pub.dev" source: hosted - version: "0.4.1" + version: "0.4.4" device_info_plus: dependency: "direct main" description: name: device_info_plus - sha256: f52ab3b76b36ede4d135aab80194df8925b553686f0fa12226b4e2d658e45903 + sha256: "77f757b789ff68e4eaf9c56d1752309bd9f7ad557cb105b938a7f8eb89e59110" url: "https://pub.dev" source: hosted - version: "8.2.2" + version: "9.1.2" device_info_plus_platform_interface: dependency: transitive description: @@ -349,10 +349,10 @@ packages: dependency: "direct main" description: name: dotted_border - sha256: "07a5c5e8d4e6e992279e190e0352be8faa5b8f96d81c77a78b2d42f060279840" + sha256: "108837e11848ca776c53b30bc870086f84b62ed6e01c503ed976e8f8c7df9c04" url: "https://pub.dev" source: hosted - version: "2.0.0+3" + version: "2.1.0" ed25519_edwards: dependency: transitive description: @@ -405,82 +405,82 @@ packages: dependency: "direct main" description: name: ffi - sha256: ed5337a5660c506388a9f012be0288fb38b49020ce2b45fe1f8b8323fe429f99 + sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21" url: "https://pub.dev" source: hosted - version: "2.0.2" + version: "2.1.2" file: dependency: transitive description: name: file - sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d" + sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" url: "https://pub.dev" source: hosted - version: "6.1.4" + version: "7.0.0" file_selector: dependency: "direct main" description: name: file_selector - sha256: "1d2fde93dddf634a9c3c0faa748169d7ac0d83757135555707e52f02c017ad4f" + sha256: "5019692b593455127794d5718304ff1ae15447dea286cdda9f0db2a796a1b828" url: "https://pub.dev" source: hosted - version: "0.9.5" + version: "1.0.3" file_selector_android: dependency: transitive description: name: file_selector_android - sha256: "43e5c719f671b9181bef1bf2851135c3ad993a9a6c804a4ccb07579cfee84e34" + sha256: "1cd66575f063b689e041aec836905ba7be18d76c9f0634d0d75daec825f67095" url: "https://pub.dev" source: hosted - version: "0.5.0+2" + version: "0.5.0+7" file_selector_ios: dependency: transitive description: name: file_selector_ios - sha256: "54542b6b35e3ced6246df5fae13cf0b879d14669d0fdff1a53a098f16e23328b" + sha256: b015154e6d9fddbc4d08916794df170b44531798c8dd709a026df162d07ad81d url: "https://pub.dev" source: hosted - version: "0.5.1+4" + version: "0.5.1+8" file_selector_linux: dependency: transitive description: name: file_selector_linux - sha256: "770eb1ab057b5ae4326d1c24cc57710758b9a46026349d021d6311bd27580046" + sha256: "045d372bf19b02aeb69cacf8b4009555fb5f6f0b7ad8016e5f46dd1387ddd492" url: "https://pub.dev" source: hosted - version: "0.9.2" + version: "0.9.2+1" file_selector_macos: dependency: transitive description: name: file_selector_macos - sha256: "4ada532862917bf16e3adb3891fe3a5917a58bae03293e497082203a80909412" + sha256: b15c3da8bd4908b9918111fa486903f5808e388b8d1c559949f584725a6594d6 url: "https://pub.dev" source: hosted - version: "0.9.3+1" + version: "0.9.3+3" file_selector_platform_interface: dependency: transitive description: name: file_selector_platform_interface - sha256: "412705a646a0ae90f33f37acfae6a0f7cbc02222d6cd34e479421c3e74d3853c" + sha256: a3994c26f10378a039faa11de174d7b78eb8f79e4dd0af2a451410c1a5c3f66b url: "https://pub.dev" source: hosted - version: "2.6.0" + version: "2.6.2" file_selector_web: dependency: transitive description: name: file_selector_web - sha256: e292740c469df0aeeaba0895bf622bea351a05e87d22864c826bf21c4780e1d7 + sha256: "619e431b224711a3869e30dbd7d516f5f5a4f04b265013a50912f39e1abc88c8" url: "https://pub.dev" source: hosted - version: "0.9.2" + version: "0.9.4+1" file_selector_windows: dependency: transitive description: name: file_selector_windows - sha256: "1372760c6b389842b77156203308940558a2817360154084368608413835fc26" + sha256: d3547240c20cabf205c7c7f01a50ecdbc413755814d6677f3cb366f04abcead0 url: "https://pub.dev" source: hosted - version: "0.9.3" + version: "0.9.3+1" fixnum: dependency: transitive description: @@ -493,10 +493,10 @@ packages: dependency: "direct main" description: name: fl_chart - sha256: "48a1b69be9544e2b03d9a8e843affd89e43f3194c9248776222efcb4206bb1ec" + sha256: c1e26c7e48496be85104c16c040950b0436674cdf0737f3f6e95511b2529b592 url: "https://pub.dev" source: hosted - version: "0.62.0" + version: "0.63.0" flip_card: dependency: "direct main" description: @@ -510,14 +510,22 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_flip_card: + dependency: "direct main" + description: + name: flutter_flip_card + sha256: "1de81921a3bcc40f2eae77db18bddc0ce6bbea6be30274ffa83e8c06fe2b1936" + url: "https://pub.dev" + source: hosted + version: "0.0.5" flutter_lints: dependency: "direct dev" description: name: flutter_lints - sha256: "2118df84ef0c3ca93f96123a616ae8540879991b8b57af2f81b76a7ada49b2a4" + sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04 url: "https://pub.dev" source: hosted - version: "2.0.2" + version: "2.0.3" flutter_staggered_grid_view: dependency: "direct main" description: @@ -530,10 +538,10 @@ packages: dependency: "direct main" description: name: flutter_svg - sha256: "8c5d68a82add3ca76d792f058b186a0599414f279f00ece4830b9b231b570338" + sha256: "7b4ca6cf3304575fe9c8ec64813c8d02ee41d2afe60bcfe0678bcb5375d596a2" url: "https://pub.dev" source: hosted - version: "2.0.7" + version: "2.0.10+1" flutter_vector_icons: dependency: "direct main" description: @@ -567,18 +575,18 @@ packages: dependency: transitive description: name: gap - sha256: db02ec4ac4511ea8d324d7f671d526959a8e7857468b4ea64113fe8a82f16a2c + sha256: f19387d4e32f849394758b91377f9153a1b41d79513ef7668c088c77dbc6955d url: "https://pub.dev" source: hosted - version: "2.0.2" + version: "3.0.1" get_it: dependency: "direct main" description: name: get_it - sha256: "529de303c739fca98cd7ece5fca500d8ff89649f1bb4b4e94fb20954abcd7468" + sha256: e6017ce7fdeaf218dc51a100344d8cb70134b80e28b760f8bb23c242437bafd7 url: "https://pub.dev" source: hosted - version: "7.6.0" + version: "7.6.7" glob: dependency: transitive description: @@ -595,6 +603,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.1" + gtk: + dependency: transitive + description: + name: gtk + sha256: e8ce9ca4b1df106e4d72dad201d345ea1a036cc12c360f1a7d5a758f78ffa42c + url: "https://pub.dev" + source: hosted + version: "2.1.0" hex: dependency: "direct main" description: @@ -615,18 +631,18 @@ packages: dependency: "direct dev" description: name: hive_generator - sha256: "65998cc4d2cd9680a3d9709d893d2f6bb15e6c1f92626c3f1fa650b4b3281521" + sha256: "06cb8f58ace74de61f63500564931f9505368f45f98958bd7a6c35ba24159db4" url: "https://pub.dev" source: hosted - version: "2.0.0" + version: "2.0.1" http: dependency: transitive description: name: http - sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525" + sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938" url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.2.1" http_multi_server: dependency: transitive description: @@ -647,10 +663,10 @@ packages: dependency: "direct main" description: name: image - sha256: a72242c9a0ffb65d03de1b7113bc4e189686fc07c7147b8b41811d0dd0e0d9bf + sha256: "4c68bfd5ae83e700b5204c1e74451e7bf3cf750e6843c6e158289cf56bda018e" url: "https://pub.dev" source: hosted - version: "4.0.17" + version: "4.1.7" infinite_scroll_pagination: dependency: "direct main" description: @@ -711,10 +727,10 @@ packages: dependency: "direct main" description: name: layout - sha256: e3674626233dfa4669f73175dd920e0379f84412c50e03af53dde13d2c7a91c4 + sha256: c282b5eac1deafc384ba0f0bbf2acc815e397ef131c29eb93f0b52f365db86c9 url: "https://pub.dev" source: hosted - version: "1.0.3" + version: "1.0.5" lints: dependency: transitive description: @@ -751,10 +767,10 @@ packages: dependency: "direct main" description: name: lottie - sha256: "0793a5866062e5cc8a8b24892fa94c3095953ea914a7fdf790f550dd7537fe60" + sha256: a93542cc2d60a7057255405f62252533f8e8956e7e06754955669fd32fb4b216 url: "https://pub.dev" source: hosted - version: "2.5.0" + version: "2.7.0" marquee_widget: dependency: "direct main" description: @@ -767,18 +783,18 @@ packages: dependency: transitive description: name: matcher - sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb url: "https://pub.dev" source: hosted - version: "0.12.15" + version: "0.12.16+1" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.8.0" menu_base: dependency: transitive description: @@ -791,26 +807,34 @@ packages: dependency: transitive description: name: meta - sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" + sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.11.0" mime: dependency: transitive description: name: mime - sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e + sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.0.5" mobile_scanner: dependency: transitive description: name: mobile_scanner - sha256: "54dd914e1bb5758b3db7aa02e56d65d80285ba0705e0e5fa5cfbb11e27344c4b" + sha256: "827765afbd4792ff3fd105ad593821ac0f6d8a7d352689013b07ee85be336312" url: "https://pub.dev" source: hosted - version: "3.3.0" + version: "4.0.1" + mutex: + dependency: "direct main" + description: + name: mutex + sha256: "8827da25de792088eb33e572115a5eb0d61d61a3c01acbc8bcbe76ed78f1a1f2" + url: "https://pub.dev" + source: hosted + version: "3.1.0" nested: dependency: transitive description: @@ -827,30 +851,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.7" - ocr_engine_builtin: - dependency: transitive - description: - name: ocr_engine_builtin - sha256: ced1acd5cd3dee2ea63e8dd2b77fac63deebf3ee5b4654e26b87314b917eebbb - url: "https://pub.dev" - source: hosted - version: "0.1.1" - ocr_engine_youdao: - dependency: transitive - description: - name: ocr_engine_youdao - sha256: f5c5eee137c4e816f53ea49323e2db389733da9b2a8c0ee70f1525057e05239d - url: "https://pub.dev" - source: hosted - version: "0.1.1" open_filex: dependency: "direct main" description: name: open_filex - sha256: "854aefd72dfd74219dc8c8d1767c34ec1eae64b8399a5be317bddb1ec2108915" + sha256: "74e2280754cf8161e860746c3181db2c996d6c1909c7057b738ede4a469816b8" url: "https://pub.dev" source: hosted - version: "4.3.2" + version: "4.4.0" overlay_support: dependency: "direct main" description: @@ -871,10 +879,10 @@ packages: dependency: "direct main" description: name: package_info_plus - sha256: ceb027f6bc6a60674a233b4a90a7658af1aebdea833da0b5b53c1e9821a78c7b + sha256: "7e76fad405b3e4016cd39d08f455a4eb5199723cf594cd1b8916d47140d93017" url: "https://pub.dev" source: hosted - version: "4.0.2" + version: "4.2.0" package_info_plus_platform_interface: dependency: transitive description: @@ -887,18 +895,18 @@ packages: dependency: "direct main" description: name: page_transition - sha256: a7694bc120b7064a7f57c336914bb8885acf4f70bb3772c30c2fcfe6a85e43ff + sha256: dee976b1f23de9bbef5cd512fe567e9f6278caee11f5eaca9a2115c19dc49ef6 url: "https://pub.dev" source: hosted - version: "2.0.9" + version: "2.1.0" path: dependency: "direct main" description: name: path - sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" url: "https://pub.dev" source: hosted - version: "1.8.3" + version: "1.9.0" path_drawing: dependency: transitive description: @@ -919,82 +927,82 @@ packages: dependency: "direct main" description: name: path_provider - sha256: "3087813781ab814e4157b172f1a11c46be20179fcc9bea043e0fba36bc0acaa2" + sha256: b27217933eeeba8ff24845c34003b003b2b22151de3c908d0e679e8fe1aa078b url: "https://pub.dev" source: hosted - version: "2.0.15" + version: "2.1.2" path_provider_android: dependency: transitive description: name: path_provider_android - sha256: "2cec049d282c7f13c594b4a73976b0b4f2d7a1838a6dd5aaf7bd9719196bee86" + sha256: "477184d672607c0a3bf68fbbf601805f92ef79c82b64b4d6eb318cbca4c48668" url: "https://pub.dev" source: hosted - version: "2.0.27" + version: "2.2.2" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: "916731ccbdce44d545414dd9961f26ba5fbaa74bcbb55237d8e65a623a8c7297" + sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f" url: "https://pub.dev" source: hosted - version: "2.2.4" + version: "2.3.2" path_provider_linux: dependency: transitive description: name: path_provider_linux - sha256: ffbb8cc9ed2c9ec0e4b7a541e56fd79b138e8f47d2fb86815f15358a349b3b57 + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 url: "https://pub.dev" source: hosted - version: "2.1.11" + version: "2.2.1" path_provider_platform_interface: dependency: transitive description: name: path_provider_platform_interface - sha256: "57585299a729335f1298b43245842678cb9f43a6310351b18fb577d6e33165ec" + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" url: "https://pub.dev" source: hosted - version: "2.0.6" + version: "2.1.2" path_provider_windows: dependency: transitive description: name: path_provider_windows - sha256: "1cb68ba4cd3a795033de62ba1b7b4564dace301f952de6bfb3cd91b202b6ee96" + sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170" url: "https://pub.dev" source: hosted - version: "2.1.7" + version: "2.2.1" petitparser: dependency: transitive description: name: petitparser - sha256: cb3798bef7fc021ac45b308f4b51208a152792445cce0448c9a4ba5879dd8750 + sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27 url: "https://pub.dev" source: hosted - version: "5.4.0" + version: "6.0.2" platform: dependency: transitive description: name: platform - sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76" + sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec" url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.1.4" plugin_platform_interface: dependency: transitive description: name: plugin_platform_interface - sha256: "43798d895c929056255600343db8f049921cbec94d31ec87f1dc5c16c01935dd" + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" url: "https://pub.dev" source: hosted - version: "2.1.5" + version: "2.1.8" pointycastle: dependency: transitive description: name: pointycastle - sha256: "7c1e5f0d23c9016c5bbd8b1473d0d3fb3fc851b876046039509e18e0c7485f2c" + sha256: "43ac87de6e10afabc85c445745a7b799e04de84cebaa4fd7bf55a5e1e9604d29" url: "https://pub.dev" source: hosted - version: "3.7.3" + version: "3.7.4" pool: dependency: transitive description: @@ -1015,18 +1023,18 @@ packages: dependency: "direct main" description: name: pretty_qr_code - sha256: ea7ccb3069e0f5b89b441449b9ec10f4148ddda7a4bef89a130d2ebdaa0be647 + sha256: cbdb4af29da1c1fa21dd76f809646c591320ab9e435d3b0eab867492d43607d5 url: "https://pub.dev" source: hosted - version: "2.0.3" + version: "3.3.0" provider: dependency: "direct main" description: name: provider - sha256: cdbe7530b12ecd9eb455bdaa2fcb8d4dad22e80b8afb4798b41479d5ce26847f + sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c url: "https://pub.dev" source: hosted - version: "6.0.5" + version: "6.1.2" pub_semver: dependency: transitive description: @@ -1063,26 +1071,26 @@ packages: dependency: "direct main" description: name: screen_capturer - sha256: "39001271f34eca330a1a59b735da0a0f546fc6f9f38b68554d6d2478baeb4025" + sha256: ab818709c948bb83288dcf257762ec59c8e10c5929da8718220b3cf00a742de0 url: "https://pub.dev" source: hosted - version: "0.1.6" + version: "0.1.7" screen_retriever: dependency: transitive description: name: screen_retriever - sha256: "4931f226ca158123ccd765325e9fbf360bfed0af9b460a10f960f9bb13d58323" + sha256: "6ee02c8a1158e6dae7ca430da79436e3b1c9563c8cf02f524af997c201ac2b90" url: "https://pub.dev" source: hosted - version: "0.1.6" + version: "0.1.9" screenshot: dependency: "direct main" description: name: screenshot - sha256: "455284ff1f5b911d94a43c25e1385485cf6b4f288293eba68f15dad711c7b81c" + sha256: "5488135006b34529bf3765a7b1900ca94ccafca300c573550a0474a7162ebf95" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.3.0" sec: dependency: transitive description: @@ -1103,74 +1111,74 @@ packages: dependency: "direct main" description: name: share_plus - sha256: ed3fcea4f789ed95913328e629c0c53e69e80e08b6c24542f1b3576046c614e8 + sha256: "3ef39599b00059db0990ca2e30fca0a29d8b37aae924d60063f8e0184cf20900" url: "https://pub.dev" source: hosted - version: "7.0.2" + version: "7.2.2" share_plus_platform_interface: dependency: transitive description: name: share_plus_platform_interface - sha256: "0c6e61471bd71b04a138b8b588fa388e66d8b005e6f2deda63371c5c505a0981" + sha256: "251eb156a8b5fa9ce033747d73535bf53911071f8d3b6f4f0b578505ce0d4496" url: "https://pub.dev" source: hosted - version: "3.2.1" + version: "3.4.0" shared_preferences: dependency: transitive description: name: shared_preferences - sha256: "0344316c947ffeb3a529eac929e1978fcd37c26be4e8468628bac399365a3ca1" + sha256: "81429e4481e1ccfb51ede496e916348668fd0921627779233bd24cc3ff6abd02" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.2.2" shared_preferences_android: dependency: transitive description: name: shared_preferences_android - sha256: fe8401ec5b6dcd739a0fe9588802069e608c3fdbfd3c3c93e546cf2f90438076 + sha256: "8568a389334b6e83415b6aae55378e158fbc2314e074983362d20c562780fb06" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.2.1" shared_preferences_foundation: dependency: transitive description: name: shared_preferences_foundation - sha256: f39696b83e844923b642ce9dd4bd31736c17e697f6731a5adf445b1274cf3cd4 + sha256: "7708d83064f38060c7b39db12aefe449cb8cdc031d6062280087bc4cdb988f5c" url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.3.5" shared_preferences_linux: dependency: transitive description: name: shared_preferences_linux - sha256: "71d6806d1449b0a9d4e85e0c7a917771e672a3d5dc61149cc9fac871115018e1" + sha256: "9f2cbcf46d4270ea8be39fa156d86379077c8a5228d9dfdb1164ae0bb93f1faa" url: "https://pub.dev" source: hosted - version: "2.3.0" + version: "2.3.2" shared_preferences_platform_interface: dependency: transitive description: name: shared_preferences_platform_interface - sha256: "23b052f17a25b90ff2b61aad4cc962154da76fb62848a9ce088efe30d7c50ab1" + sha256: "22e2ecac9419b4246d7c22bfbbda589e3acf5c0351137d87dd2939d984d37c3b" url: "https://pub.dev" source: hosted - version: "2.3.0" + version: "2.3.2" shared_preferences_web: dependency: transitive description: name: shared_preferences_web - sha256: "7347b194fb0bbeb4058e6a4e87ee70350b6b2b90f8ac5f8bd5b3a01548f6d33a" + sha256: "9aee1089b36bd2aafe06582b7d7817fd317ef05fc30e6ba14bff247d0933042a" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.3.0" shared_preferences_windows: dependency: transitive description: name: shared_preferences_windows - sha256: f95e6a43162bce43c9c3405f3eb6f39e5b5d11f65fab19196cf8225e2777624d + sha256: "841ad54f3c8381c480d0c9b508b89a34036f512482c407e6df7a9c4aa2ef8f59" url: "https://pub.dev" source: hosted - version: "2.3.0" + version: "2.3.2" shelf: dependency: transitive description: @@ -1191,10 +1199,10 @@ packages: dependency: transitive description: name: shell_executor - sha256: f89eb17bb2660568a255bb5612f2773ba9295792d7bb6b96e3ef87985a9457fa + sha256: "49254102f8feba7d5a4d528a412b22e59e7907dd4214036e9b9c6cbf047589e9" url: "https://pub.dev" source: hosted - version: "0.1.4" + version: "0.1.5" shortid: dependency: transitive description: @@ -1220,10 +1228,10 @@ packages: dependency: transitive description: name: source_gen - sha256: fc0da689e5302edb6177fdd964efcb7f58912f43c28c2047a808f5bfff643d16 + sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832" url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.5.0" source_helper: dependency: transitive description: @@ -1236,26 +1244,26 @@ packages: dependency: transitive description: name: source_span - sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.10.0" stack_trace: dependency: transitive description: name: stack_trace - sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.11.1" stacked: dependency: "direct main" description: name: stacked - sha256: "125f96248c6a33fa29a4e622e9b57ee5f222289475292dd1b9fa266cc218a2db" + sha256: "32641025f7bedf3acddd068008932c5f4d89bd089f1b091f61c9fe466c66229e" url: "https://pub.dev" source: hosted - version: "3.4.1" + version: "3.4.2" stacked_shared: dependency: transitive description: @@ -1265,7 +1273,7 @@ packages: source: hosted version: "1.4.0" stream_channel: - dependency: "direct overridden" + dependency: transitive description: name: stream_channel sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 @@ -1300,10 +1308,10 @@ packages: dependency: transitive description: name: test_api - sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb + sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "0.7.0" timing: dependency: transitive description: @@ -1316,10 +1324,10 @@ packages: dependency: "direct main" description: name: tray_manager - sha256: b1975a05e0c6999e983cf9a58a6a098318c896040ccebac5398a3cc9e43b9c69 + sha256: "4ab709d70a4374af172f8c39e018db33a4271265549c6fc9d269a65e5f4b0225" url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.2.1" typed_data: dependency: transitive description: @@ -1328,22 +1336,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.2" - uni_ocr: - dependency: "direct main" - description: - name: uni_ocr - sha256: "36ad2309d59901a00e46c7e3896a3713370272543a447c3692cdb627aa4e748a" - url: "https://pub.dev" - source: hosted - version: "0.1.1" - uni_ocr_client: - dependency: transitive - description: - name: uni_ocr_client - sha256: "0508e0891008e6b240c0773a8cd73e5144f3a9d06e9db09787520b4cfb69535d" - url: "https://pub.dev" - source: hosted - version: "0.1.1" universal_io: dependency: transitive description: @@ -1356,66 +1348,66 @@ packages: dependency: "direct main" description: name: url_launcher - sha256: "781bd58a1eb16069412365c98597726cd8810ae27435f04b3b4d3a470bacd61e" + sha256: "0ecc004c62fd3ed36a2ffcbe0dd9700aee63bd7532d0b642a488b1ec310f492e" url: "https://pub.dev" source: hosted - version: "6.1.12" + version: "6.2.5" url_launcher_android: dependency: transitive description: name: url_launcher_android - sha256: "78cb6dea3e93148615109e58e42c35d1ffbf5ef66c44add673d0ab75f12ff3af" + sha256: d4ed0711849dd8e33eb2dd69c25db0d0d3fdc37e0a62e629fe32f57a22db2745 url: "https://pub.dev" source: hosted - version: "6.0.37" + version: "6.3.0" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: "9af7ea73259886b92199f9e42c116072f05ff9bea2dcb339ab935dfc957392c2" + sha256: "9149d493b075ed740901f3ee844a38a00b33116c7c5c10d7fb27df8987fb51d5" url: "https://pub.dev" source: hosted - version: "6.1.4" + version: "6.2.5" url_launcher_linux: dependency: transitive description: name: url_launcher_linux - sha256: "207f4ddda99b95b4d4868320a352d374b0b7e05eefad95a4a26f57da413443f5" + sha256: ab360eb661f8879369acac07b6bb3ff09d9471155357da8443fd5d3cf7363811 url: "https://pub.dev" source: hosted - version: "3.0.5" + version: "3.1.1" url_launcher_macos: dependency: transitive description: name: url_launcher_macos - sha256: "1c4fdc0bfea61a70792ce97157e5cc17260f61abbe4f39354513f39ec6fd73b1" + sha256: b7244901ea3cf489c5335bdacda07264a6e960b1c1b1a9f91e4bc371d9e68234 url: "https://pub.dev" source: hosted - version: "3.0.6" + version: "3.1.0" url_launcher_platform_interface: dependency: transitive description: name: url_launcher_platform_interface - sha256: bfdfa402f1f3298637d71ca8ecfe840b4696698213d5346e9d12d4ab647ee2ea + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.3.2" url_launcher_web: dependency: transitive description: name: url_launcher_web - sha256: cc26720eefe98c1b71d85f9dc7ef0cada5132617046369d9dc296b3ecaa5cbb4 + sha256: "3692a459204a33e04bc94f5fb91158faf4f2c8903281ddd82915adecdb1a901d" url: "https://pub.dev" source: hosted - version: "2.0.18" + version: "2.3.0" url_launcher_windows: dependency: transitive description: name: url_launcher_windows - sha256: "7967065dd2b5fccc18c653b97958fdf839c5478c28e767c61ee879f4e7882422" + sha256: ecf9725510600aa2bb6d7ddabe16357691b6d2805f66216a97d1b881e21beff7 url: "https://pub.dev" source: hosted - version: "3.0.7" + version: "3.1.1" uuid: dependency: transitive description: @@ -1436,26 +1428,26 @@ packages: dependency: transitive description: name: vector_graphics - sha256: "670f6e07aca990b4a2bcdc08a784193c4ccdd1932620244c3a86bb72a0eac67f" + sha256: "32c3c684e02f9bc0afb0ae0aa653337a2fe022e8ab064bcd7ffda27a74e288e3" url: "https://pub.dev" source: hosted - version: "1.1.7" + version: "1.1.11+1" vector_graphics_codec: dependency: transitive description: name: vector_graphics_codec - sha256: "7451721781d967db9933b63f5733b1c4533022c0ba373a01bdd79d1a5457f69f" + sha256: c86987475f162fadff579e7320c7ddda04cd2fdeffbe1129227a85d9ac9e03da url: "https://pub.dev" source: hosted - version: "1.1.7" + version: "1.1.11+1" vector_graphics_compiler: dependency: transitive description: name: vector_graphics_compiler - sha256: "80a13c613c8bde758b1464a1755a7b3a8f2b6cec61fbf0f5a53c94c30f03ba2e" + sha256: "12faff3f73b1741a36ca7e31b292ddeb629af819ca9efe9953b70bd63fc8cd81" url: "https://pub.dev" source: hosted - version: "1.1.7" + version: "1.1.11+1" vector_math: dependency: transitive description: @@ -1464,14 +1456,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + version: + dependency: "direct main" + description: + name: version + sha256: "3d4140128e6ea10d83da32fef2fa4003fccbf6852217bb854845802f04191f94" + url: "https://pub.dev" + source: hosted + version: "3.0.2" wakelock_plus: dependency: "direct main" description: name: wakelock_plus - sha256: aac3f3258f01781ec9212df94eecef1eb9ba9350e106728def405baa096ba413 + sha256: f268ca2116db22e57577fb99d52515a24bdc1d570f12ac18bb762361d43b043d url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.4" wakelock_plus_platform_interface: dependency: transitive description: @@ -1484,10 +1484,10 @@ packages: dependency: transitive description: name: wallet - sha256: "569c91c2af13a9e1119c001f9c09218eccf3f383eb8d15ba13a5b558010c1bc0" + sha256: "687fd89a16557649b26189e597792962f405797fc64113e8758eabc2c2605c32" url: "https://pub.dev" source: hosted - version: "0.0.12+1" + version: "0.0.13" wallet_connect_uri_validator: dependency: "direct main" description: @@ -1512,46 +1512,54 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" + web: + dependency: transitive + description: + name: web + sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" + url: "https://pub.dev" + source: hosted + version: "0.5.1" web3dart: dependency: transitive description: name: web3dart - sha256: eb4302857aec35f8eb51a54c4bcea40f0647f1160683291ec77bfeff6dae6c08 + sha256: bebbea9278723cef51d21caf65668860e7547f59114fe9f8af01b873a72ba0e6 url: "https://pub.dev" source: hosted - version: "2.7.0" + version: "2.7.1" web_socket_channel: dependency: transitive description: name: web_socket_channel - sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b + sha256: "1d8e795e2a8b3730c41b8a98a2dff2e0fb57ae6f0764a1c46ec5915387d257b2" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.4.4" win32: dependency: transitive description: name: win32 - sha256: "5a751eddf9db89b3e5f9d50c20ab8612296e4e8db69009788d6c8b060a84191c" + sha256: "8cb58b45c47dcb42ab3651533626161d6b67a2921917d8d429791f76972b3480" url: "https://pub.dev" source: hosted - version: "4.1.4" + version: "5.3.0" win32_registry: dependency: transitive description: name: win32_registry - sha256: "1c52f994bdccb77103a6231ad4ea331a244dbcef5d1f37d8462f713143b0bfae" + sha256: "41fd8a189940d8696b1b810efb9abcf60827b6cbfab90b0c43e8439e3a39d85a" url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.2" window_manager: dependency: "direct main" description: name: window_manager - sha256: "9eef00e393e7f9308309ce9a8b2398c9ee3ca78b50c96e8b4f9873945693ac88" + sha256: b3c895bdf936c77b83c5254bec2e6b3f066710c1f89c38b20b8acc382b525494 url: "https://pub.dev" source: hosted - version: "0.3.5" + version: "0.3.8" x25519: dependency: transitive description: @@ -1564,18 +1572,18 @@ packages: dependency: transitive description: name: xdg_directories - sha256: e0b1147eec179d3911f1f19b59206448f78195ca1d20514134e10641b7d7fbff + sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d url: "https://pub.dev" source: hosted - version: "1.0.1" + version: "1.0.4" xml: dependency: transitive description: name: xml - sha256: "5bc72e1e45e941d825fd7468b9b4cc3b9327942649aeb6fc5cdbf135f0a86e84" + sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 url: "https://pub.dev" source: hosted - version: "6.3.0" + version: "6.5.0" yaml: dependency: transitive description: @@ -1584,23 +1592,32 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.2" + znn_ledger_dart: + dependency: "direct main" + description: + path: "." + ref: "release/v0.0.4" + resolved-ref: "9fde67444e82448a2f5dc1d9f0ee32ef8cdd9600" + url: "https://github.com/hypercore-one/znn_ledger_dart.git" + source: git + version: "0.0.4" znn_sdk_dart: dependency: "direct main" description: path: "." - ref: d960c0bdc6dc553eaa75d5ddddb34cab530578b8 - resolved-ref: d960c0bdc6dc553eaa75d5ddddb34cab530578b8 + ref: "8511f17a0dfa721e8f0ec2e0b0aae409f509e102" + resolved-ref: "8511f17a0dfa721e8f0ec2e0b0aae409f509e102" url: "https://github.com/zenon-network/znn_sdk_dart.git" source: git - version: "0.0.5" + version: "0.0.7" zxing2: dependency: "direct main" description: name: zxing2 - sha256: "1e141568c9646bc262fa75aacf739bc151ef6ad0226997c0016cc3da358a1bbc" + sha256: "6cf995abd3c86f01ba882968dedffa7bc130185e382f2300239d2e857fc7912c" url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.2.3" sdks: - dart: ">=3.0.1 <4.0.0" - flutter: ">=3.10.0" + dart: ">=3.3.0 <4.0.0" + flutter: ">=3.19.0" diff --git a/pubspec.yaml b/pubspec.yaml index df746ec1..261b45f1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -9,33 +9,34 @@ dependencies: flutter: sdk: flutter - fl_chart: ^0.62.0 + fl_chart: ^0.63.0 overlay_support: ^2.1.0 - lottie: ^2.1.0 + lottie: ^2.6.0 dotted_border: ^2.0.0+2 flutter_svg: ^2.0.0+1 flutter_vector_icons: ^2.0.0 expandable: ^5.0.1 marquee_widget: ^1.2.0 layout: ^1.0.2 - flutter_staggered_grid_view: ^0.4.0 + flutter_staggered_grid_view: ^0.4.1 flip_card: ^0.7.0 + flutter_flip_card: ^0.0.5 auto_size_text: ^3.0.0 - window_manager: ^0.3.4 - stacked: ^3.1.2 - path_provider: ^2.0.8 + window_manager: ^0.3.5 + stacked: ^3.4.1 + path_provider: ^2.1.0 rxdart: ^0.27.3 hive: ^2.0.5 - url_launcher: ^6.0.18 + url_launcher: ^6.1.12 provider: ^6.0.2 intl: ^0.18.0 - package_info_plus: ^4.0.2 - device_info_plus: ^8.0.0 - infinite_scroll_pagination: ^3.1.0 - share_plus: ^7.0.2 + package_info_plus: ^4.1.0 + device_info_plus: ^9.0.3 + infinite_scroll_pagination: ^3.2.0 + share_plus: ^7.1.0 page_transition: ^2.0.4 - file_selector: ^0.9.2+2 - pretty_qr_code: ^2.0.3 + file_selector: ^1.0.0 + pretty_qr_code: ^3.2.1 screenshot: ^2.1.0 desktop_drop: ^0.4.0 validators: ^3.0.0 @@ -44,10 +45,14 @@ dependencies: znn_sdk_dart: git: url: https://github.com/zenon-network/znn_sdk_dart.git - ref: d960c0bdc6dc553eaa75d5ddddb34cab530578b8 + ref: 8511f17a0dfa721e8f0ec2e0b0aae409f509e102 + znn_ledger_dart: + git: + url: https://github.com/hypercore-one/znn_ledger_dart.git + ref: release/v0.0.4 json_rpc_2: ^3.0.2 path: ^1.8.2 - ffi: ^2.0.1 + ffi: ^2.1.0 get_it: ^7.2.0 hex: ^0.2.0 local_notifier: ^0.1.5 @@ -56,21 +61,22 @@ dependencies: launch_at_startup: ^0.2.1 logging: ^1.2.0 collection: ^1.17.0 - app_links: ^3.4.3 + app_links: ^3.4.5 walletconnect_flutter_v2: ^2.0.14 preference_list: ^0.0.1 - screen_capturer: ^0.1.2 - uni_ocr: ^0.1.0 + screen_capturer: ^0.1.6 zxing2: ^0.2.0 image: ^4.0.10 clipboard_watcher: ^0.2.0 wallet_connect_uri_validator: ^0.1.0 big_decimal: ^0.5.0 - ai_barcode_scanner: ^0.0.7 + ai_barcode_scanner: ^3.4.3 + version: ^3.0.0 + mutex: ^3.1.0 dev_dependencies: - build_runner: ^2.1.7 + build_runner: ^2.4.6 hive_generator: ^2.0.0 - flutter_lints: ^2.0.1 + flutter_lints: ^2.0.2 dependency_validator: ^3.0.0 flutter: @@ -81,9 +87,4 @@ flutter: - assets/images/ - assets/i18n/ - assets/lottie/ - - assets/svg/ - -# TODO: Are these really needed? -dependency_overrides: - stream_channel: ^2.1.2 - collection: ^1.17.2 \ No newline at end of file + - assets/svg/ \ No newline at end of file diff --git a/test-runs/v0.1.0-rc.1/p2p-swap/p2p-swap-2023-06-07-windows-10.csv b/test-runs/v0.1.0-rc.1/p2p-swap/p2p-swap-2023-06-07-windows-10.csv new file mode 100644 index 00000000..cbc2f900 --- /dev/null +++ b/test-runs/v0.1.0-rc.1/p2p-swap/p2p-swap-2023-06-07-windows-10.csv @@ -0,0 +1,11 @@ +Version: v0.1.0-rc.1,Environment: Windows 10,, +Date: 2023-06-07,Tester: vilkris,, +,,, +Test case,Description,Priority,Result +Basic swap functionality,,, +Start & complete native P2P Swap,"Start a native swap, wait for the counterparty to join and complete the swap.",PRIO1,PASS +Join & complete native P2P Swap,Join a native swap and wait for the counterparty to complete the swap.,PRIO1,PASS +,,, +Reclaim functionality,,, +Reclaim funds after started swap fails,"Start swap and wait for the initial deposit to expire, then reclaim funds.",PRIO1,PASS +Reclaim funds after joined swap fails,"Join swap and wait for the counter deposit to expire, then reclaim funds.",PRIO1,PASS \ No newline at end of file diff --git a/test-runs/v0.1.0-rc.8/p2p-swap/p2p-swap-2023-09-17-windows-10.csv b/test-runs/v0.1.0-rc.8/p2p-swap/p2p-swap-2023-09-17-windows-10.csv new file mode 100644 index 00000000..1bf7e29e --- /dev/null +++ b/test-runs/v0.1.0-rc.8/p2p-swap/p2p-swap-2023-09-17-windows-10.csv @@ -0,0 +1,55 @@ +Version: v0.1.0-rc.8,Environment: Windows 10,, +Date: 2023-09-17,Tester: vilkris,, +,,, +Test case,Description,Priority,Result +Basic swap functionality,,, +Start & complete native P2P Swap with Plasma,"Start a native swap, wait for the counterparty to join and complete the swap with Plasma.",PRIO1,PASS +Join & complete native P2P Swap with Plasma,Join a native swap and wait for the counterparty to complete the swap with Plasma.,PRIO1,PASS +Start & complete native P2P Swap with PoW,"Start a native swap, wait for the counterparty to join and complete the swap with PoW.",PRIO1,PASS +Join & complete native P2P Swap with PoW,Join a native swap and wait for the counterparty to complete the swap with PoW.,PRIO1,PASS +Initial deposit is too old,A swap cannot be joined if the initial deposit has been created over 24 hours ago.,PRIO1,PASS +Minimum safe time to complete swap,A swap cannot be completed if the counter deposit's expiration time is in less than 10 minutes.,PRIO1,PASS +Deposit must be intended for user,A swap cannot be joined if the initial deposit is not intended for the user.,PRIO1,PASS +Joining self started swap,A swap cannot be joined if the swap was started by the same user.,PRIO1,PASS +Deposit already in use,A swap cannot be joined if the deposit is in use in another swap.,PRIO1,PASS +Hashlock already in use,A swap cannot be joined if the deposit's hashlock is in use in another swap.,PRIO1,PASS +Joining an expired swap,A swap cannot be joined if the deposit has expired.,PRIO1,PASS +Joining a swap too late,A swap cannot be joined if the deposit expires in less than 6 hours.,PRIO1,PASS +Counter deposit max duration,The counter deposit's duration can not exceed 24 hours.,PRIO1,PASS +Joining a swap with a non-unique hashlock,A swap cannot be joined if the deposit's hashlock has been used in another deposit in the past 24 hours.,PRIO1,PASS +Initial deposit duration,The initial deposit's duration is 8 hours.,PRIO1,PASS +Counter deposit duration,The counter deposit's duration is 1 hour.,PRIO1,PASS +Deposit must exist when completing swap,The counter deposit must exist in the HTLC contract when the unlock transaction is sent.,PRIO1,PASS +Syrius stays active and unlocked during inbound swap,"During an inbound swap, the wakelock keeps Syrius active and unlocked until the swap completes or expires.",PRIO1,PASS +Swap counterparty address cannot belong to keystore,The swap's counterparty cannot be an address from the user's keystore.,PRIO1,PASS +,,, +Swap states and deposits,,, +Invalid method calls to HTLC contract,Invalid method calls to the embedded HTLC contract do not affect swap states.,PRIO1,PASS +Only one counter deposit is accepted,Only the first valid counter deposit is accepted for a swap.,PRIO1,PASS +Swap initiator must be recipient of counter deposit,The swap initiator address must be the recipient of the counter deposit.,PRIO1,PASS +Counter deposit sender must be counterparty,The counter deposit's sender must be the recipient of the initial deposit.,PRIO1,PASS +Counter deposit uses same hash type,The counter deposit must use the same hash type as the initial deposit.,PRIO1,PASS +Proxy unlocked deposit,The swap item of a proxy unlocked deposit is marked as completed.,PRIO1,PASS +Swap expiration,A swap is marked as expired 10 minutes before the counter deposit expires.,PRIO1,PASS +Swap state maintained after restart of Syrius,A swap's state is maintained is Syrius is closed and reopened.,PRIO1,PASS +,,, +Deleting swap items,,, +Deleting active swap,An active swap cannot be deleted from the swaps list.,PRIO1,PASS +Deleting reclaimable swap,A reclaimable swap cannot be deleted from the swaps list.,PRIO1,PASS +Deleting swap history,Deleting the swap history deletes all swap items except active and reclaimable swaps.,PRIO1,PASS +Deleting completed swap,A completed swap item can be deleted from the swaps list.,PRIO2,PASS +Deleting unsuccessful swap,An unsuccessful swap item can be deleted from the swaps list.,PRIO2,PASS +Delete swap confirmation,Confirmation is asked before deleting a swap item from the swaps list.,PRIO2,PASS +,,, +Reclaim functionality,,, +Reclaim funds after started swap fails,"Start swap and wait for the initial deposit to expire, then reclaim funds.",PRIO1,PASS +Reclaim funds after joined swap fails,"Join swap and wait for the counter deposit to expire, then reclaim funds.",PRIO1,PASS +,,, +Recover functionality,,, +Recover funds of any expired deposit,"Start or join a swap and wait for the deposit to expire, then recover funds.",PRIO1,PASS +,,, +Miscellaneous,,, +Swap preimage not shown to user,The swap's preimage is not shown to the user until the swap is no longer active.,PRIO1,PASS +Show warning if deposited token is not in favorites,Show a warning to the user if a swap's token is not in their favorites (except ZNN and QSR).,PRIO1,PASS +Disallow swap start & join when generating plasma,"When plasma is being generated, disallow starting and joining a swap.",PRIO2,PASS +Swap tutorial link,The swap tutorial link opens the swap tutorial in a browser.,PRIO3,PASS diff --git a/test-runs/v0.2.0-rc.0/ledger/ledger-2024-03-08-windows-10.csv b/test-runs/v0.2.0-rc.0/ledger/ledger-2024-03-08-windows-10.csv new file mode 100644 index 00000000..5fe3ef89 --- /dev/null +++ b/test-runs/v0.2.0-rc.0/ledger/ledger-2024-03-08-windows-10.csv @@ -0,0 +1,77 @@ +Version: v0.2.0-rc.0,Environment: Windows 10,, +Date: 8.3.2024,Tester: theta-aquarii,, +,,, +Test case,Description,Priority,Result +New wallet,,, +Create new wallet using 12 word seed phrase,"Create a new wallet using a 12 word seed phrase",PRIO1,PASS +Create new wallet using 24 word seed phrase,"Create a new wallet using a 24 word seed phrase",PRIO1,PASS +,,, +Import wallet,,, +Import wallet using 12 word seed phrase,"Import a wallet using a 12 word seed phrase",PRIO1,PASS +Import wallet using 24 word seed phrase,"Import a wallet using a 24 word seed phrase",PRIO1,PASS +Import wallet using key store wallet file,"Import a wallet using a backup wallet file",PRIO2,PASS +,,, +Hardware Wallet,,, +Scan devices with no devices connected,"Scanning for devices when no Ledger devices are connected, returns an empty result.",PRIO1,PASS +Scan devices with one device connected,"Scanning for devices when one Ledger device is connected, returns 1 result.",PRIO1, +Scan devices with more than one device connected,"Scanning for devices when more than one Ledger device is connected, returns multiple results.",PRIO1, +Select device with device disconnected,"Selecting a disconnected Ledger device after it has been scanned, displays an error message.",PRIO1, +Select device with device connected/locked,"Selecting a connected/locked Ledger device, displays an error message.",PRIO1, +Select device with device connected/unlocked and wong app open,"Selecting a connected/unlocked Ledger device with the wrong app open, displays an error message.",PRIO1, +Select device with device connected/unlocked and app open,"Selecting a connected/unlocked Ledger device with the Zenon app open, displays the address and enables the continue button.",PRIO1, +Create hardware wallet,"Create a hardware wallet using a Ledger Nano S/S+ device.",PRIO1, +,,, +Unlocking,,, +Lock/unlock keystore wallet,"Lock and unlock a keystore wallet with a password.",PRIO1,PASS +Lock/unlock hardware wallet with device disconnected,"Unlocking a hardware wallet with a password does not require the device to be connected.",PRIO1, +Discreet mode keystore wallet,"Enable and disable discreet mode on a keystore wallet.",PRIO2,PASS +Discreet mode hardware wallet with device disconnected,"Enable and disable discreet mode on a hardware wallet.",PRIO2, +,,, +Transactions,,, +Send transaction on keystore wallet,"Send a transaction on a keystore wallet.",PRIO1,PASS +Receive transaction on keystore wallet,"Receive a transaction on a keystore wallet.",PRIO1,PASS +Send and confirm transaction on hardware wallet,"Send and confirm a transaction on a hardware wallet.",PRIO1, +Receive and confirm transaction on hardware wallet,"Receive and confirm a transaction on a hardware wallet.",PRIO1, +Send and reject transaction on hardware wallet,"Send and reject a transaction on a hardware wallet.",PRIO1, +Receive and reject transaction on hardware wallet,"Receive and reject a transaction on a hardware wallet.",PRIO1, +,,, +P2P Swap,,, +Swap history migration on existing keystore wallet,"Open an existing keystore wallet with swap history and verify the migration of the swap history.",PRIO1,PASS +Swap history on keystore wallet after password change,"Change the password on a keystore with swap history and verify that the swap history is not lost.",PRIO1,PASS +Swap history on keystore wallet after delete cache,"Delete cache on a keystore with swap history and verify that the swap history is not lost.",PRIO1,FAIL +Verify swap history on hardware wallet after password change,"Change the password on a hardware wallet with swap history and verify that the swap history is not lost.",PRIO1, +Verify swap history on hardware wallet after reset cache,"Reset cache on a hardware wallet with swap history and verify that the swap history is not lost.",PRIO1, +,,, +WalletConnect,,, +Wrap/unwrap assets on keystore wallet,"Wrap and unwrap assets on a keystore wallet.",PRIO1,PASS +Wrap/unwrap assets on hardware wallet,"Wrap and unwrap assets on a hardware wallet.",PRIO1, +,,, +Addresses,,, +Add 1 address on keystore wallet,"Add and confirm 1 address on a keystore wallet.",PRIO1,PASS +Add 1+ addresses on keystore wallet,"Add and confirm 1+ address on a keystore wallet.",PRIO1,PASS +Add and confirm 1 address on hardware wallet,"Add and confirm 1 address on a hardware wallet with device connected/unlocked and app open.",PRIO1, +Add and confirm 1+ addresses on hardware wallet,"Add and confirm 1+ address on a hardware wallet with device connected/unlocked and app open.",PRIO1, +Add and reject 1 address on hardware wallet,"Add and reject 1 address on a hardware wallet with device connected/unlocked and app open.",PRIO1, +Add and reject 1+ addresses on hardware wallet,"Add and reject 1+ address on a hardware wallet with device connected/unlocked and app open.",PRIO1, +,,, +Security,,, +Change password of keystore wallet,"Change the password of a keystore wallet and verify the change.",PRIO1,PASS +Change password of hardware wallet with device disconnected,"Change the password of a hardware wallet with the device disconnected and verify the change.",PRIO1, +Sign message using keystore wallet,"Sign a message using a keystore wallet.",PRIO1,PASS +Sign message using hardware wallet,"Signing a message on a hardware wallet is not supported and raises an unsupported exception.",PRIO2, +Sign file using keystore wallet,"Sign a file using a keystore wallet.",PRIO1,PASS +Sign file using hardware wallet,"Signing a file on a hardware wallet is not supported and raises an unsupported exception.",PRIO2, +,,, +Wallet Options,,, +Delete cache and unlock keystore wallet,"Delete cache and unlock a keystore wallet.",PRIO1,PASS +Delete cache and unlock hardware wallet with device disconnected,"A hardware wallet with deleted cache needs to be initialized with a device; otherwise an error message is shown.",PRIO1, +Delete cache and unlock hardware wallet with device connected/locked,"A hardware wallet with deleted cache needs to be initialized with a device; otherwise an error message is shown.",PRIO1, +Delete cache and unlock hardware wallet with device connected/unlocked and app closed,"A hardware wallet with deleted cache needs to be initialized with a device; otherwise an error message is shown.",PRIO1, +Delete cache and unlock hardware wallet with device connected/unlocked and wrong app open,"A hardware wallet with deleted cache needs to be initialized with a device; otherwise an error message is shown.",PRIO1, +Delete cache and unlock hardware wallet with device connected/unlocked and app open,"A hardware wallet with deleted cache needs to be initialized with a device; otherwise an error message is shown.",PRIO1, +,,, +Backup,,, +Backup keystore wallet,"Backup a keystore wallet.",PRIO1,PASS +Dump mnemonic of keystore wallet,"Dump mnemonic of a keystore wallet.",PRIO1,PASS +Verify backup wallet button disabled on hardware wallet,"The backup function is not supported on a hardware wallet and therefor disabled.",PRIO2, +Verify dump mnemonic button disabled on hardware wallet,"The dump mnemonic function is not supported on a hardware wallet and therefor disabled.",PRIO2, \ No newline at end of file diff --git a/test-runs/v0.2.0-rc.0/ledger/ledger-2024-03-09-linux.csv b/test-runs/v0.2.0-rc.0/ledger/ledger-2024-03-09-linux.csv new file mode 100644 index 00000000..f958c657 --- /dev/null +++ b/test-runs/v0.2.0-rc.0/ledger/ledger-2024-03-09-linux.csv @@ -0,0 +1,77 @@ +Version: v0.2.0-rc.0,Environment: Ubuntu 22.04.3 LTS/Ledger Nano S,, +Date: 2024-03-09,Tester: CryptoFish,, +,,, +Test case,Description,Priority,Result +New wallet,,, +Create new wallet using 12 word seed phrase,"Create a new wallet using a 12 word seed phrase",PRIO1, +Create new wallet using 24 word seed phrase,"Create a new wallet using a 24 word seed phrase",PRIO1, +,,, +Import wallet,,, +Import wallet using 12 word seed phrase,"Import a wallet using a 12 word seed phrase",PRIO1, +Import wallet using 24 word seed phrase,"Import a wallet using a 24 word seed phrase",PRIO1, +Import wallet using key store wallet file,"Import a wallet using a backup wallet file",PRIO2, +,,, +Hardware Wallet,,, +Scan devices with no devices connected,"Scanning for devices when no Ledger devices are connected, returns an empty result.",PRIO1,PASS +Scan devices with one device connected,"Scanning for devices when one Ledger device is connected, returns 1 result.",PRIO1,PASS +Scan devices with more than one device connected,"Scanning for devices when more than one Ledger device is connected, returns multiple results.",PRIO1, +Select device with device disconnected,"Selecting a disconnected Ledger device after it has been scanned, displays an error message.",PRIO1,PASS +Select device with device connected/locked,"Selecting a connected/locked Ledger device, displays an error message.",PRIO1,PASS +Select device with device connected/unlocked and wong app open,"Selecting a connected/unlocked Ledger device with the wrong app open, displays an error message.",PRIO1,PASS +Select device with device connected/unlocked and app open,"Selecting a connected/unlocked Ledger device with the Zenon app open, displays the address and enables the continue button.",PRIO1,PASS +Create hardware wallet,"Create a hardware wallet using a Ledger Nano S/S+ device.",PRIO1,PASS +,,, +Unlocking,,, +Lock/unlock keystore wallet,"Lock and unlock a keystore wallet with a password.",PRIO1, +Lock/unlock hardware wallet with device disconnected,"Unlocking a hardware wallet with a password does not require the device to be connected.",PRIO1,PASS +Discreet mode keystore wallet,"Enable and disable discreet mode on a keystore wallet.",PRIO2, +Discreet mode hardware wallet with device disconnected,"Enable and disable discreet mode on a hardware wallet.",PRIO2,PASS +,,, +Transactions,,, +Send transaction on keystore wallet,"Send a transaction on a keystore wallet.",PRIO1, +Receive transaction on keystore wallet,"Receive a transaction on a keystore wallet.",PRIO1, +Send and confirm transaction on hardware wallet,"Send and confirm a transaction on a hardware wallet.",PRIO1,PASS +Receive and confirm transaction on hardware wallet,"Receive and confirm a transaction on a hardware wallet.",PRIO1,PASS +Send and reject transaction on hardware wallet,"Send and reject a transaction on a hardware wallet.",PRIO1,PASS +Receive and reject transaction on hardware wallet,"Receive and reject a transaction on a hardware wallet.",PRIO1,FAIL +,,, +P2P Swap,,, +Swap history migration on existing keystore wallet,"Open an existing keystore wallet with swap history and verify the migration of the swap history.",PRIO1, +Swap history on keystore wallet after password change,"Change the password on a keystore with swap history and verify that the swap history is not lost.",PRIO1, +Swap history on keystore wallet after delete cache,"Delete cache on a keystore with swap history and verify that the swap history is not lost.",PRIO1, +Verify swap history on hardware wallet after password change,"Change the password on a hardware wallet with swap history and verify that the swap history is not lost.",PRIO1, +Verify swap history on hardware wallet after reset cache,"Reset cache on a hardware wallet with swap history and verify that the swap history is not lost.",PRIO1, +,,, +WalletConnect,,, +Wrap/unwrap assets on keystore wallet,"Wrap and unwrap assets on a keystore wallet.",PRIO1, +Wrap/unwrap assets on hardware wallet,"Wrap and unwrap assets on a hardware wallet.",PRIO1, +,,, +Addresses,,, +Add 1 address on keystore wallet,"Add and confirm 1 address on a keystore wallet.",PRIO1, +Add 1+ addresses on keystore wallet,"Add and confirm 1+ address on a keystore wallet.",PRIO1, +Add and confirm 1 address on hardware wallet,"Add and confirm 1 address on a hardware wallet with device connected/unlocked and app open.",PRIO1,PASS +Add and confirm 1+ addresses on hardware wallet,"Add and confirm 1+ address on a hardware wallet with device connected/unlocked and app open.",PRIO1,PASS +Add and reject 1 address on hardware wallet,"Add and reject 1 address on a hardware wallet with device connected/unlocked and app open.",PRIO1,PASS +Add and reject 1+ addresses on hardware wallet,"Add and reject 1+ address on a hardware wallet with device connected/unlocked and app open.",PRIO1,PASS +,,, +Security,,, +Change password of keystore wallet,"Change the password of a keystore wallet and verify the change.",PRIO1, +Change password of hardware wallet with device disconnected,"Change the password of a hardware wallet with the device disconnected and verify the change.",PRIO1,PASS +Sign message using keystore wallet,"Sign a message using a keystore wallet.",PRIO1, +Sign message using hardware wallet,"Signing a message on a hardware wallet is not supported and raises an unsupported exception.",PRIO2,PASS +Sign file using keystore wallet,"Sign a file using a keystore wallet.",PRIO1, +Sign file using hardware wallet,"Signing a file on a hardware wallet is not supported and raises an unsupported exception.",PRIO2,PASS +,,, +Wallet Options,,, +Delete cache and unlock keystore wallet,"Delete cache and unlock a keystore wallet.",PRIO1, +Delete cache and unlock hardware wallet with device disconnected,"A hardware wallet with deleted cache needs to be initialized with a device; otherwise an error message is shown.",PRIO1,PASS +Delete cache and unlock hardware wallet with device connected/locked,"A hardware wallet with deleted cache needs to be initialized with a device; otherwise an error message is shown.",PRIO1,PASS +Delete cache and unlock hardware wallet with device connected/unlocked and app closed,"A hardware wallet with deleted cache needs to be initialized with a device; otherwise an error message is shown.",PRIO1,PASS +Delete cache and unlock hardware wallet with device connected/unlocked and wrong app open,"A hardware wallet with deleted cache needs to be initialized with a device; otherwise an error message is shown.",PRIO1,PASS +Delete cache and unlock hardware wallet with device connected/unlocked and app open,"A hardware wallet with deleted cache needs to be initialized with a device; otherwise an error message is shown.",PRIO1,PASS +,,, +Backup,,, +Backup keystore wallet,"Backup a keystore wallet.",PRIO1, +Dump mnemonic of keystore wallet,"Dump mnemonic of a keystore wallet.",PRIO1, +Verify backup wallet button disabled on hardware wallet,"The backup function is not supported on a hardware wallet and therefor disabled.",PRIO2,PASS +Verify dump mnemonic button disabled on hardware wallet,"The dump mnemonic function is not supported on a hardware wallet and therefor disabled.",PRIO2,PASS \ No newline at end of file diff --git a/test-runs/v0.2.0-rc.1/auto-receive/auto-receive-2024-03-11-windows-10.csv b/test-runs/v0.2.0-rc.1/auto-receive/auto-receive-2024-03-11-windows-10.csv new file mode 100644 index 00000000..da95e7c9 --- /dev/null +++ b/test-runs/v0.2.0-rc.1/auto-receive/auto-receive-2024-03-11-windows-10.csv @@ -0,0 +1,21 @@ +Version: v0.2.0-rc.1,Environment: Windows 10,, +Date: 2024-03-11,Tester: CryptoFish,, +,,, +Test case,Description,Priority,Result +Wallet,,, +Create wallet and set auto-receive preference,Create a wallet and set the auto-receive preference in the node-management screen.,PRIO1,PASS +,,, +Transactions,,, +Receive transaction with auto-receive enabled,Automatically receive a transaction by enabling the auto-receive preference.,PRIO1,PASS +Receive transaction with auto-receive disabled,Manually receive a transaction by disabling the auto-receive preference and manually receive it from the pending transaction list.,PRIO1,PASS +,,, +P2P Swap,,, +Start & complete native P2P Swap with auto-receive enabled,"Start a native swap, wait for the counterparty to join and complete the swap with auto-receive enabled.",PRIO1,PASS +Start & complete native P2P Swap with auto-receive disabled,"Start a native swap, wait for the counterparty to join and complete the swap with auto-receive disabled.",PRIO1,PASS +Join & complete native P2P Swap with auto-receive enabled,"Join a native swap and wait for the counterparty to complete the swap with auto-receive enabled.",PRIO1,PASS +Join & complete native P2P Swap with auto-receive disabled,"Join a native swap and wait for the counterparty to complete the swap with auto-receive disabled.",PRIO1,PASS +,,, +Wallet Options,,, +Disable auto-receive,Disable the auto-receive preference,PRIO1,PASS +Enable auto-receive,Enable the auto-receive preference,PRIO1,PASS +Reset wallet and verify auto-receive after confirmation,Reset and reinitialize the wallet and verify that no transactions are automatically received before confirming the auto-receive preference.,PRIO1,PASS diff --git a/test-runs/v0.2.0-rc.1/community-nodes/community-nodes-2024-03-11-windows-10.csv b/test-runs/v0.2.0-rc.1/community-nodes/community-nodes-2024-03-11-windows-10.csv new file mode 100644 index 00000000..4370eee1 --- /dev/null +++ b/test-runs/v0.2.0-rc.1/community-nodes/community-nodes-2024-03-11-windows-10.csv @@ -0,0 +1,23 @@ +Version: v0.2.0-rc.1,Environment: Windows 10,, +Date: 2024-03-11,Tester: CryptoFish,, +,,, +Test case,Description,Priority,Result +Assets,,, +Add community nodes,Add nodes by editing the assets/community-nodes.json file and verify added nodes by restarting syrius,PRIO1,PASS +Remove community nodes,Remove nodes by editing the assets/community-nodes.json file and verify removed nodes by restarting syrius,PRIO1,PASS +Invalid community nodes,Add invalid node urls by editing the assets/community-nodes.json file and verify they are not added by restarting syrius,PRIO1,PASS +Delete community nodes file,Delete the assets/community-nodes.json file and verify empty community nodes by restarting syrius,PRIO2,PASS +Invalidate community nodes file,Invalidate the assets/community-nodes.json file by using incorrect format and verify empty community nodes by restartng syrius,PRIO2,PASS +,,, +Node management (onboarding),,, +Verify community nodes list,Verify community node list and corresponding icons,PRIO1,PASS +Confirm community node,Select and confirm a community node and verify connection,PRIO1,PASS +Add existing node,Adding an existing node causes a node already exist exception,PRIO1,PASS +Shuffle community nodes,Verify community node list is shuffled each time they are loaded,PRIO1,PASS +,,, +Node management (settings),,, +Verify community nodes list,Verify community node list and corresponding icons,PRIO1,PASS +Select community nodes,Select and confirm a community node and verify connection,PRIO1,PASS +Add existing node,Adding an existing node causes a node already exist exception,PRIO1,PASS +Edit existing node,Editing an existing node causes a node already exist exception,PRIO1,PASS +Shuffle community nodes,Verify community node list is shuffled each time they are loaded,PRIO1,PASS diff --git a/test-runs/v0.2.0-rc.1/walletconnect/walletconnect-2024-03-10-windows-10.csv b/test-runs/v0.2.0-rc.1/walletconnect/walletconnect-2024-03-10-windows-10.csv new file mode 100644 index 00000000..33970b72 --- /dev/null +++ b/test-runs/v0.2.0-rc.1/walletconnect/walletconnect-2024-03-10-windows-10.csv @@ -0,0 +1,24 @@ +Version: v0.2.0-rc.1,Environment: Windows 10,, +Date: 2024-03-10,Tester: CryptoFish,, +,,, +Test case,Description,Priority,Result +Pairing,,, +Create pair with wc url,Create a walletconnect pair by manually connecting to a wc url.,PRIO1,PASS +Create pair with on-screen QR scanner,Create a walletconnect pair by scanning a QR with the on-screen QR scanner.,PRIO2,PASS +Create pair with camera QR scanner,Create a walletconnect pair by scanning a QR with the camera QR scanner.,PRIO2, +Disconenct a pairing from syrius,Disconnect a pairing by removing it from the WalletConnect Pairing List.,PRIO1,PASS +Disconnect a pairing from third-party app,Disconnect a pairing by removing it from third-party application and verify its removal from the Pairing List.,PRIO1,PASS +Expired pairing,An expired pairing cannot be used to start a session.,PRIO1, +,,, +Sessions,,, +Approve session,Approve a session and verify the acknowledged session in the WalletConnect Sessions List.,PRIO1,PASS +Reject session,Reject a session and verify inactive pairing.,PRIO1,PASS +Expired session,An expired session cannot be used to send requests.,PRIO1, +,,, +Requests,,, +Approve request,Approve a request and verify is has been approved.,PRIO1,PASS +Reject request,Reject a request and verify it has been rejected.,PRIO1,PASS +,,, +Miscellaneous,,, +Pairings and sessions are deleted on delete cache,Delete cache and verify pairings and sessions are deleted.,PRIO2,FAIL +Pairings and sessions are deleted on wallet reset,Reset and reinitialize wallet and verify pairings and sessions are deleted.,PRIO2,FAIL \ No newline at end of file diff --git a/test-runs/v0.2.0-rc.3/auto-receive/auto-receive-2024-03-13-windows-11.csv b/test-runs/v0.2.0-rc.3/auto-receive/auto-receive-2024-03-13-windows-11.csv new file mode 100644 index 00000000..80945d98 --- /dev/null +++ b/test-runs/v0.2.0-rc.3/auto-receive/auto-receive-2024-03-13-windows-11.csv @@ -0,0 +1,21 @@ +Version: v0.2.0-rc.3,Environment: Windows 11 Enterprise Eval 22H2,, +Date: 3/13/2024,Tester: coinselor,, +,,, +Test case,Description,Priority,Result +Wallet,,, +Create wallet and set auto-receive preference,Create a wallet and set the auto-receive preference in the node-management screen.,PRIO1,PASS +,,, +Transactions,,, +Receive transaction with auto-receive enabled,Automatically receive a transaction by enabling the auto-receive preference.,PRIO1,PASS +Receive transaction with auto-receive disabled,Manually receive a transaction by disabling the auto-receive preference and manually receive it from the pending transaction list.,PRIO1,PASS +,,, +P2P Swap,,, +Start & complete native P2P Swap with auto-receive enabled,"Start a native swap, wait for the counterparty to join and complete the swap with auto-receive enabled.",PRIO1,PASS +Start & complete native P2P Swap with auto-receive disabled,"Start a native swap, wait for the counterparty to join and complete the swap with auto-receive disabled.",PRIO1,PASS +Join & complete native P2P Swap with auto-receive enabled,"Join a native swap and wait for the counterparty to complete the swap with auto-receive enabled.",PRIO1,PASS +Join & complete native P2P Swap with auto-receive disabled,"Join a native swap and wait for the counterparty to complete the swap with auto-receive disabled.",PRIO1,PASS +,,, +Wallet Options,,, +Disable auto-receive,Disable the auto-receive preference,PRIO1,PASS +Enable auto-receive,Enable the auto-receive preference,PRIO1,PASS +Reset wallet and verify auto-receive after confirmation,Reset and reinitialize the wallet and verify that no transactions are automatically received before confirming the auto-receive preference.,PRIO1,FAIL diff --git a/test-runs/v0.2.0-rc.3/community-nodes/community-nodes-2024-03-13-windows-11.csv b/test-runs/v0.2.0-rc.3/community-nodes/community-nodes-2024-03-13-windows-11.csv new file mode 100644 index 00000000..24d7397c --- /dev/null +++ b/test-runs/v0.2.0-rc.3/community-nodes/community-nodes-2024-03-13-windows-11.csv @@ -0,0 +1,23 @@ +Version: v0.2.0-rc.3,Environment: Windows 11 Enterprise Eval 22H2,, +Date: 3/13/2024,Tester: coinselor,, +,,, +Test case,Description,Priority,Result +Assets,,, +Add community nodes,Add nodes by editing the assets/community-nodes.json file and verify added nodes by restarting syrius,PRIO1,PASS +Remove community nodes,Remove nodes by editing the assets/community-nodes.json file and verify removed nodes by restarting syrius,PRIO1,PASS +Invalid community nodes,Add invalid node urls by editing the assets/community-nodes.json file and verify they are not added by restarting syrius,PRIO1,PASS +Delete community nodes file,Delete the assets/community-nodes.json file and verify empty community nodes by restarting syrius,PRIO2,PASS +Invalidate community nodes file,Invalidate the assets/community-nodes.json file by using incorrect format and verify empty community nodes by restartng syrius,PRIO2,PASS +,,, +Node management (onboarding),,, +Verify community nodes list,Verify community node list and corresponding icons,PRIO1,PASS +Confirm community node,Select and confirm a community node and verify connection,PRIO1,PASS +Add existing node,Adding an existing node causes a node already exist exception,PRIO1,PASS +Shuffle community nodes,Verify community node list is shuffled each time they are loaded,PRIO1,PASS +,,, +Node management (settings),,, +Verify community nodes list,Verify community node list and corresponding icons,PRIO1,PASS +Select community nodes,Select and confirm a community node and verify connection,PRIO1,PASS +Add existing node,Adding an existing node causes a node already exist exception,PRIO1,PASS +Edit existing node,Editing an existing node causes a node already exist exception,PRIO1,PASS +Shuffle community nodes,Verify community node list is shuffled each time they are loaded,PRIO1,PASS diff --git a/test-runs/v0.2.0-rc.4/ledger/ledger-2024-03-16-macos-m1.csv b/test-runs/v0.2.0-rc.4/ledger/ledger-2024-03-16-macos-m1.csv new file mode 100644 index 00000000..9c7f7f14 --- /dev/null +++ b/test-runs/v0.2.0-rc.4/ledger/ledger-2024-03-16-macos-m1.csv @@ -0,0 +1,77 @@ +Version: v0.2.0-rc.4,Environment: macOS M1,, +Date: 16/3/24,Tester: 0x3639,, +,,, +Test case,Description,Priority,Result +New wallet,,, +Create new wallet using 12 word seed phrase,"Create a new wallet using a 12 word seed phrase",PRIO1, +Create new wallet using 24 word seed phrase,"Create a new wallet using a 24 word seed phrase",PRIO1, +,,, +Import wallet,,, +Import wallet using 12 word seed phrase,"Import a wallet using a 12 word seed phrase",PRIO1, +Import wallet using 24 word seed phrase,"Import a wallet using a 24 word seed phrase",PRIO1, +Import wallet using key store wallet file,"Import a wallet using a backup wallet file",PRIO2, +,,, +Hardware Wallet,,, +Scan devices with no devices connected,"Scanning for devices when no Ledger devices are connected, returns an empty result.",PRIO1,PASS +Scan devices with one device connected,"Scanning for devices when one Ledger device is connected, returns 1 result.",PRIO1,PASS +Scan devices with more than one device connected,"Scanning for devices when more than one Ledger device is connected, returns multiple results.",PRIO1, +Select device with device disconnected,"Selecting a disconnected Ledger device after it has been scanned, displays an error message.",PRIO1, +Select device with device connected/locked,"Selecting a connected/locked Ledger device, displays an error message.",PRIO1, +Select device with device connected/unlocked and wong app open,"Selecting a connected/unlocked Ledger device with the wrong app open, displays an error message.",PRIO1, +Select device with device connected/unlocked and app open,"Selecting a connected/unlocked Ledger device with the Zenon app open, displays the address and enables the continue button.",PRIO1, +Create hardware wallet,"Create a hardware wallet using a Ledger Nano S/S+ device.",PRIO1,PASS +,,, +Unlocking,,, +Lock/unlock keystore wallet,"Lock and unlock a keystore wallet with a password.",PRIO1, +Lock/unlock hardware wallet with device disconnected,"Unlocking a hardware wallet with a password does not require the device to be connected.",PRIO1, +Discreet mode keystore wallet,"Enable and disable discreet mode on a keystore wallet.",PRIO2, +Discreet mode hardware wallet with device disconnected,"Enable and disable discreet mode on a hardware wallet.",PRIO2, +,,, +Transactions,,, +Send transaction on keystore wallet,"Send a transaction on a keystore wallet.",PRIO1, +Receive transaction on keystore wallet,"Receive a transaction on a keystore wallet.",PRIO1, +Send and confirm transaction on hardware wallet,"Send and confirm a transaction on a hardware wallet.",PRIO1,PASS +Receive and confirm transaction on hardware wallet,"Receive and confirm a transaction on a hardware wallet.",PRIO1,PASS +Send and reject transaction on hardware wallet,"Send and reject a transaction on a hardware wallet.",PRIO1,PASS +Receive and reject transaction on hardware wallet,"Receive and reject a transaction on a hardware wallet.",PRIO1,PASS +,,, +P2P Swap,,, +Swap history migration on existing keystore wallet,"Open an existing keystore wallet with swap history and verify the migration of the swap history.",PRIO1, +Swap history on keystore wallet after password change,"Change the password on a keystore with swap history and verify that the swap history is not lost.",PRIO1, +Swap history on keystore wallet after delete cache,"Delete cache on a keystore with swap history and verify that the swap history is not lost.",PRIO1, +Verify swap history on hardware wallet after password change,"Change the password on a hardware wallet with swap history and verify that the swap history is not lost.",PRIO1, +Verify swap history on hardware wallet after reset cache,"Reset cache on a hardware wallet with swap history and verify that the swap history is not lost.",PRIO1, +,,, +WalletConnect,,, +Wrap/unwrap assets on keystore wallet,"Wrap and unwrap assets on a keystore wallet.",PRIO1, +Wrap/unwrap assets on hardware wallet,"Wrap and unwrap assets on a hardware wallet.",PRIO1,PASS +,,, +Addresses,,, +Add 1 address on keystore wallet,"Add and confirm 1 address on a keystore wallet.",PRIO1, +Add 1+ addresses on keystore wallet,"Add and confirm 1+ address on a keystore wallet.",PRIO1, +Add and confirm 1 address on hardware wallet,"Add and confirm 1 address on a hardware wallet with device connected/unlocked and app open.",PRIO1,PASS +Add and confirm 1+ addresses on hardware wallet,"Add and confirm 1+ address on a hardware wallet with device connected/unlocked and app open.",PRIO1,PASS +Add and reject 1 address on hardware wallet,"Add and reject 1 address on a hardware wallet with device connected/unlocked and app open.",PRIO1,PASS +Add and reject 1+ addresses on hardware wallet,"Add and reject 1+ address on a hardware wallet with device connected/unlocked and app open.",PRIO1,PASS +,,, +Security,,, +Change password of keystore wallet,"Change the password of a keystore wallet and verify the change.",PRIO1, +Change password of hardware wallet with device disconnected,"Change the password of a hardware wallet with the device disconnected and verify the change.",PRIO1, +Sign message using keystore wallet,"Sign a message using a keystore wallet.",PRIO1, +Sign message using hardware wallet,"Signing a message on a hardware wallet is not supported and raises an unsupported exception.",PRIO2,PASS +Sign file using keystore wallet,"Sign a file using a keystore wallet.",PRIO1, +Sign file using hardware wallet,"Signing a file on a hardware wallet is not supported and raises an unsupported exception.",PRIO2,PASS +,,, +Wallet Options,,, +Delete cache and unlock keystore wallet,"Delete cache and unlock a keystore wallet.",PRIO1, +Delete cache and unlock hardware wallet with device disconnected,"A hardware wallet with deleted cache needs to be initialized with a device; otherwise an error message is shown.",PRIO1,PASS +Delete cache and unlock hardware wallet with device connected/locked,"A hardware wallet with deleted cache needs to be initialized with a device; otherwise an error message is shown.",PRIO1,PASS +Delete cache and unlock hardware wallet with device connected/unlocked and app closed,"A hardware wallet with deleted cache needs to be initialized with a device; otherwise an error message is shown.",PRIO1,PASS +Delete cache and unlock hardware wallet with device connected/unlocked and wrong app open,"A hardware wallet with deleted cache needs to be initialized with a device; otherwise an error message is shown.",PRIO1,PASS +Delete cache and unlock hardware wallet with device connected/unlocked and app open,"A hardware wallet with deleted cache needs to be initialized with a device; otherwise an error message is shown.",PRIO1,PASS +,,, +Backup,,, +Backup keystore wallet,"Backup a keystore wallet.",PRIO1, +Dump mnemonic of keystore wallet,"Dump mnemonic of a keystore wallet.",PRIO1, +Verify backup wallet button disabled on hardware wallet,"The backup function is not supported on a hardware wallet and therefor disabled.",PRIO2,PASS +Verify dump mnemonic button disabled on hardware wallet,"The dump mnemonic function is not supported on a hardware wallet and therefor disabled.",PRIO2,PASS \ No newline at end of file diff --git a/test-runs/v0.2.0-rc.5/walletconnect/walletconnect-2024-03-22-macos-11.csv b/test-runs/v0.2.0-rc.5/walletconnect/walletconnect-2024-03-22-macos-11.csv new file mode 100644 index 00000000..240faad2 --- /dev/null +++ b/test-runs/v0.2.0-rc.5/walletconnect/walletconnect-2024-03-22-macos-11.csv @@ -0,0 +1,24 @@ +Version: v0.2.0-rc.5,Environment: macOS Big Sur v11.7.10,, +Date: 3/22/2024,Tester: coinselor,, +,,, +Test case,Description,Priority,Result +Pairing,,, +Create pair with wc url,Create a walletconnect pair by manually connecting to a wc url.,PRIO1,PASS +Create pair with on-screen QR scanner,Create a walletconnect pair by scanning a QR with the on-screen QR scanner.,PRIO2,PASS +Create pair with camera QR scanner,Create a walletconnect pair by scanning a QR with the camera QR scanner.,PRIO2,FAIL +Disconenct a pairing from syrius,Disconnect a pairing by removing it from the WalletConnect Pairing List.,PRIO1,PASS +Disconnect a pairing from third-party app,Disconnect a pairing by removing it from third-party application and verify its removal from the Pairing List.,PRIO1,PASS +Expired pairing,An expired pairing cannot be used to start a session.,PRIO1, +,,, +Sessions,,, +Approve session,Approve a session and verify the acknowledged session in the WalletConnect Sessions List.,PRIO1,PASS +Reject session,Reject a session and verify inactive pairing.,PRIO1,PASS +Expired session,An expired session cannot be used to send requests.,PRIO1, +,,, +Requests,,, +Approve request,Approve a request and verify is has been approved.,PRIO1, +Reject request,Reject a request and verify it has been rejected.,PRIO1, +,,, +Miscellaneous,,, +Pairings and sessions are deleted on delete cache,Delete cache and verify pairings and sessions are deleted.,PRIO2, +Pairings and sessions are deleted on wallet reset,Reset and reinitialize wallet and verify pairings and sessions are deleted.,PRIO2, \ No newline at end of file diff --git a/test-runs/v0.2.0-rc.6/walletconnect/walletconnect-2024-03-25-macos-11.csv b/test-runs/v0.2.0-rc.6/walletconnect/walletconnect-2024-03-25-macos-11.csv new file mode 100644 index 00000000..4f6d9862 --- /dev/null +++ b/test-runs/v0.2.0-rc.6/walletconnect/walletconnect-2024-03-25-macos-11.csv @@ -0,0 +1,24 @@ +Version: v0.2.0-rc.6,Environment: macOS Big Sur v11.7.10,, +Date: 3/25/2024,Tester: coinselor,, +,,, +Test case,Description,Priority,Result +Pairing,,, +Create pair with wc url,Create a walletconnect pair by manually connecting to a wc url.,PRIO1,PASS +Create pair with on-screen QR scanner,Create a walletconnect pair by scanning a QR with the on-screen QR scanner.,PRIO2,PASS +Create pair with camera QR scanner,Create a walletconnect pair by scanning a QR with the camera QR scanner.,PRIO2,PASS +Disconenct a pairing from syrius,Disconnect a pairing by removing it from the WalletConnect Pairing List.,PRIO1,PASS +Disconnect a pairing from third-party app,Disconnect a pairing by removing it from third-party application and verify its removal from the Pairing List.,PRIO1,PASS +Expired pairing,An expired pairing cannot be used to start a session.,PRIO1,PASS +,,, +Sessions,,, +Approve session,Approve a session and verify the acknowledged session in the WalletConnect Sessions List.,PRIO1,PASS +Reject session,Reject a session and verify inactive pairing.,PRIO1,PASS +Expired session,An expired session cannot be used to send requests.,PRIO1,PASS +,,, +Requests,,, +Approve request,Approve a request and verify is has been approved.,PRIO1,PASS +Reject request,Reject a request and verify it has been rejected.,PRIO1,PASS +,,, +Miscellaneous,,, +Pairings and sessions are deleted on delete cache,Delete cache and verify pairings and sessions are deleted.,PRIO2,PASS +Pairings and sessions are deleted on wallet reset,Reset and reinitialize wallet and verify pairings and sessions are deleted.,PRIO2,FAIL \ No newline at end of file diff --git a/test/widget_test.dart b/test/widget_test.dart deleted file mode 100644 index c17a98f8..00000000 --- a/test/widget_test.dart +++ /dev/null @@ -1,30 +0,0 @@ -// 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:zenon_syrius_wallet_flutter/main.dart'; - -void main() { - testWidgets('Counter increments smoke test', (WidgetTester tester) async { - // Build our app and trigger a frame. - await tester.pumpWidget(const MyApp()); - - // Verify that our counter starts at 0. - expect(find.text('0'), findsOneWidget); - expect(find.text('1'), findsNothing); - - // Tap the '+' icon and trigger a frame. - await tester.tap(find.byIcon(Icons.add)); - await tester.pump(); - - // Verify that our counter has incremented. - expect(find.text('0'), findsNothing); - expect(find.text('1'), findsOneWidget); - }); -} diff --git a/tests/auto-receive/auto-receive-tests.csv b/tests/auto-receive/auto-receive-tests.csv new file mode 100644 index 00000000..b02139de --- /dev/null +++ b/tests/auto-receive/auto-receive-tests.csv @@ -0,0 +1,21 @@ +Version:,Environment:,, +Date:,Tester:,, +,,, +Test case,Description,Priority,Result +Wallet,,, +Create wallet and set auto-receive preference,Create a wallet and set the auto-receive preference in the node-management screen.,PRIO1, +,,, +Transactions,,, +Receive transaction with auto-receive enabled,Automatically receive a transaction by enabling the auto-receive preference.,PRIO1, +Receive transaction with auto-receive disabled,Manually receive a transaction by disabling the auto-receive preference and manually receive it from the pending transaction list.,PRIO1, +,,, +P2P Swap,,, +Start & complete native P2P Swap with auto-receive enabled,"Start a native swap, wait for the counterparty to join and complete the swap with auto-receive enabled.",PRIO1, +Start & complete native P2P Swap with auto-receive disabled,"Start a native swap, wait for the counterparty to join and complete the swap with auto-receive disabled.",PRIO1, +Join & complete native P2P Swap with auto-receive enabled,"Join a native swap and wait for the counterparty to complete the swap with auto-receive enabled.",PRIO1, +Join & complete native P2P Swap with auto-receive disabled,"Join a native swap and wait for the counterparty to complete the swap with auto-receive disabled.",PRIO1, +,,, +Wallet Options,,, +Disable auto-receive,Disable the auto-receive preference,PRIO1, +Enable auto-receive,Enable the auto-receive preference,PRIO1, +Reset wallet and verify auto-receive after confirmation,Reset and reinitialize the wallet and verify that no transactions are automatically received before confirming the auto-receive preference.,PRIO1, diff --git a/tests/community-nodes/community-nodes-tests.csv b/tests/community-nodes/community-nodes-tests.csv new file mode 100644 index 00000000..efa5db7d --- /dev/null +++ b/tests/community-nodes/community-nodes-tests.csv @@ -0,0 +1,23 @@ +Version:,Environment:,, +Date:,Tester:,, +,,, +Test case,Description,Priority,Result +Assets,,, +Add community nodes,Add nodes by editing the assets/community-nodes.json file and verify added nodes by restarting syrius,PRIO1, +Remove community nodes,Remove nodes by editing the assets/community-nodes.json file and verify removed nodes by restarting syrius,PRIO1, +Invalid community nodes,Add invalid node urls by editing the assets/community-nodes.json file and verify they are not added by restarting syrius,PRIO1, +Delete community nodes file,Delete the assets/community-nodes.json file and verify empty community nodes by restarting syrius,PRIO2, +Invalidate community nodes file,Invalidate the assets/community-nodes.json file by using incorrect format and verify empty community nodes by restartng syrius,PRIO2, +,,, +Node management (onboarding),,, +Verify community nodes list,Verify community node list and corresponding icons,PRIO1, +Confirm community node,Select and confirm a community node and verify connection,PRIO1, +Add existing node,Adding an existing node causes a node already exist exception,PRIO1, +Shuffle community nodes,Verify community node list is shuffled each time they are loaded,PRIO1, +,,, +Node management (settings),,, +Verify community nodes list,Verify community node list and corresponding icons,PRIO1, +Select community nodes,Select and confirm a community node and verify connection,PRIO1, +Add existing node,Adding an existing node causes a node already exist exception,PRIO1, +Edit existing node,Editing an existing node causes a node already exist exception,PRIO1, +Shuffle community nodes,Verify community node list is shuffled each time they are loaded,PRIO1, diff --git a/tests/ledger/ledger-tests.csv b/tests/ledger/ledger-tests.csv new file mode 100644 index 00000000..0e0fdc4b --- /dev/null +++ b/tests/ledger/ledger-tests.csv @@ -0,0 +1,77 @@ +Version:,Environment:,, +Date:,Tester:,, +,,, +Test case,Description,Priority,Result +New wallet,,, +Create new wallet using 12 word seed phrase,"Create a new wallet using a 12 word seed phrase",PRIO1, +Create new wallet using 24 word seed phrase,"Create a new wallet using a 24 word seed phrase",PRIO1, +,,, +Import wallet,,, +Import wallet using 12 word seed phrase,"Import a wallet using a 12 word seed phrase",PRIO1, +Import wallet using 24 word seed phrase,"Import a wallet using a 24 word seed phrase",PRIO1, +Import wallet using key store wallet file,"Import a wallet using a backup wallet file",PRIO2, +,,, +Hardware Wallet,,, +Scan devices with no devices connected,"Scanning for devices when no Ledger devices are connected, returns an empty result.",PRIO1, +Scan devices with one device connected,"Scanning for devices when one Ledger device is connected, returns 1 result.",PRIO1, +Scan devices with more than one device connected,"Scanning for devices when more than one Ledger device is connected, returns multiple results.",PRIO1, +Select device with device disconnected,"Selecting a disconnected Ledger device after it has been scanned, displays an error message.",PRIO1, +Select device with device connected/locked,"Selecting a connected/locked Ledger device, displays an error message.",PRIO1, +Select device with device connected/unlocked and wong app open,"Selecting a connected/unlocked Ledger device with the wrong app open, displays an error message.",PRIO1, +Select device with device connected/unlocked and app open,"Selecting a connected/unlocked Ledger device with the Zenon app open, displays the address and enables the continue button.",PRIO1, +Create hardware wallet,"Create a hardware wallet using a Ledger Nano S/S+ device.",PRIO1, +,,, +Unlocking,,, +Lock/unlock keystore wallet,"Lock and unlock a keystore wallet with a password.",PRIO1, +Lock/unlock hardware wallet with device disconnected,"Unlocking a hardware wallet with a password does not require the device to be connected.",PRIO1, +Discreet mode keystore wallet,"Enable and disable discreet mode on a keystore wallet.",PRIO2, +Discreet mode hardware wallet with device disconnected,"Enable and disable discreet mode on a hardware wallet.",PRIO2, +,,, +Transactions,,, +Send transaction on keystore wallet,"Send a transaction on a keystore wallet.",PRIO1, +Receive transaction on keystore wallet,"Receive a transaction on a keystore wallet.",PRIO1, +Send and confirm transaction on hardware wallet,"Send and confirm a transaction on a hardware wallet.",PRIO1, +Receive and confirm transaction on hardware wallet,"Receive and confirm a transaction on a hardware wallet.",PRIO1, +Send and reject transaction on hardware wallet,"Send and reject a transaction on a hardware wallet.",PRIO1, +Receive and reject transaction on hardware wallet,"Receive and reject a transaction on a hardware wallet.",PRIO1, +,,, +P2P Swap,,, +Swap history migration on existing keystore wallet,"Open an existing keystore wallet with swap history and verify the migration of the swap history.",PRIO1, +Swap history on keystore wallet after password change,"Change the password on a keystore with swap history and verify that the swap history is not lost.",PRIO1, +Swap history on keystore wallet after delete cache,"Delete cache on a keystore with swap history and verify that the swap history is not lost.",PRIO1, +Verify swap history on hardware wallet after password change,"Change the password on a hardware wallet with swap history and verify that the swap history is not lost.",PRIO1, +Verify swap history on hardware wallet after reset cache,"Reset cache on a hardware wallet with swap history and verify that the swap history is not lost.",PRIO1, +,,, +WalletConnect,,, +Wrap/unwrap assets on keystore wallet,"Wrap and unwrap assets on a keystore wallet.",PRIO1, +Wrap/unwrap assets on hardware wallet,"Wrap and unwrap assets on a hardware wallet.",PRIO1, +,,, +Addresses,,, +Add 1 address on keystore wallet,"Add and confirm 1 address on a keystore wallet.",PRIO1, +Add 1+ addresses on keystore wallet,"Add and confirm 1+ address on a keystore wallet.",PRIO1, +Add and confirm 1 address on hardware wallet,"Add and confirm 1 address on a hardware wallet with device connected/unlocked and app open.",PRIO1, +Add and confirm 1+ addresses on hardware wallet,"Add and confirm 1+ address on a hardware wallet with device connected/unlocked and app open.",PRIO1, +Add and reject 1 address on hardware wallet,"Add and reject 1 address on a hardware wallet with device connected/unlocked and app open.",PRIO1, +Add and reject 1+ addresses on hardware wallet,"Add and reject 1+ address on a hardware wallet with device connected/unlocked and app open.",PRIO1, +,,, +Security,,, +Change password of keystore wallet,"Change the password of a keystore wallet and verify the change.",PRIO1, +Change password of hardware wallet with device disconnected,"Change the password of a hardware wallet with the device disconnected and verify the change.",PRIO1, +Sign message using keystore wallet,"Sign a message using a keystore wallet.",PRIO1, +Sign message using hardware wallet,"Signing a message on a hardware wallet is not supported and raises an unsupported exception.",PRIO2, +Sign file using keystore wallet,"Sign a file using a keystore wallet.",PRIO1, +Sign file using hardware wallet,"Signing a file on a hardware wallet is not supported and raises an unsupported exception.",PRIO2, +,,, +Wallet Options,,, +Delete cache and unlock keystore wallet,"Delete cache and unlock a keystore wallet.",PRIO1, +Delete cache and unlock hardware wallet with device disconnected,"A hardware wallet with deleted cache needs to be initialized with a device; otherwise an error message is shown.",PRIO1, +Delete cache and unlock hardware wallet with device connected/locked,"A hardware wallet with deleted cache needs to be initialized with a device; otherwise an error message is shown.",PRIO1, +Delete cache and unlock hardware wallet with device connected/unlocked and app closed,"A hardware wallet with deleted cache needs to be initialized with a device; otherwise an error message is shown.",PRIO1, +Delete cache and unlock hardware wallet with device connected/unlocked and wrong app open,"A hardware wallet with deleted cache needs to be initialized with a device; otherwise an error message is shown.",PRIO1, +Delete cache and unlock hardware wallet with device connected/unlocked and app open,"A hardware wallet with deleted cache needs to be initialized with a device; otherwise an error message is shown.",PRIO1, +,,, +Backup,,, +Backup keystore wallet,"Backup a keystore wallet.",PRIO1, +Dump mnemonic of keystore wallet,"Dump mnemonic of a keystore wallet.",PRIO1, +Verify backup wallet button disabled on hardware wallet,"The backup function is not supported on a hardware wallet and therefor disabled.",PRIO2, +Verify dump mnemonic button disabled on hardware wallet,"The dump mnemonic function is not supported on a hardware wallet and therefor disabled.",PRIO2, \ No newline at end of file diff --git a/tests/p2p-swap/p2p-swap-tests.csv b/tests/p2p-swap/p2p-swap-tests.csv new file mode 100644 index 00000000..e77b6e07 --- /dev/null +++ b/tests/p2p-swap/p2p-swap-tests.csv @@ -0,0 +1,57 @@ +Version:,Environment:,, +Date:,Tester:,, +,,, +Test case,Description,Priority,Result +Basic swap functionality,,, +Start & complete native P2P Swap with Plasma,"Start a native swap, wait for the counterparty to join and complete the swap with Plasma.",PRIO1, +Join & complete native P2P Swap with Plasma,Join a native swap and wait for the counterparty to complete the swap with Plasma.,PRIO1, +Start & complete native P2P Swap with PoW,"Start a native swap, wait for the counterparty to join and complete the swap with PoW.",PRIO1, +Join & complete native P2P Swap with PoW,Join a native swap and wait for the counterparty to complete the swap with PoW.,PRIO1, +Initial deposit is too old,A swap cannot be joined if the initial deposit has been created over 24 hours ago.,PRIO1, +Minimum safe time to complete swap,A swap cannot be completed if the counter deposit's expiration time is in less than 10 minutes.,PRIO1, +Deposit must be intended for user,A swap cannot be joined if the initial deposit is not intended for the user.,PRIO1, +Joining self started swap,A swap cannot be joined if the swap was started by the same user.,PRIO1, +Deposit already in use,A swap cannot be joined if the deposit is in use in another swap.,PRIO1, +Hashlock already in use,A swap cannot be joined if the deposit's hashlock is in use in another swap.,PRIO1, +Joining an expired swap,A swap cannot be joined if the deposit has expired.,PRIO1, +Joining a swap too late,A swap cannot be joined if the deposit expires in less than 6 hours.,PRIO1, +Counter deposit max duration,The counter deposit's duration can not exceed 24 hours.,PRIO1, +Joining a swap with a non-unique hashlock,A swap cannot be joined if the deposit's hashlock has been used in another deposit in the past 24 hours.,PRIO1, +Initial deposit duration,The initial deposit's duration is 8 hours.,PRIO1, +Counter deposit duration,The counter deposit's duration is 1 hour.,PRIO1, +Deposit must exist when completing swap,The counter deposit must exist in the HTLC contract when the unlock transaction is sent.,PRIO1, +Syrius stays active and unlocked during inbound swap,"During an inbound swap, the wakelock keeps Syrius active and unlocked until the swap completes or expires.",PRIO1, +Swap counterparty address cannot belong to keystore,The swap's counterparty cannot be an address from the user's keystore.,PRIO1, +,,, +Swap states and deposits,,, +Invalid method calls to HTLC contract,Invalid method calls to the embedded HTLC contract do not affect swap states.,PRIO1, +Only one counter deposit is accepted,Only the first valid counter deposit is accepted for a swap.,PRIO1, +Swap initiator must be recipient of counter deposit,The swap initiator address must be the recipient of the counter deposit.,PRIO1, +Counter deposit sender must be counterparty,The counter deposit's sender must be the recipient of the initial deposit.,PRIO1, +Counter deposit uses same hash type,The counter deposit must use the same hash type as the initial deposit.,PRIO1, +Proxy unlocked deposit,The swap item of a proxy unlocked deposit is marked as completed.,PRIO1, +Swap expiration,A swap is marked as expired 10 minutes before the counter deposit expires.,PRIO1, +Swap state maintained after restart of Syrius,A swap's state is maintained if Syrius is closed and reopened.,PRIO1, +,,, +Deleting swap items,,, +Deleting active swap,An active swap cannot be deleted from the swaps list.,PRIO1, +Deleting reclaimable swap,A reclaimable swap cannot be deleted from the swaps list.,PRIO1, +Deleting swap history,Deleting the swap history deletes all swap items except active and reclaimable swaps.,PRIO1, +Deleting swap history by chain ID,Deleting the swap history only deletes the swap items that have the same chain ID as the the current connected node.,PRIO1, +Deleting completed swap,A completed swap item can be deleted from the swaps list.,PRIO2, +Deleting unsuccessful swap,An unsuccessful swap item can be deleted from the swaps list.,PRIO2, +Delete swap confirmation,Confirmation is asked before deleting a swap item from the swaps list.,PRIO2, +,,, +Reclaim functionality,,, +Reclaim funds after started swap fails,"Start swap and wait for the initial deposit to expire, then reclaim funds.",PRIO1, +Reclaim funds after joined swap fails,"Join swap and wait for the counter deposit to expire, then reclaim funds.",PRIO1, +,,, +Recover functionality,,, +Recover funds of any expired deposit,"Start or join a swap and wait for the deposit to expire, then recover funds.",PRIO1, +,,, +Miscellaneous,,, +Swap preimage not shown to user,The swap's preimage is not shown to the user until the swap is no longer active.,PRIO1, +Show warning if deposited token is not in favorites,Show a warning to the user if a swap's token is not in their favorites (except ZNN and QSR).,PRIO1, +Only show swap items that match node chain ID,Only show swap items in the swaps list that match the connected node's chain ID.,PRIO1, +Disallow swap start & join when generating plasma,"When plasma is being generated, disallow starting and joining a swap.",PRIO2, +Swap tutorial link,The swap tutorial link opens the swap tutorial in a browser.,PRIO3, diff --git a/tests/walletconnect/walletconnect-tests.csv b/tests/walletconnect/walletconnect-tests.csv new file mode 100644 index 00000000..c2f99a58 --- /dev/null +++ b/tests/walletconnect/walletconnect-tests.csv @@ -0,0 +1,24 @@ +Version:,Environment:,, +Date:,Tester:,, +,,, +Test case,Description,Priority,Result +Pairing,,, +Create pair with wc url,Create a walletconnect pair by manually connecting to a wc url.,PRIO1, +Create pair with on-screen QR scanner,Create a walletconnect pair by scanning a QR with the on-screen QR scanner.,PRIO2, +Create pair with camera QR scanner,Create a walletconnect pair by scanning a QR with the camera QR scanner.,PRIO2, +Disconenct a pairing from syrius,Disconnect a pairing by removing it from the WalletConnect Pairing List.,PRIO1, +Disconnect a pairing from third-party app,Disconnect a pairing by removing it from third-party application and verify its removal from the Pairing List.,PRIO1, +Expired pairing,An expired pairing cannot be used to start a session.,PRIO1, +,,, +Sessions,,, +Approve session,Approve a session and verify the acknowledged session in the WalletConnect Sessions List.,PRIO1, +Reject session,Reject a session and verify inactive pairing.,PRIO1, +Expired session,An expired session cannot be used to send requests.,PRIO1, +,,, +Requests,,, +Approve request,Approve a request and verify is has been approved.,PRIO1, +Reject request,Reject a request and verify it has been rejected.,PRIO1, +,,, +Miscellaneous,,, +Pairings and sessions are deleted on delete cache,Delete cache and verify pairings and sessions are deleted.,PRIO2, +Pairings and sessions are deleted on wallet reset,Reset and reinitialize wallet and verify pairings and sessions are deleted.,PRIO2, \ No newline at end of file diff --git a/udev/20-hw1.rules b/udev/20-hw1.rules new file mode 100644 index 00000000..5928fd42 --- /dev/null +++ b/udev/20-hw1.rules @@ -0,0 +1,14 @@ +# HW.1 / Nano +SUBSYSTEMS=="usb", ATTRS{idVendor}=="2581", ATTRS{idProduct}=="1b7c|2b7c|3b7c|4b7c", TAG+="uaccess", TAG+="udev-acl" +# Blue +SUBSYSTEMS=="usb", ATTRS{idVendor}=="2c97", ATTRS{idProduct}=="0000|0000|0001|0002|0003|0004|0005|0006|0007|0008|0009|000a|000b|000c|000d|000e|000f|0010|0011|0012|0013|0014|0015|0016|0017|0018|0019|001a|001b|001c|001d|001e|001f", TAG+="uaccess", TAG+="udev-acl" +# Nano S +SUBSYSTEMS=="usb", ATTRS{idVendor}=="2c97", ATTRS{idProduct}=="0001|1000|1001|1002|1003|1004|1005|1006|1007|1008|1009|100a|100b|100c|100d|100e|100f|1010|1011|1012|1013|1014|1015|1016|1017|1018|1019|101a|101b|101c|101d|101e|101f", TAG+="uaccess", TAG+="udev-acl" +# Aramis +SUBSYSTEMS=="usb", ATTRS{idVendor}=="2c97", ATTRS{idProduct}=="0002|2000|2001|2002|2003|2004|2005|2006|2007|2008|2009|200a|200b|200c|200d|200e|200f|2010|2011|2012|2013|2014|2015|2016|2017|2018|2019|201a|201b|201c|201d|201e|201f", TAG+="uaccess", TAG+="udev-acl" +# HW2 +SUBSYSTEMS=="usb", ATTRS{idVendor}=="2c97", ATTRS{idProduct}=="0003|3000|3001|3002|3003|3004|3005|3006|3007|3008|3009|300a|300b|300c|300d|300e|300f|3010|3011|3012|3013|3014|3015|3016|3017|3018|3019|301a|301b|301c|301d|301e|301f", TAG+="uaccess", TAG+="udev-acl" +# Nano X +SUBSYSTEMS=="usb", ATTRS{idVendor}=="2c97", ATTRS{idProduct}=="0004|4000|4001|4002|4003|4004|4005|4006|4007|4008|4009|400a|400b|400c|400d|400e|400f|4010|4011|4012|4013|4014|4015|4016|4017|4018|4019|401a|401b|401c|401d|401e|401f", TAG+="uaccess", TAG+="udev-acl" +# Ledger Test +SUBSYSTEMS=="usb", ATTRS{idVendor}=="2c97", ATTRS{idProduct}=="0005|5000|5001|5002|5003|5004|5005|5006|5007|5008|5009|500a|500b|500c|500d|500e|500f|5010|5011|5012|5013|5014|5015|5016|5017|5018|5019|501a|501b|501c|501d|501e|501f", TAG+="uaccess", TAG+="udev-acl" \ No newline at end of file diff --git a/udev/README.md b/udev/README.md new file mode 100644 index 00000000..c2d4f495 --- /dev/null +++ b/udev/README.md @@ -0,0 +1,19 @@ +# udev rules + +This directory contains all of the udev rules for the supported devices as retrieved from vendor websites and repositories. +These are necessary for the devices to be reachable on linux environments. + + - `20-hw1.rules` (Ledger): https://github.com/LedgerHQ/udev-rules/blob/master/20-hw1.rules + +# Usage + +Apply these rules by copying them to `/etc/udev/rules.d/` and notifying `udevadm`. +Your user will need to be added to the `plugdev` group, which needs to be created if it does not already exist. + +```Shell + sudo cp udev/*.rules /etc/udev/rules.d/ && \ + sudo udevadm trigger && \ + sudo udevadm control --reload-rules && \ + sudo groupadd plugdev && \ + sudo usermod -aG plugdev `whoami` +``` \ No newline at end of file diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt index 04781fcc..3313166a 100644 --- a/windows/CMakeLists.txt +++ b/windows/CMakeLists.txt @@ -106,10 +106,18 @@ string(REPLACE "\"rootUri\": \"file:///" "" ZNN_SDK_DART_PATH "${ZNN_SDK_DART_PA string(REPLACE "/\"," "" ZNN_SDK_DART_PATH "${ZNN_SDK_DART_PATH}") string(STRIP "${ZNN_SDK_DART_PATH}" ZNN_SDK_DART_PATH) +file(STRINGS "${SYRIUS_PROJECT_DIRECTORY}/.dart_tool/package_config.json" + ZNN_LEDGER_DART_PATH REGEX "(file:).*(znn_ledger_dart).*\\/\"" ) + +string(REPLACE "\"rootUri\": \"file:///" "" ZNN_LEDGER_DART_PATH "${ZNN_LEDGER_DART_PATH}") +string(REPLACE "/\"," "" ZNN_LEDGER_DART_PATH "${ZNN_LEDGER_DART_PATH}") +string(STRIP "${ZNN_LEDGER_DART_PATH}" ZNN_LEDGER_DART_PATH) + list(APPEND SYRIUS_LIBRARIES "${SYRIUS_PROJECT_DIRECTORY}/lib/embedded_node/blobs/libznn.dll" "${ZNN_SDK_DART_PATH}/lib/src/argon2/blobs/argon2_ffi_plugin.dll" "${ZNN_SDK_DART_PATH}/lib/src/pow/blobs/libpow_links.dll" + "${ZNN_LEDGER_DART_PATH}/lib/src/ledger/blobs/libledger_ffi.dll" ) foreach(znn_library ${SYRIUS_LIBRARIES}) diff --git a/windows/flutter/CMakeLists.txt b/windows/flutter/CMakeLists.txt index b02c5485..86edc67b 100644 --- a/windows/flutter/CMakeLists.txt +++ b/windows/flutter/CMakeLists.txt @@ -9,6 +9,11 @@ include(${EPHEMERAL_DIR}/generated_config.cmake) # https://github.com/flutter/flutter/issues/57146. set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") +# Set fallback configurations for older versions of the flutter tool. +if (NOT DEFINED FLUTTER_TARGET_PLATFORM) + set(FLUTTER_TARGET_PLATFORM "windows-x64") +endif() + # === Flutter Library === set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") @@ -91,7 +96,7 @@ add_custom_command( COMMAND ${CMAKE_COMMAND} -E env ${FLUTTER_TOOL_ENVIRONMENT} "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" - windows-x64 $ + ${FLUTTER_TARGET_PLATFORM} $ VERBATIM ) add_custom_target(flutter_assemble DEPENDS