From ffaa90c17812f9fd7f7c0835269b4ddb4a6334c9 Mon Sep 17 00:00:00 2001 From: maznnwell <181888996+maznnwell@users.noreply.github.com> Date: Mon, 20 Jan 2025 11:41:41 +0300 Subject: [PATCH] Rearchitecture transfer (#135) * feat: Create dev run configuration * refactor: dashboard blocs. * mods: delegation cubit. tests. * mods: delegation cubit - tests. * feat: Improve the DashboardCubit class * feat: Finish the delegation widgets * feat: Use a CardData object to hold the title and description of a card * feat: Fill up the balance widgets * fix: Assign the correct card data to the CardType.balance value * feat: Improve the DashboardCubit so that the cubits that implement it can specify the state * feat: Fill up the dual coin stats widgets * fix: Navigate after the current frame finished rendering * fix: Apply fixes with dart fix --apply * fix: Use the super parameters * refactor: Replace deprecated methods or classes * feat: Use very_good_analysis for linter rules and solve some issues with dart fix --apply * feat: Add localization * feat: Exemplify how to use AppLocalizations * feat: Enable l10n for the dev entry point also * fix: Regenerate the windows folder * refactor: Delete unused widgets * feat: Add documentation of public members inside the dual_coin_stats folder * feat: Replace Pillars with PillarsCard * feat: Replace RealtimeStatistics with RealtimeStatisticsCard * feat: Fill up the sentinels widgets * refactor: Change fields' order * fix: Documentation * feat: Fill the staking widgets * fix: Documentation * feat: Fill up the total hourly transactions widgets * feat: Delete unused blocs * feat: Tweak balance features * refactor: Delete unused bloc * feat: Create the node sync status feature * feat: Delete unused class * feat: Adapt Transfer widget to the new architecture * chore: Redo dashboard.dart exports * chore: Delete unused feature balance_dashboard * feat: Use localization version strings for generating CardData objects * chore: Improve documentation * chore: Rename CubitStatus class to DashboardStatus * chore: Improve documentation of the balance feature * feat: transfer BLoC rearchitecture + localizations * chore: Improve documentation and solve linter issues * feat: transfer BLoC rearchitecture complete. added receive_transaction & transfer_widget_balance * feat: Make the DashboardCubit a HydratedCubit * feat: Make custom exceptions serializable and move them to separated feature folders * feat: Structure the error handling mechanism * feat: Localize some strings * refactor: Make custom exceptions extend from DashboardCubitException * refactor: Restructure new code in a single folder * transfer tab unit testing wip * transfer tab unit testing wip * chore: Move node_sync_status inside features folder * chore: Improve documentation * feat: Add always_specify_types to the linter rules and run dart fix * chore: Solve linter hints in rearchitecture folder * chore: Improve documentation * chore: Create and use the state_copy_with documentation template * feat: transfer BLoC rearchitecture complete. added receive_transaction & transfer_widget_balance * feat: Add tests for delegation cubit * feat: Add asserts for the TimerState class * feat: Restructure exceptions * feat: Improve error logging * chore: Update Zenon SDK * chore: Delete flutter_lints dependency * chore: Update dependencies * feat: Change the interval at which the node sync status is fetched * chore: Rename and change location of a refresh interval constant * feat: Don't emit the loading state if the current one has a success status * feat: Add required named parameters to the TimerCubit constructor * feat: Run dart format in rearchitecture folder * feat: Delete the CubitException class * testing dashboard wip * chore: Improve documentation * testing dashboard wip * feat: Create constants for specific UI gaps * transfer tab wip * feat: dashboard unit testing added * removed unused import * node sync status tests added * feat: Create the new app themes and improve the UI of the back of the new card scaffold * feat: Create a cubit for hiding the front widget of the NewCardScaffold * feat: Log errors * feat: Make password field obscure text by default * feat: Create the newTheme and isDarkMode getters to the BuildContextExtension * feat: Rename local variable * feat: Delete the font specification from the new themes * fix: Getting setting and getting the new theme * chore: Rename 'newTheme' getter to 'theme' * feat: Adapt widgets to the new theme data * chore: Rename getter 'theme' to 'themeData' * refactor: Cubit initializations * chore: Use documentation templates for cubit state's fromJson and toJson * fix: Some tests * feat: json serialization added * feat: unit tests reworked; comments add * chore: Improve documentation * feat: Align the text field and the button on the center axis * feat: unit tests reworked; comments add * chore: Repair dart analysis hints * feat: dependency injection helpers added * feat: unit tests added * todo rewrite * feat: Create a bloc to manage the 'Send' and 'Receive' card dimensions and move the send payment bloc files * refactor: Rename 'DimensionCard' class to 'CardDimension' * refactor: Rename feature transfer_balance to multiple_balance * feat: Tweak multiple balance feature * feat: Create the SendCard widget * feat: Create the other send medium widgets * feat: Use the new send payment bloc * feat: Rename the new send payment feature to send transaction * feat: Use only one version of send and receive widgets - WIP * feat: Modify the 'Send' large widget and tweak send transaction bloc * feat: Have only one variant of send widget * refactor: Improve code styling * feat: Sketch the new receive feature * refactor: Add some missing exports * feat: Finish receive feature * fix: Tests from send feature * feat: Delete some old blocs * fix: Test * refactor: Move the multiple balance feature directory * refactor: Rename CubitFailureException to FailureException * feat: Sketch the latest transactions feature * feat: Create a new variant of infinite table * feat: Tweak the latest transaction table design * feat: Regenerate file * feat: Transform pending transactions cubit into a bloc * fix: Tests * feat: Redone 'Pending Transactions' card design * feat: Use some of the global constants * refactor: Delete unused blocs and widgets * feat: Refresh latest and pending transactions list after a block was received * feat: Create the LatestTransactionsMoreRequested event * feat: Create a generic infinite list bloc * fix: Latest transactions bloc tests * refactor: Make PendingTransactionsBloc extend from InfiniteListBloc * feat: Improve tests * feat: Make the CopyToClipboardIcon an IconButton * feat: Refresh pending transactions list when an unreceived transaction hash is found * feat: Use the parameter for the page size * feat: Log bloc errors * feat: Always feed the new data to the view when the InfiniteListRequested event is called * feat: Refresh the multiple balance bloc data after a new address was generated * refactor: Delete unused bloc * feat: Add RefreshBlocMixin to the InfiniteListBloc class * feat: Add the hash of the new block in the transfer notification details * refactor: Improve InfiniteScrollTable and InfiniteScrollTableCell classes * fix: Show the proper message when there are no more new items * feat: Add documentation * fix: Dart analysis hint * feat: Add documentation * feat: Use icons from the SDK * feat: Also refresh the MultipleBalanceBloc stream when needed * feat: Add some documentation * fix: Some Dart Analysis hints * feat: Create new send and receive transaction icons * feat: Remove chip * feat: Add tooltip * feat: Tweak asset column * refactor: Create separate column classes * refactor: Create column types * feat: Tweak the asset cell * feat: Tweak padding * feat: Tweak text style * feat: Change the color of columns * feat: Change how the date is formatted * feat: Enable searching and sorting the dropdown items * feat: Make ZNN the default token in the receive card * feat: Tweak tokens initialization * feat: Make methods non-static, so that they can be mocked * fix: Dart analysis hints * feat: Improve the sorting functions * refactor: Remove unused imports * refactor: Create separete cell classes * feat: Add some tooltips * fix: Tests * fix: Receive QR code * feat: Remove styling from left side chart titles and use the default generated Y interval * feat: Revert some Windows changes * feat: Associated a custom URL deep linking scheme with the app - on Windows * feat: Restore bundling local libraries * feat: Restore updating Git metadata * fix: Fetching the dlls that will be installed in the Windows app folder and re-enable the generation of git metadata * shell script modified * build generated changes * fix: Fetching the dlls that will be installed in the Linux app folder * fix: Finding the correct path for .so files * feat: Update znn sdk and ledger * feat: Reset local git metadata * feat: Init kNumOfPillars * feat: Upgrade dependecies for Flutter 3.27 * fix: Seed input fields * fix: Errors after merge * feat: Decrease the size of copy to clipboard icon inside the tables * feat: Get the type of the tx from the transaction block * feat: Disable filtering of dropdown entries and add menu height * feat: Improve Send and Receive cards description * feat: Add buttons to share and save the receive QR * feat: Remove local Git metadata * feat: Use the data from the network to hardcode ZNN and QSR coins * feat: Show the coins first in the ZtsDropdown * feat: Add a prefix to the token labels * feat: Delete unused image * feat: Localize string * feat: Change the logic of showDialogWithNoAndYesOptions function * feat: Check if the bloc emitter is closed * feat: Refresh pending txs when changing the default address * feat: Sort also the tokens inside the 'Send' card and improve sorting of the assets * feat: Add support for refreshing the token list * feat: Add support for refreshing the 'Send' card data * feat: Change the ZNN, QSR, token and error colors * feat: Add the token standard to the coin label * feat: Add an icon for the coin label * feat: Check if splash initialization has been done * fix: Register the latest transactions bloc upper in the widget tree * feat: Move registering singletons higher up the widget tree * feat: Change the token label in the dropdown * feat: Add missing end of line * feat: Override function * feat: Add files to .gitignore * feat: Use the token symbol instead of the name --------- Co-authored-by: kossmmos <184493470+kossmmos@users.noreply.github.com> --- .gitignore | 5 +- analysis_options.yaml | 3 +- assets/images/qr_code_child_image_znn.png | Bin 8563 -> 0 bytes assets/images/qr_code_child_image_znn_cut.png | Bin 0 -> 5952 bytes lib/blocs/accelerator/create_phase_bloc.dart | 2 +- .../accelerator/create_project_bloc.dart | 4 +- .../accelerator/submit_donation_bloc.dart | 2 +- lib/blocs/accelerator/update_phase_bloc.dart | 2 +- lib/blocs/accelerator/vote_project_bloc.dart | 2 +- lib/blocs/auto_receive_tx_worker.dart | 54 ++- lib/blocs/auto_unlock_htlc_worker.dart | 2 +- lib/blocs/blocs.dart | 1 - .../htlc_swap/complete_htlc_swap_bloc.dart | 4 +- .../htlc_swap/join_htlc_swap_bloc.dart | 4 +- .../reclaim_htlc_swap_funds_bloc.dart | 4 +- .../htlc_swap/start_htlc_swap_bloc.dart | 4 +- lib/blocs/pillars/delegate_button_bloc.dart | 2 +- .../pillars/disassemble_pillar_bloc.dart | 4 +- lib/blocs/pillars/pillars_deploy_bloc.dart | 4 +- .../pillars/pillars_deposit_qsr_bloc.dart | 4 +- .../pillars/pillars_withdraw_qsr_bloc.dart | 4 +- lib/blocs/pillars/undelegate_button_bloc.dart | 2 +- lib/blocs/pillars/update_pillar_bloc.dart | 2 +- lib/blocs/plasma/cancel_plasma_bloc.dart | 4 +- lib/blocs/plasma/plasma_options_bloc.dart | 4 +- lib/blocs/refresh_bloc_mixin.dart | 14 +- .../sentinels/disassemble_button_bloc.dart | 4 +- .../sentinels/sentinel_deposit_qsr_bloc.dart | 4 +- .../sentinels/sentinel_register_bloc.dart | 4 +- .../sentinels/sentinel_withdraw_qsr_bloc.dart | 4 +- lib/blocs/staking/cancel_stake_bloc.dart | 4 +- lib/blocs/staking/staking_options_bloc.dart | 4 +- lib/blocs/tokens/burn_token_bloc.dart | 4 +- lib/blocs/tokens/issue_token_bloc.dart | 4 +- lib/blocs/tokens/mint_token_bloc.dart | 4 +- lib/blocs/tokens/tokens.dart | 2 - lib/blocs/tokens/tokens_bloc.dart | 24 -- lib/blocs/tokens/transfer_ownership_bloc.dart | 2 +- .../transfer/latest_transactions_bloc.dart | 17 - .../transfer/pending_transactions_bloc.dart | 17 - .../transfer/receive_transaction_bloc.dart | 17 - lib/blocs/transfer/send_payment_bloc.dart | 50 --- lib/blocs/transfer/transfer.dart | 4 - .../transfer_widgets_balance_bloc.dart | 36 -- .../wallet_connect/chains/nom_service.dart | 59 +-- lib/l10n/app_en.arb | 45 +- lib/main.dart | 261 +++++++----- lib/main_dev.dart | 257 ++++++----- lib/rearchitecture/features/features.dart | 5 + .../bloc/latest_transactions_bloc.dart | 35 ++ .../latest_transactions.dart | 2 + .../view/latest_transactions_card.dart | 303 +++++++++++++ .../latest_transactions/view/view.dart | 1 + .../bloc/multiple_balance_bloc.dart | 89 ++++ .../bloc/multiple_balance_bloc.g.dart | 36 ++ .../bloc/multiple_balance_event.dart | 19 + .../bloc/multiple_balance_state.dart | 62 +++ .../multiple_balance/multiple_balance.dart | 1 + .../bloc/pending_transactions_bloc.dart | 38 ++ .../pending_transactions.dart | 2 + .../view/pending_transactions_card.dart | 253 +++++++++++ .../pending_transactions/view/view.dart | 1 + .../features/receive/receive.dart | 2 + .../features/receive/view/receive_card.dart | 32 ++ .../features/receive/view/view.dart | 1 + .../receive/widgets/receive_error.dart | 17 + .../receive/widgets/receive_initial.dart | 13 + .../receive/widgets/receive_populated.dart | 145 +++++++ .../features/receive/widgets/widgets.dart | 3 + .../features/send/bloc/bloc.dart | 1 + .../send_transaction_bloc.dart | 129 ++++++ .../send_transaction_bloc.g.dart | 36 ++ .../send_transaction_event.dart | 53 +++ .../send_transaction_state.dart | 64 +++ lib/rearchitecture/features/send/send.dart | 3 + .../features/send/view/send_card.dart | 36 ++ .../features/send/view/view.dart | 1 + .../features/send/widgets/send_button.dart | 18 + .../features/send/widgets/send_empty.dart | 15 + .../features/send/widgets/send_error.dart | 16 + .../features/send/widgets/send_loading.dart | 13 + .../features/send/widgets/send_populated.dart | 369 ++++++++++++++++ .../features/send/widgets/widgets.dart | 5 + .../features/tokens/cubit/tokens_cubit.dart | 51 +++ .../features/tokens/cubit/tokens_cubit.g.dart | 30 ++ .../features/tokens/cubit/tokens_state.dart | 62 +++ .../features/transfer/view/transfer_card.dart | 12 +- .../cubit/receive_transaction_cubit.dart | 63 +++ .../cubit/receive_transaction_cubit.g.dart | 34 ++ .../cubit/receive_transaction_state.dart | 66 +++ .../utils/bloc_observers/bloc_observers.dart | 1 + .../bloc_observers/custom_bloc_observer.dart | 11 + lib/rearchitecture/utils/blocs/blocs.dart | 1 + .../infinite_list/infinite_list_bloc.dart | 173 ++++++++ .../infinite_list/infinite_list_bloc.g.dart | 37 ++ .../infinite_list/infinite_list_event.dart | 40 ++ .../infinite_list/infinite_list_state.dart | 87 ++++ lib/rearchitecture/utils/constants/api.dart | 5 + .../utils/constants/app_sizes.dart | 4 + .../utils/constants/constants.dart | 1 + .../cubits/hide_widget/hide_widget_cubit.dart | 2 +- .../cubits/hide_widget/hide_widget_state.dart | 10 +- .../utils/cubits/timer_cubit.dart | 6 +- .../utils/exceptions/exceptions.dart | 2 +- .../utils/exceptions/failure_exception.dart | 35 ++ .../utils/exceptions/failure_exception.g.dart | 17 + .../utils/exceptions/syrius_exception.dart | 4 +- .../utils/extensions/extensions.dart | 1 + .../utils/extensions/token_extension.dart | 7 + lib/rearchitecture/utils/functions/api.dart | 11 + .../utils/functions/functions.dart | 2 + .../utils/functions/tokens.dart | 22 + .../utils/models/card/card_type.dart | 41 ++ lib/rearchitecture/utils/utils.dart | 3 + .../utils/widgets/dropdowns/dropdowns.dart | 2 + .../dropdowns/new_addresses_dropdown.dart | 88 ++++ .../utils/widgets/dropdowns/zts_dropdown.dart | 98 +++++ .../cells/address_cell.dart | 16 + .../cells/amount_cell.dart | 29 ++ .../cells/analysis_options.yaml | 3 + .../cells/asset_cell.dart | 27 ++ .../infinite_scroll_table/cells/cells.dart | 7 + .../cells/date_cell.dart | 29 ++ .../cells/hash_cell.dart | 19 + .../cells/receive_cell.dart | 31 ++ .../cells/type_cell.dart | 40 ++ .../widgets/infinite_scroll_table/export.dart | 2 + .../infinite_scroll_table.dart | 185 ++++++++ .../infinite_scroll_table_cell.dart | 106 +++++ .../infinite_scroll_table_column.dart | 52 +++ .../infinite_scroll_table_column_type.dart | 35 ++ .../utils/widgets/new_card_scaffold.dart | 2 - lib/rearchitecture/utils/widgets/widgets.dart | 4 + lib/screens/splash_screen.dart | 28 +- lib/services/web3wallet_service.dart | 6 +- lib/utils/account_block_utils.dart | 5 +- lib/utils/address_utils.dart | 18 +- lib/utils/app_colors.dart | 6 +- lib/utils/color_utils.dart | 8 +- lib/utils/format_utils.dart | 28 +- lib/utils/widget_utils.dart | 21 +- lib/utils/zts_utils.dart | 52 +-- lib/widgets/main_app_container.dart | 116 +++-- .../p2p_swap_widgets/detail_row.dart | 7 +- .../p2p_swap_widgets/p2p_swaps_card.dart | 83 ++-- .../pillar_widgets/pillar_collect.dart | 2 +- .../sentinel_widgets/sentinel_collect.dart | 2 +- .../settings_widgets/addresses.dart | 66 +-- .../settings_widgets/security.dart | 11 +- .../staking_widgets/stake_collect.dart | 2 +- .../token_widgets/token_card.dart | 4 +- .../token_widgets/token_stepper.dart | 2 + .../latest_transactions.dart | 364 ---------------- .../latest_transactions_transfer_widget.dart | 373 ---------------- .../pending_transactions.dart | 292 ------------- .../receive/receive_large.dart | 241 ----------- .../receive/receive_medium.dart | 233 ---------- .../receive/receive_small.dart | 49 --- .../transfer_widgets/send/send_large.dart | 401 ------------------ .../transfer_widgets/send/send_medium.dart | 339 --------------- .../transfer_widgets/send/send_small.dart | 54 --- .../transfer_widgets/transfer_widgets.dart | 10 +- .../wallet_connect_qr_card.dart | 2 +- .../reusable_widgets/buttons/buttons.dart | 2 +- .../buttons/copy_to_clipboard_button.dart | 65 +++ .../buttons/send_payment_button.dart | 30 -- .../transfer_toggle_card_size_button.dart | 11 +- .../reusable_widgets/custom_table.dart | 12 +- lib/widgets/reusable_widgets/dialogs.dart | 20 +- .../dropdown/coin_dropdown.dart | 97 ++--- .../icons/copy_to_clipboard_icon.dart | 66 --- lib/widgets/reusable_widgets/icons/icons.dart | 1 - .../infinite_scroll_table.dart | 12 +- .../input_fields/amount_suffix_widgets.dart | 11 +- .../standard_fluid_layout.dart | 42 +- .../reusable_widgets/receive_qr_image.dart | 284 ++++++------- .../reusable_widgets/settings_address.dart | 5 +- .../reusable_widgets/settings_node.dart | 148 ++++--- .../dashboard_tab_child.dart | 13 +- .../tab_children_widgets/lock_tab_child.dart | 6 + .../notifications_tab_child.dart | 2 +- .../transfer_tab_child.dart | 127 ++---- macos/Runner/AppDelegate.swift | 4 + pubspec.lock | 254 +++++------ pubspec.yaml | 10 +- test/balance/cubit/balance_cubit_test.dart | 10 +- .../cubit/delegation_cubit_test.dart | 10 +- .../cubit/dual_coin_stats_cubit_test.dart | 14 +- .../latest_transactions_bloc_test.dart | 273 ++++++++++++ .../multiple_balance_bloc_test.dart | 181 ++++++++ .../cubit/node_sync_status_cubit_test.dart | 16 +- .../pending_transactions_bloc_test.dart | 273 ++++++++++++ test/pillars/cubit/pillars_cubit_test.dart | 19 +- .../cubit/realtime_statistics_cubit_test.dart | 9 +- .../send/bloc/send_transaction_bloc_test.dart | 229 ++++++++++ .../sentinels/cubit/sentinels_cubit_test.dart | 13 +- test/staking/cubit/staking_cubit_test.dart | 9 +- .../total_hourly_transactions_cubit_test.dart | 13 +- .../receive_transaction_cubit_test.dart | 154 +++++++ 199 files changed, 5890 insertions(+), 3779 deletions(-) delete mode 100644 assets/images/qr_code_child_image_znn.png create mode 100644 assets/images/qr_code_child_image_znn_cut.png delete mode 100644 lib/blocs/tokens/tokens_bloc.dart delete mode 100644 lib/blocs/transfer/latest_transactions_bloc.dart delete mode 100644 lib/blocs/transfer/pending_transactions_bloc.dart delete mode 100644 lib/blocs/transfer/receive_transaction_bloc.dart delete mode 100644 lib/blocs/transfer/send_payment_bloc.dart delete mode 100644 lib/blocs/transfer/transfer.dart delete mode 100644 lib/blocs/transfer/transfer_widgets_balance_bloc.dart create mode 100644 lib/rearchitecture/features/latest_transactions/bloc/latest_transactions_bloc.dart create mode 100644 lib/rearchitecture/features/latest_transactions/latest_transactions.dart create mode 100644 lib/rearchitecture/features/latest_transactions/view/latest_transactions_card.dart create mode 100644 lib/rearchitecture/features/latest_transactions/view/view.dart create mode 100644 lib/rearchitecture/features/multiple_balance/bloc/multiple_balance_bloc.dart create mode 100644 lib/rearchitecture/features/multiple_balance/bloc/multiple_balance_bloc.g.dart create mode 100644 lib/rearchitecture/features/multiple_balance/bloc/multiple_balance_event.dart create mode 100644 lib/rearchitecture/features/multiple_balance/bloc/multiple_balance_state.dart create mode 100644 lib/rearchitecture/features/multiple_balance/multiple_balance.dart create mode 100644 lib/rearchitecture/features/pending_transactions/bloc/pending_transactions_bloc.dart create mode 100644 lib/rearchitecture/features/pending_transactions/pending_transactions.dart create mode 100644 lib/rearchitecture/features/pending_transactions/view/pending_transactions_card.dart create mode 100644 lib/rearchitecture/features/pending_transactions/view/view.dart create mode 100644 lib/rearchitecture/features/receive/receive.dart create mode 100644 lib/rearchitecture/features/receive/view/receive_card.dart create mode 100644 lib/rearchitecture/features/receive/view/view.dart create mode 100644 lib/rearchitecture/features/receive/widgets/receive_error.dart create mode 100644 lib/rearchitecture/features/receive/widgets/receive_initial.dart create mode 100644 lib/rearchitecture/features/receive/widgets/receive_populated.dart create mode 100644 lib/rearchitecture/features/receive/widgets/widgets.dart create mode 100644 lib/rearchitecture/features/send/bloc/bloc.dart create mode 100644 lib/rearchitecture/features/send/bloc/send_transaction/send_transaction_bloc.dart create mode 100644 lib/rearchitecture/features/send/bloc/send_transaction/send_transaction_bloc.g.dart create mode 100644 lib/rearchitecture/features/send/bloc/send_transaction/send_transaction_event.dart create mode 100644 lib/rearchitecture/features/send/bloc/send_transaction/send_transaction_state.dart create mode 100644 lib/rearchitecture/features/send/send.dart create mode 100644 lib/rearchitecture/features/send/view/send_card.dart create mode 100644 lib/rearchitecture/features/send/view/view.dart create mode 100644 lib/rearchitecture/features/send/widgets/send_button.dart create mode 100644 lib/rearchitecture/features/send/widgets/send_empty.dart create mode 100644 lib/rearchitecture/features/send/widgets/send_error.dart create mode 100644 lib/rearchitecture/features/send/widgets/send_loading.dart create mode 100644 lib/rearchitecture/features/send/widgets/send_populated.dart create mode 100644 lib/rearchitecture/features/send/widgets/widgets.dart create mode 100644 lib/rearchitecture/features/tokens/cubit/tokens_cubit.dart create mode 100644 lib/rearchitecture/features/tokens/cubit/tokens_cubit.g.dart create mode 100644 lib/rearchitecture/features/tokens/cubit/tokens_state.dart create mode 100644 lib/rearchitecture/transfer/receive_transaction/cubit/receive_transaction_cubit.dart create mode 100644 lib/rearchitecture/transfer/receive_transaction/cubit/receive_transaction_cubit.g.dart create mode 100644 lib/rearchitecture/transfer/receive_transaction/cubit/receive_transaction_state.dart create mode 100644 lib/rearchitecture/utils/bloc_observers/bloc_observers.dart create mode 100644 lib/rearchitecture/utils/bloc_observers/custom_bloc_observer.dart create mode 100644 lib/rearchitecture/utils/blocs/blocs.dart create mode 100644 lib/rearchitecture/utils/blocs/infinite_list/infinite_list_bloc.dart create mode 100644 lib/rearchitecture/utils/blocs/infinite_list/infinite_list_bloc.g.dart create mode 100644 lib/rearchitecture/utils/blocs/infinite_list/infinite_list_event.dart create mode 100644 lib/rearchitecture/utils/blocs/infinite_list/infinite_list_state.dart create mode 100644 lib/rearchitecture/utils/constants/api.dart create mode 100644 lib/rearchitecture/utils/exceptions/failure_exception.dart create mode 100644 lib/rearchitecture/utils/exceptions/failure_exception.g.dart create mode 100644 lib/rearchitecture/utils/extensions/token_extension.dart create mode 100644 lib/rearchitecture/utils/functions/api.dart create mode 100644 lib/rearchitecture/utils/functions/functions.dart create mode 100644 lib/rearchitecture/utils/functions/tokens.dart create mode 100644 lib/rearchitecture/utils/widgets/dropdowns/dropdowns.dart create mode 100644 lib/rearchitecture/utils/widgets/dropdowns/new_addresses_dropdown.dart create mode 100644 lib/rearchitecture/utils/widgets/dropdowns/zts_dropdown.dart create mode 100644 lib/rearchitecture/utils/widgets/infinite_scroll_table/cells/address_cell.dart create mode 100644 lib/rearchitecture/utils/widgets/infinite_scroll_table/cells/amount_cell.dart create mode 100644 lib/rearchitecture/utils/widgets/infinite_scroll_table/cells/analysis_options.yaml create mode 100644 lib/rearchitecture/utils/widgets/infinite_scroll_table/cells/asset_cell.dart create mode 100644 lib/rearchitecture/utils/widgets/infinite_scroll_table/cells/cells.dart create mode 100644 lib/rearchitecture/utils/widgets/infinite_scroll_table/cells/date_cell.dart create mode 100644 lib/rearchitecture/utils/widgets/infinite_scroll_table/cells/hash_cell.dart create mode 100644 lib/rearchitecture/utils/widgets/infinite_scroll_table/cells/receive_cell.dart create mode 100644 lib/rearchitecture/utils/widgets/infinite_scroll_table/cells/type_cell.dart create mode 100644 lib/rearchitecture/utils/widgets/infinite_scroll_table/export.dart create mode 100644 lib/rearchitecture/utils/widgets/infinite_scroll_table/infinite_scroll_table.dart create mode 100644 lib/rearchitecture/utils/widgets/infinite_scroll_table/infinite_scroll_table_cell.dart create mode 100644 lib/rearchitecture/utils/widgets/infinite_scroll_table/infinite_scroll_table_column.dart create mode 100644 lib/rearchitecture/utils/widgets/infinite_scroll_table/infinite_scroll_table_column_type.dart delete mode 100644 lib/widgets/modular_widgets/transfer_widgets/latest_transactions/latest_transactions.dart delete mode 100644 lib/widgets/modular_widgets/transfer_widgets/latest_transactions/latest_transactions_transfer_widget.dart delete mode 100644 lib/widgets/modular_widgets/transfer_widgets/pending_transactions/pending_transactions.dart delete mode 100644 lib/widgets/modular_widgets/transfer_widgets/receive/receive_large.dart delete mode 100644 lib/widgets/modular_widgets/transfer_widgets/receive/receive_medium.dart delete mode 100644 lib/widgets/modular_widgets/transfer_widgets/receive/receive_small.dart delete mode 100644 lib/widgets/modular_widgets/transfer_widgets/send/send_large.dart delete mode 100644 lib/widgets/modular_widgets/transfer_widgets/send/send_medium.dart delete mode 100644 lib/widgets/modular_widgets/transfer_widgets/send/send_small.dart create mode 100644 lib/widgets/reusable_widgets/buttons/copy_to_clipboard_button.dart delete mode 100644 lib/widgets/reusable_widgets/buttons/send_payment_button.dart delete mode 100644 lib/widgets/reusable_widgets/icons/copy_to_clipboard_icon.dart create mode 100644 test/latest_transactions/latest_transactions_bloc_test.dart create mode 100644 test/multiple_balance/multiple_balance_bloc_test.dart create mode 100644 test/pending_transactions/pending_transactions_bloc_test.dart create mode 100644 test/send/bloc/send_transaction_bloc_test.dart create mode 100644 test/transfer/receive_transaction/receive_transaction_cubit_test.dart diff --git a/.gitignore b/.gitignore index 6b7cc2b9..a419bb01 100644 --- a/.gitignore +++ b/.gitignore @@ -130,4 +130,7 @@ app.*.symbols !**/ios/**/default.perspectivev3 !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages !/dev/ci/**/Gemfile.lock -!.vscode/settings.json \ No newline at end of file +!.vscode/settings.json +/macos/libargon2_ffi.dylib +/macos/libledger_ffi.dylib +/macos/libpow_links.dylib diff --git a/analysis_options.yaml b/analysis_options.yaml index ed7808e3..21c5c133 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -18,4 +18,5 @@ linter: always_specify_types: true # This rule is in contradiction with always_specify_types, so it must be disabled if the other # one is enabled - omit_local_variable_types: false \ No newline at end of file + omit_local_variable_types: false + no_default_cases: false \ 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 deleted file mode 100644 index b23a6d80a74f2a49034049ab8cdb4a55e01ca3c8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 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 diff --git a/assets/images/qr_code_child_image_znn_cut.png b/assets/images/qr_code_child_image_znn_cut.png new file mode 100644 index 0000000000000000000000000000000000000000..73fa9bd22fc52fdc2c3caae1d950b39dbbd8b7d2 GIT binary patch literal 5952 zcmeHL`(G2+whv}%5~xESw)jYZ@QP>^DXo$zN+yIyEDVjdMNbO?KG4<{Bojo72qTyT zA4Rbil;9A(CDmKhqqRQJ;tjU+6vLbnuGm_OM;eBdDph=;*0Uz|Z@8cP!})yRGb`Wq z-D|DAXYIAuZ0j3D>IDBO{yZLUf<8^RkjL{IMTs$EvsRvUeIZGKljv1pYjbn25OZxwnj8UGZHpFP^K@2hbSZhs&0b@>w!Z`JRc zuKgLndnJnHi8YL0Xa&vJ@Nhu|k3xBVh=Mmxi183H$rFbDhwy*34kN$*cKJwQ;<&Cq zx;H)j{^?`OACJ1f-Y6k+<*}-Z(F28v8=Cn>p*s4S`El14bX%zB;HimsWHDsu-rBl< z9k1Ol!o>Pceen#Bib6Xi#)Vg9x8ra@zEG_Xwm!7d{NF_&O?cQq^S`jFqnmuvOu%xs z`?MBy^hqJ7{QyRs+{R(Qg7zf(uwm&F|J_lZh$~<^ht=q{#IAWskPal3dk)0sM#rkYJ5;~Fw3+`pn0$N7<8-DnNu*vqz2mWtsdQS@`a@M5 z_A|ISRQ)CHCG_Cd$bE5A%s#ySc?Fs;jMw@(n2igLRW`$Aw(< z3$m(L@ACO83mrJ94}91VgpCBnsxIyJ#jPb8^_-PS;4TiF5I1MB} zlVVjn>u+@jC~CyTQqjY;ZA>$dgF?O!P`oJRpeYm@AXHnc%4Vw8#n;zE0GID*&6_!> z4UHk=D>&%=KeT3rh=ZbVvqGr;yrXRvZr*O?koh3^*nzk?NW`gvK(KHs6>e7f$T7HC z;3FsE=GQn*!e-hzK}$?mc89S=)!?EtPK3P+%PhU~E`|OgF-k@2!SGz5#O}wL$(fz{ z$bsVzK4P~;d4RLNjhPJj964YVVF$oNm0Y#%8?kYrRSiklAuC=eQr}wNEX(N9M|#S- z18ixWtHC&mn5a}W)El?cvLsLqTuowD`z$B?PwLteoMmJ1uhqosKK2rdn4u+Jjvah~ zBBtiU_<{h6SkEB^GbmyS2+D%@DMIg4#ZW}FPnAg#lR0EiNVy>0VfZx4^)g2uTvdK| z#_JaKcO8u{v@z=4wRP#8OZ1F-TYcSv&d)W>6&oO3pKF;b+d0+e-=wY`)^_QmJZ}zE zyFROGT&^WnP_C$|Mlzo;QLgVhI3$R2_4rhC=hxhb&If-j;W1=z*g%A=7?Y7V!v+q@ zjAvxzhf?D%kvbZ&Ws1~8u#YHLGLV(3o%Eeb&YM1pEeq$kKZvk=;uU3qKOO?8%99?( z@?l6-qxG@(a$W~GcfFazCJ8xc2-yh9)KKf(kIbvDts}aw7gE8Hd#Ztv@Q{nu65|h6 z^}C+xV8~R}K#?tI1F&uN&JS|f?VL&ZO%*%ACj<`;T!Uo*1L_<$gR?Eq31=^g)G~{@ zjbH+}u+(c(nE+{Vl-Ss}hYkw&9F!IJndu-U1YO+sDIF6vkRIwenusq=>*Bo0@uiy1 z22I!X#Z-ByvS6Ij{3=XfvxPJH-#4ny$N8|k$i~!s0#lkXWTQaqLh{isg=1T)8bwv| z_ih`mrgSulI^^El+D$ua<0@;DHs-J~Vk3mWUI=(10=^bKCmiEfHAZ*H$0Vb#S=BP3 zdNh)qEA$B}cE7mzGqJHVl!g5Os^yZ=bw2WK;n>tzE~s~JY@RicuJqJ?u!gB_jINTe zoHUvAX_llsm)+T*?aFJ4#e=|Sas7<>tkyL#A3c6?Jg4cbtn2*RJj(;4+n2ZloeeL3S56!GAQN z5W2OV-#brEDhs4a!I@I_K)jERo&N1FdTJJDE8L^W+CLt~K?pa?h1|GWe)RmZiChkQ zG`k98o>`DSx$*SR`-%CkN%`o(F|OzF$Ns32r+qcF^qtK$9rF720NY|M3VGilDb5X3 zt+QLyvp)&KBvtZySAgwaha3psRLT1bbJ!CR9$v`$=AN2b{<~4^iO>PwW%cPrP4)b% zo@%HNXi*~9HW4@f(?|V(YZu!;UHa#PyTAR^Jv?xK|E5Q`?}fLgJ$-!t>k}&ra#+Dg z;N*&PqA^^jsF)`jt0W9TCA@o~Y=8yhy(1e<26(1uOlwEm-$pjZo&``m8U|pNaDx)S zqI|mO8^YVE5 zsKaM5&3rsW=k_ZWjm@^BwR%P%HB}@~^LJ+}LbuX^Dlcy`qA&=cX)g=+^3I8{O%hYZ zdBhgT&90&xv2E2U=pcM>ksW2swDA)Me>9Z6x;#tZ$Yf6MbMW1fYyy~nPf7+yd3kq* zSmv?Ae0Lbz#(`oo2f*J%*gmnT;!DJKsjt`Xe}Lh&RIJ; zBF9_g05;N#b~*S7DNOYDt=wj7NiYabWiYF8w_mG_{1Ia57GYmNEVE?f-(WdDMr?j$^76}Z3Lo)JIUADrJ&Roas=$jGj2m+vlECutQ2(h;I zR`g9Jd(_V$cw0vHSseTsB5X~XyYekWF=u6#;B7J4XLayXrDWeucu2^;26%{}dCOMx z?W3KQIz``B+IayU(07fqqiZslJjq$?Xe4gF2p~l#`PtF71xz!%lpH1dh9z53x`$4J zSP}q)cM}X?y@P*WN=_r(l^+SQSBh%R9OYjIAayhW#Y&_pLKDlzrX z!_6L@B3?oYL%kJrlF(qjf+(oI-aP0WHForl5UY}!>XR8|y~8P|(CfqA*$|xAjsk7G z#MBwd&fexof_}4d*jt;*RW3HPyk;=}h&<8QiFJ0=BpSQW*gxcI zMPC(-h4~=p0kqF_!m)gx@TzcwUy13=@x#syRF1^~mYf)sv&|<=fqgDZ_}PldGoe#4 zVL&>@ZFbJfGT{b3F+!@m(3lkJ+(YT^yrtAN7}wx%mLunshL+=t+|`Pcu=TI>4iWCW zcc^QPK5YrN|2f(mWL}jl!FHwhx`_%-;Y?g#4_%f|P;<3_L)KEkU9ChxiK%oO9T@5* zqr62Sc!<`gn}dgRh?-nFMXPMIQfF%&_Kv9WkZ0pksrmI_+=`V&u%%!0>~T1ML2QN3 zNGg0V#!eL2T_oial$bD?DN07plA6v)O`l20`H^07MOHx=J~&-f%IPG~YtU%r$+m$3=P(!4K`Gf3#gQVUWR8!0P(tQ-kj$(S(=H*lPr@0Og5dK4 zZR?yt&_NKC>qDJw2iS;YhaqzKOM!kFj5~U)P)W{W*;XuDh?%7(?C*zrDxr=TT4oqp zUd9VVs#36dNJ{PjozyfP1b9b)NXP|Z6NV*c_d&}Y?QA8$&vO)^wiBs#*L%>{`-BD1 z>B9?Lo>lMyS6y1d4TGyFBEYBHiP+{q=A^mRHFVDoN6+?bTeuLLX~h&)>?0}pqtD^J zF!sdCEErc1ND+vkW$Ib%9T}N#;YjaDNPGN+o}G^4&v6x}XS?IhbX>K^XDLgF{S&6F zvlR%jKVae!Uf79HdWKTm>#OLv8XxH+5q868z_^u;v{M1IOK}fE z8G=-_W^a6v`-+2MW6x7)rr2~wa<>cwSn%zR0vTDllOxRo1LHX-^FF4XDLw7&e*^;TBc1y<8M(Mq)I{jc&B4v%bhcT;UXuu$sPvLyUO1d4{tL-e*>kpXHH-+d z=a}gv0|+H|ryvS{NC3&*FA+t}@x$QoNI1(>IeMxcN0clB30s~0q?AH0!Ma)#C~6Vx>{>@sFm7&!qL$3$#T(49m=+A~k@}~qO4$siGEk@3ASL?+ zcJzr5E86KuilNZ5f3%_-;a443!O*M8?&dkT;HC!&ggK8uVcdWyN^2ZRGvUhPk|@{< z##Y!-IF65s3}%>U-!`sZp=-d(`$Ft!02C(BO*NQrz^@sqQaFzJX0Zv5W4>8pg5#JE zI|+_sz8Q+yJw&lM#hv#Wg}xN&h2vPT$4Hqki?BDgIGlG7#k6Gibv1?VzJUb7g$4bR z!TbnxdmYY=pv!b$hd}>!^sv)Jp(nz;MR0}my9Ilk##HWU?7tokI+^J~9e`F$h>5lC z27;-aRKiu7&>fDroiu-^&bAs+SbjyiBjDx=>WrAoMGPt%&0iLOp$;yH*6M6J3f&gr zg;J9Hl0gMmQwOf}>Xj_-DU>J`%Y$;}LKOW9t`LwF0g#*UOJ*k^_lv6Gb<_N-xmhiI zTyUz+u?XTz(6~Qe!1x6ZAeqS&Iuc=Mk>Y~VO2;B#wn57IBZ}N`yK4c39#tBkEC~AQ z9mgq}KiuBx`X}h1d>G&Xg;N5Kc*MwX2IGfDc!!hW5#=4m8NV0Brju~Hy0x$O$}XD! zQj~WMfc|1rP9)1qj=yln9~Vr6eGYGBqHo{()f7{M5(<5z(P0e41*7%t`5TdXP4Y*d4!?^^T9LZ$-PHoMG9V+No+H>GFL#`fyh)Qc@ zWss}wuj|f@2i*$$`6ilwRHS+UxmuB79fLcmc8`wRhV8R-Ptp>fj6Myf7FcfXlLWx6 OH&35T=vuY8)V~31KsoyW literal 0 HcmV?d00001 diff --git a/lib/blocs/accelerator/create_phase_bloc.dart b/lib/blocs/accelerator/create_phase_bloc.dart index d68e8bfc..6a2357e7 100644 --- a/lib/blocs/accelerator/create_phase_bloc.dart +++ b/lib/blocs/accelerator/create_phase_bloc.dart @@ -23,7 +23,7 @@ class CreatePhaseBloc extends BaseBloc { znnFundsNeeded, qsrFundsNeeded, ); - AccountBlockUtils.createAccountBlock(transactionParams, 'create phase') + AccountBlockUtils().createAccountBlock(transactionParams, 'create phase') .then( addEvent, ) diff --git a/lib/blocs/accelerator/create_project_bloc.dart b/lib/blocs/accelerator/create_project_bloc.dart index 3c142198..f5ed1792 100644 --- a/lib/blocs/accelerator/create_project_bloc.dart +++ b/lib/blocs/accelerator/create_project_bloc.dart @@ -23,12 +23,12 @@ class CreateProjectBloc extends BaseBloc { znnFundsNeeded, qsrFundsNeeded, ); - AccountBlockUtils.createAccountBlock( + AccountBlockUtils().createAccountBlock( transactionParams, 'creating project', ).then( (AccountBlockTemplate block) { - ZenonAddressUtils.refreshBalance(); + ZenonAddressUtils().refreshBalance(); addEvent(block); }, ).onError( diff --git a/lib/blocs/accelerator/submit_donation_bloc.dart b/lib/blocs/accelerator/submit_donation_bloc.dart index 4e6d1298..13423605 100644 --- a/lib/blocs/accelerator/submit_donation_bloc.dart +++ b/lib/blocs/accelerator/submit_donation_bloc.dart @@ -27,7 +27,7 @@ class SubmitDonationBloc extends BaseBloc { Future _sendDonationBlock( AccountBlockTemplate transactionParams,) async { - await AccountBlockUtils.createAccountBlock( + await AccountBlockUtils().createAccountBlock( transactionParams, 'donate for accelerator', ).then( diff --git a/lib/blocs/accelerator/update_phase_bloc.dart b/lib/blocs/accelerator/update_phase_bloc.dart index a66b8d08..e8b4e525 100644 --- a/lib/blocs/accelerator/update_phase_bloc.dart +++ b/lib/blocs/accelerator/update_phase_bloc.dart @@ -17,7 +17,7 @@ class UpdatePhaseBloc extends BaseBloc { znnFundsNeeded, qsrFundsNeeded, ); - AccountBlockUtils.createAccountBlock(transactionParams, 'update phase') + AccountBlockUtils().createAccountBlock(transactionParams, 'update phase') .then( addEvent, ) diff --git a/lib/blocs/accelerator/vote_project_bloc.dart b/lib/blocs/accelerator/vote_project_bloc.dart index 8a9a8153..5fbab60d 100644 --- a/lib/blocs/accelerator/vote_project_bloc.dart +++ b/lib/blocs/accelerator/vote_project_bloc.dart @@ -18,7 +18,7 @@ class VoteProjectBloc extends BaseBloc { pillarInfo.name, vote.index, ); - AccountBlockUtils.createAccountBlock( + AccountBlockUtils().createAccountBlock( transactionParams, 'vote for project', ) diff --git a/lib/blocs/auto_receive_tx_worker.dart b/lib/blocs/auto_receive_tx_worker.dart index e90ab23b..a92a5a6d 100644 --- a/lib/blocs/auto_receive_tx_worker.dart +++ b/lib/blocs/auto_receive_tx_worker.dart @@ -7,9 +7,9 @@ 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/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/constants.dart'; +import 'package:zenon_syrius_wallet_flutter/rearchitecture/features/features.dart'; +import 'package:zenon_syrius_wallet_flutter/rearchitecture/utils/blocs/blocs.dart'; +import 'package:zenon_syrius_wallet_flutter/utils/utils.dart'; import 'package:znn_sdk_dart/znn_sdk_dart.dart'; class AutoReceiveTxWorker extends BaseBloc { @@ -23,23 +23,25 @@ class AutoReceiveTxWorker extends BaseBloc { } Future autoReceiveTransactionHash( - Hash currentHash,) async { + Hash currentHash, + ) async { if (!running) { running = true; try { final Address toAddress = (await zenon!.ledger.getAccountBlockByHash(currentHash))!.toAddress; - final AccountBlockTemplate transactionParams = AccountBlockTemplate.receive( + final AccountBlockTemplate transactionParams = + AccountBlockTemplate.receive( currentHash, ); final AccountBlockTemplate response = - await AccountBlockUtils.createAccountBlock( + await AccountBlockUtils().createAccountBlock( transactionParams, 'receive transaction', address: toAddress, waitForRequiredPlasma: true, ); - _sendSuccessNotification(response, toAddress.toString()); + _onSuccess(response, toAddress.toString()); return response; } on RpcException catch (e, stackTrace) { _sendErrorNotification(e.toString()); @@ -54,9 +56,10 @@ class AutoReceiveTxWorker extends BaseBloc { Future autoReceive() async { if (sharedPrefsService!.get( - kAutoReceiveKey, - defaultValue: kAutoReceiveDefaultValue, - ) == false) { + kAutoReceiveKey, + defaultValue: kAutoReceiveDefaultValue, + ) == + false) { pool.clear(); return; } @@ -68,17 +71,18 @@ class AutoReceiveTxWorker extends BaseBloc { try { final Address toAddress = (await zenon!.ledger.getAccountBlockByHash(currentHash))!.toAddress; - final AccountBlockTemplate transactionParams = AccountBlockTemplate.receive( + final AccountBlockTemplate transactionParams = + AccountBlockTemplate.receive( currentHash, ); final AccountBlockTemplate response = - await AccountBlockUtils.createAccountBlock( + await AccountBlockUtils().createAccountBlock( transactionParams, 'receive transaction', address: toAddress, waitForRequiredPlasma: true, ); - _sendSuccessNotification(response, toAddress.toString()); + _onSuccess(response, toAddress.toString()); if (pool.isNotEmpty) { pool.removeFirst(); } @@ -111,6 +115,11 @@ class AutoReceiveTxWorker extends BaseBloc { (syncInfo.targetHeight > 0 && syncInfo.currentHeight > 0 && (syncInfo.targetHeight - syncInfo.currentHeight) < 3))) { + sl.get().add( + InfiniteListRefreshRequested( + address: Address.parse(kSelectedAddress!), + ), + ); pool.add(hash); } }); @@ -127,6 +136,25 @@ class AutoReceiveTxWorker extends BaseBloc { ); } + void _onSuccess(AccountBlockTemplate block, String toAddress) { + sl.get().add( + MultipleBalanceFetch( + addresses: kDefaultAddressList.map((String? e) => e!).toList(), + ), + ); + + final Address address = Address.parse(kSelectedAddress!); + sl.get().add( + InfiniteListRefreshRequested(address: address), + ); + sl.get().add( + InfiniteListRefreshRequested( + address: address, + ), + ); + _sendSuccessNotification(block, toAddress); + } + void _sendSuccessNotification(AccountBlockTemplate block, String toAddress) { addEvent( WalletNotification( diff --git a/lib/blocs/auto_unlock_htlc_worker.dart b/lib/blocs/auto_unlock_htlc_worker.dart index abd56c67..e1886bad 100644 --- a/lib/blocs/auto_unlock_htlc_worker.dart +++ b/lib/blocs/auto_unlock_htlc_worker.dart @@ -42,7 +42,7 @@ class AutoUnlockHtlcWorker extends BaseBloc { final AccountBlockTemplate transactionParams = zenon!.embedded.htlc .unlock(htlc.id, FormatUtils.decodeHexString(swap.preimage!)); final AccountBlockTemplate response = - await AccountBlockUtils.createAccountBlock( + await AccountBlockUtils().createAccountBlock( transactionParams, 'complete swap', address: htlc.hashLocked, diff --git a/lib/blocs/blocs.dart b/lib/blocs/blocs.dart index cc499f11..73cb2228 100644 --- a/lib/blocs/blocs.dart +++ b/lib/blocs/blocs.dart @@ -20,4 +20,3 @@ export 'sentinels/sentinels.dart'; export 'settings/settings.dart'; export 'staking/staking.dart'; export 'tokens/tokens.dart'; -export 'transfer/transfer.dart'; 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 80ecb59d..34412dfe 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,13 +30,13 @@ class CompleteHtlcSwapBloc extends BaseBloc { final AccountBlockTemplate transactionParams = zenon!.embedded.htlc.unlock( Hash.parse(htlcId), FormatUtils.decodeHexString(swap.preimage!),); - AccountBlockUtils.createAccountBlock(transactionParams, 'complete swap', + AccountBlockUtils().createAccountBlock(transactionParams, 'complete swap', address: Address.parse(swap.selfAddress), waitForRequiredPlasma: true,) .then( (AccountBlockTemplate response) async { swap.state = P2pSwapState.completed; await htlcSwapsService!.storeSwap(swap); - ZenonAddressUtils.refreshBalance(); + ZenonAddressUtils().refreshBalance(); addEvent(swap); }, ).onError( 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 01b2b805..6b917ef5 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 @@ -30,7 +30,7 @@ class JoinHtlcSwapBloc extends BaseBloc { initialHtlc.keyMaxSize, initialHtlc.hashLock, ); - AccountBlockUtils.createAccountBlock(transactionParams, 'join swap', + AccountBlockUtils().createAccountBlock(transactionParams, 'join swap', address: initialHtlc.hashLocked, waitForRequiredPlasma: true,) .then( (AccountBlockTemplate response) async { @@ -61,7 +61,7 @@ class JoinHtlcSwapBloc extends BaseBloc { hashType: initialHtlc.hashType, ); await htlcSwapsService!.storeSwap(swap); - ZenonAddressUtils.refreshBalance(); + ZenonAddressUtils().refreshBalance(); addEvent(swap); }, ).onError( 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 8e1db549..55f08010 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 @@ -13,12 +13,12 @@ class ReclaimHtlcSwapFundsBloc extends BaseBloc { addEvent(null); final AccountBlockTemplate transactionParams = zenon!.embedded.htlc.reclaim(htlcId); - AccountBlockUtils.createAccountBlock( + AccountBlockUtils().createAccountBlock( transactionParams, 'reclaim swap funds', address: selfAddress, waitForRequiredPlasma: true,) .then( (AccountBlockTemplate response) { - ZenonAddressUtils.refreshBalance(); + ZenonAddressUtils().refreshBalance(); addEvent(response); }, ).onError( 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 dde02568..e623ae53 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 @@ -36,7 +36,7 @@ class StartHtlcSwapBloc extends BaseBloc { htlcPreimageMaxLength, hashLock.getBytes(), ); - AccountBlockUtils.createAccountBlock(transactionParams, 'start swap', + AccountBlockUtils().createAccountBlock(transactionParams, 'start swap', address: selfAddress, waitForRequiredPlasma: true,) .then( (AccountBlockTemplate response) async { @@ -62,7 +62,7 @@ class StartHtlcSwapBloc extends BaseBloc { hashType: hashType, ); await htlcSwapsService!.storeSwap(swap); - ZenonAddressUtils.refreshBalance(); + ZenonAddressUtils().refreshBalance(); addEvent(swap); }, ).onError( diff --git a/lib/blocs/pillars/delegate_button_bloc.dart b/lib/blocs/pillars/delegate_button_bloc.dart index 71d440d6..6064bd08 100644 --- a/lib/blocs/pillars/delegate_button_bloc.dart +++ b/lib/blocs/pillars/delegate_button_bloc.dart @@ -13,7 +13,7 @@ class DelegateButtonBloc extends BaseBloc { final AccountBlockTemplate transactionParams = zenon!.embedded.pillar.delegate( pillarName!, ); - AccountBlockUtils.createAccountBlock( + AccountBlockUtils().createAccountBlock( transactionParams, 'delegate to Pillar', waitForRequiredPlasma: true, diff --git a/lib/blocs/pillars/disassemble_pillar_bloc.dart b/lib/blocs/pillars/disassemble_pillar_bloc.dart index ba149ae1..6d486c13 100644 --- a/lib/blocs/pillars/disassemble_pillar_bloc.dart +++ b/lib/blocs/pillars/disassemble_pillar_bloc.dart @@ -15,13 +15,13 @@ class DisassemblePillarBloc extends BaseBloc { final AccountBlockTemplate transactionParams = zenon!.embedded.pillar.revoke( pillarName, ); - AccountBlockUtils.createAccountBlock( + AccountBlockUtils().createAccountBlock( transactionParams, 'disassemble Pillar', waitForRequiredPlasma: true, ).then( (AccountBlockTemplate response) { - ZenonAddressUtils.refreshBalance(); + ZenonAddressUtils().refreshBalance(); addEvent(response); }, ).onError( diff --git a/lib/blocs/pillars/pillars_deploy_bloc.dart b/lib/blocs/pillars/pillars_deploy_bloc.dart index e95d6c99..7673e229 100644 --- a/lib/blocs/pillars/pillars_deploy_bloc.dart +++ b/lib/blocs/pillars/pillars_deploy_bloc.dart @@ -30,13 +30,13 @@ class PillarsDeployBloc extends BaseBloc { giveBlockRewardPercentage, giveDelegateRewardPercentage, ); - await AccountBlockUtils.createAccountBlock( + await AccountBlockUtils().createAccountBlock( transactionParams, 'register Pillar', waitForRequiredPlasma: true, ).then( (AccountBlockTemplate response) { - ZenonAddressUtils.refreshBalance(); + ZenonAddressUtils().refreshBalance(); addEvent(response); }, ).onError( diff --git a/lib/blocs/pillars/pillars_deposit_qsr_bloc.dart b/lib/blocs/pillars/pillars_deposit_qsr_bloc.dart index 432ea33c..4b2e74d7 100644 --- a/lib/blocs/pillars/pillars_deposit_qsr_bloc.dart +++ b/lib/blocs/pillars/pillars_deposit_qsr_bloc.dart @@ -20,7 +20,7 @@ class PillarsDepositQsrBloc extends BaseBloc { zenon!.embedded.pillar.depositQsr( amount, ); - AccountBlockUtils.createAccountBlock( + AccountBlockUtils().createAccountBlock( transactionParams, 'deposit ${kQsrCoin.symbol} for Pillar Slot', waitForRequiredPlasma: true, @@ -29,7 +29,7 @@ class PillarsDepositQsrBloc extends BaseBloc { await Future.delayed( kDelayAfterAccountBlockCreationCall, ); - ZenonAddressUtils.refreshBalance(); + ZenonAddressUtils().refreshBalance(); addEvent(response); }, ).onError( diff --git a/lib/blocs/pillars/pillars_withdraw_qsr_bloc.dart b/lib/blocs/pillars/pillars_withdraw_qsr_bloc.dart index 390ba7e1..d84387cc 100644 --- a/lib/blocs/pillars/pillars_withdraw_qsr_bloc.dart +++ b/lib/blocs/pillars/pillars_withdraw_qsr_bloc.dart @@ -14,14 +14,14 @@ class PillarsWithdrawQsrBloc extends BaseBloc { addEvent(null); final AccountBlockTemplate transactionParams = zenon!.embedded.pillar.withdrawQsr(); - AccountBlockUtils.createAccountBlock( + AccountBlockUtils().createAccountBlock( transactionParams, 'withdraw ${kQsrCoin.symbol} from Pillar Slot', waitForRequiredPlasma: true, ).then( (AccountBlockTemplate response) async { await Future.delayed(kDelayAfterAccountBlockCreationCall); - ZenonAddressUtils.refreshBalance(); + ZenonAddressUtils().refreshBalance(); addEvent(response); }, ).onError( diff --git a/lib/blocs/pillars/undelegate_button_bloc.dart b/lib/blocs/pillars/undelegate_button_bloc.dart index 2b021a38..fa1ce77a 100644 --- a/lib/blocs/pillars/undelegate_button_bloc.dart +++ b/lib/blocs/pillars/undelegate_button_bloc.dart @@ -11,7 +11,7 @@ class UndelegateButtonBloc extends BaseBloc { addEvent(null); final AccountBlockTemplate transactionParams = zenon!.embedded.pillar.undelegate(); - AccountBlockUtils.createAccountBlock( + AccountBlockUtils().createAccountBlock( transactionParams, 'undelegate', waitForRequiredPlasma: true, diff --git a/lib/blocs/pillars/update_pillar_bloc.dart b/lib/blocs/pillars/update_pillar_bloc.dart index 024e813a..72bf9a7a 100644 --- a/lib/blocs/pillars/update_pillar_bloc.dart +++ b/lib/blocs/pillars/update_pillar_bloc.dart @@ -21,7 +21,7 @@ class UpdatePillarBloc extends BaseBloc { giveBlockRewardPercentage, giveDelegateRewardPercentage, ); - AccountBlockUtils.createAccountBlock( + AccountBlockUtils().createAccountBlock( transactionParams, 'update pillar', ) diff --git a/lib/blocs/plasma/cancel_plasma_bloc.dart b/lib/blocs/plasma/cancel_plasma_bloc.dart index 100098cf..9a4396d8 100644 --- a/lib/blocs/plasma/cancel_plasma_bloc.dart +++ b/lib/blocs/plasma/cancel_plasma_bloc.dart @@ -12,11 +12,11 @@ class CancelPlasmaBloc extends BaseBloc { final AccountBlockTemplate transactionParams = zenon!.embedded.plasma.cancel( Hash.parse(id), ); - AccountBlockUtils.createAccountBlock(transactionParams, 'cancel Plasma', + AccountBlockUtils().createAccountBlock(transactionParams, 'cancel Plasma', waitForRequiredPlasma: true,) .then( (AccountBlockTemplate response) { - ZenonAddressUtils.refreshBalance(); + ZenonAddressUtils().refreshBalance(); addEvent(response); }, ).onError( diff --git a/lib/blocs/plasma/plasma_options_bloc.dart b/lib/blocs/plasma/plasma_options_bloc.dart index da315865..7666c373 100644 --- a/lib/blocs/plasma/plasma_options_bloc.dart +++ b/lib/blocs/plasma/plasma_options_bloc.dart @@ -13,13 +13,13 @@ class PlasmaOptionsBloc extends BaseBloc { Address.parse(beneficiaryAddress), amount, ); - AccountBlockUtils.createAccountBlock( + AccountBlockUtils().createAccountBlock( transactionParams, 'fuse ${kQsrCoin.symbol} for Plasma', waitForRequiredPlasma: true, ).then( (AccountBlockTemplate response) { - ZenonAddressUtils.refreshBalance(); + ZenonAddressUtils().refreshBalance(); addEvent(response); }, ).onError( diff --git a/lib/blocs/refresh_bloc_mixin.dart b/lib/blocs/refresh_bloc_mixin.dart index 09f54700..81189d6b 100644 --- a/lib/blocs/refresh_bloc_mixin.dart +++ b/lib/blocs/refresh_bloc_mixin.dart @@ -7,11 +7,15 @@ mixin RefreshBlocMixin { StreamSubscription? _restartWsStreamSubscription; void listenToWsRestart(VoidCallback onWsConnectionRestartedCallback) { - _restartWsStreamSubscription = zenon!.wsClient.restartedStream.listen( - (bool restarted) { - if (restarted) { - onWsConnectionRestartedCallback(); - } + _restartWsStreamSubscription = zenon?.wsClient.restartedStream.listen( + (bool restarted) { + _restartWsStreamSubscription = zenon?.wsClient.restartedStream.listen( + (bool restarted) { + if (restarted) { + onWsConnectionRestartedCallback(); + } + }, + ); }, ); } diff --git a/lib/blocs/sentinels/disassemble_button_bloc.dart b/lib/blocs/sentinels/disassemble_button_bloc.dart index 88e1f7e1..4087d2d6 100644 --- a/lib/blocs/sentinels/disassemble_button_bloc.dart +++ b/lib/blocs/sentinels/disassemble_button_bloc.dart @@ -11,13 +11,13 @@ class DisassembleButtonBloc extends BaseBloc { addEvent(null); final AccountBlockTemplate transactionParams = zenon!.embedded.sentinel.revoke(); - AccountBlockUtils.createAccountBlock( + AccountBlockUtils().createAccountBlock( transactionParams, 'disassemble Sentinel', waitForRequiredPlasma: true, ).then( (AccountBlockTemplate response) { - ZenonAddressUtils.refreshBalance(); + ZenonAddressUtils().refreshBalance(); addEvent(response); }, ).onError( diff --git a/lib/blocs/sentinels/sentinel_deposit_qsr_bloc.dart b/lib/blocs/sentinels/sentinel_deposit_qsr_bloc.dart index 8f14ba6d..5b33218b 100644 --- a/lib/blocs/sentinels/sentinel_deposit_qsr_bloc.dart +++ b/lib/blocs/sentinels/sentinel_deposit_qsr_bloc.dart @@ -20,7 +20,7 @@ class SentinelsDepositQsrBloc extends BaseBloc { zenon!.embedded.sentinel.depositQsr( amount, ); - AccountBlockUtils.createAccountBlock( + AccountBlockUtils().createAccountBlock( transactionParams, 'deposit ${kQsrCoin.symbol} for Sentinel Slot', waitForRequiredPlasma: true, @@ -29,7 +29,7 @@ class SentinelsDepositQsrBloc extends BaseBloc { await Future.delayed( kDelayAfterAccountBlockCreationCall, ); - ZenonAddressUtils.refreshBalance(); + ZenonAddressUtils().refreshBalance(); addEvent(response); }, ).onError( diff --git a/lib/blocs/sentinels/sentinel_register_bloc.dart b/lib/blocs/sentinels/sentinel_register_bloc.dart index 429076fb..9d3e2541 100644 --- a/lib/blocs/sentinels/sentinel_register_bloc.dart +++ b/lib/blocs/sentinels/sentinel_register_bloc.dart @@ -10,13 +10,13 @@ class SentinelsDeployBloc extends BaseBloc { addEvent(null); final AccountBlockTemplate transactionParams = zenon!.embedded.sentinel.register(); - AccountBlockUtils.createAccountBlock( + AccountBlockUtils().createAccountBlock( transactionParams, 'register Sentinel', waitForRequiredPlasma: true, ).then( (AccountBlockTemplate response) { - ZenonAddressUtils.refreshBalance(); + ZenonAddressUtils().refreshBalance(); addEvent(response); }, ).onError( diff --git a/lib/blocs/sentinels/sentinel_withdraw_qsr_bloc.dart b/lib/blocs/sentinels/sentinel_withdraw_qsr_bloc.dart index 2a61de7e..1a37c3af 100644 --- a/lib/blocs/sentinels/sentinel_withdraw_qsr_bloc.dart +++ b/lib/blocs/sentinels/sentinel_withdraw_qsr_bloc.dart @@ -14,14 +14,14 @@ class SentinelsWithdrawQsrBloc extends BaseBloc { addEvent(null); final AccountBlockTemplate transactionParams = zenon!.embedded.sentinel.withdrawQsr(); - AccountBlockUtils.createAccountBlock( + AccountBlockUtils().createAccountBlock( transactionParams, 'withdraw ${kQsrCoin.symbol} from Sentinel Slot', waitForRequiredPlasma: true, ).then( (AccountBlockTemplate response) async { await Future.delayed(kDelayAfterAccountBlockCreationCall); - ZenonAddressUtils.refreshBalance(); + ZenonAddressUtils().refreshBalance(); addEvent(response); }, ).onError( diff --git a/lib/blocs/staking/cancel_stake_bloc.dart b/lib/blocs/staking/cancel_stake_bloc.dart index 199cca25..366678d8 100644 --- a/lib/blocs/staking/cancel_stake_bloc.dart +++ b/lib/blocs/staking/cancel_stake_bloc.dart @@ -12,13 +12,13 @@ class CancelStakeBloc extends BaseBloc { final AccountBlockTemplate transactionParams = zenon!.embedded.stake.cancel( Hash.parse(hash), ); - AccountBlockUtils.createAccountBlock( + AccountBlockUtils().createAccountBlock( transactionParams, 'cancel stake', waitForRequiredPlasma: true, ).then( (AccountBlockTemplate response) { - ZenonAddressUtils.refreshBalance(); + ZenonAddressUtils().refreshBalance(); addEvent(response); }, ).onError( diff --git a/lib/blocs/staking/staking_options_bloc.dart b/lib/blocs/staking/staking_options_bloc.dart index d1981dbc..b3af3841 100644 --- a/lib/blocs/staking/staking_options_bloc.dart +++ b/lib/blocs/staking/staking_options_bloc.dart @@ -15,11 +15,11 @@ class StakingOptionsBloc extends BaseBloc { stakeDuration.inSeconds, amount, ); - AccountBlockUtils.createAccountBlock(transactionParams, 'create stake', + AccountBlockUtils().createAccountBlock(transactionParams, 'create stake', waitForRequiredPlasma: true,) .then( (AccountBlockTemplate response) { - ZenonAddressUtils.refreshBalance(); + ZenonAddressUtils().refreshBalance(); addEvent(response); }, ).onError( diff --git a/lib/blocs/tokens/burn_token_bloc.dart b/lib/blocs/tokens/burn_token_bloc.dart index 48afc023..acc0bfb9 100644 --- a/lib/blocs/tokens/burn_token_bloc.dart +++ b/lib/blocs/tokens/burn_token_bloc.dart @@ -14,11 +14,11 @@ class BurnTokenBloc extends BaseBloc { token.tokenStandard, amount, ); - AccountBlockUtils.createAccountBlock(transactionParams, 'burn token', + AccountBlockUtils().createAccountBlock(transactionParams, 'burn token', waitForRequiredPlasma: true,) .then( (AccountBlockTemplate response) { - ZenonAddressUtils.refreshBalance(); + ZenonAddressUtils().refreshBalance(); addEvent(response); }, ).onError( diff --git a/lib/blocs/tokens/issue_token_bloc.dart b/lib/blocs/tokens/issue_token_bloc.dart index c5252ca6..075677d1 100644 --- a/lib/blocs/tokens/issue_token_bloc.dart +++ b/lib/blocs/tokens/issue_token_bloc.dart @@ -20,14 +20,14 @@ class IssueTokenBloc extends BaseBloc { tokenStepperData.isMintable!, tokenStepperData.isOwnerBurnOnly!, tokenStepperData.isUtility!,); - AccountBlockUtils.createAccountBlock( + AccountBlockUtils().createAccountBlock( transactionParams, 'issue token', waitForRequiredPlasma: true, ).then( (AccountBlockTemplate response) { Hive.box(kFavoriteTokensBox).add(response.tokenStandard.toString()); - ZenonAddressUtils.refreshBalance(); + ZenonAddressUtils().refreshBalance(); addEvent(response); }, ).onError( diff --git a/lib/blocs/tokens/mint_token_bloc.dart b/lib/blocs/tokens/mint_token_bloc.dart index 95112d12..f4671724 100644 --- a/lib/blocs/tokens/mint_token_bloc.dart +++ b/lib/blocs/tokens/mint_token_bloc.dart @@ -16,12 +16,12 @@ class MintTokenBloc extends BaseBloc { amount, beneficiaryAddress, ); - AccountBlockUtils.createAccountBlock(transactionParams, 'mint token', + AccountBlockUtils().createAccountBlock(transactionParams, 'mint token', waitForRequiredPlasma: true,) .then( (AccountBlockTemplate response) { response.amount = amount; - ZenonAddressUtils.refreshBalance(); + ZenonAddressUtils().refreshBalance(); addEvent(response); }, ).onError( diff --git a/lib/blocs/tokens/tokens.dart b/lib/blocs/tokens/tokens.dart index a1a566c4..23016850 100644 --- a/lib/blocs/tokens/tokens.dart +++ b/lib/blocs/tokens/tokens.dart @@ -1,7 +1,5 @@ - export 'burn_token_bloc.dart'; export 'issue_token_bloc.dart'; export 'mint_token_bloc.dart'; export 'token_map_bloc.dart'; -export 'tokens_bloc.dart'; export 'transfer_ownership_bloc.dart'; diff --git a/lib/blocs/tokens/tokens_bloc.dart b/lib/blocs/tokens/tokens_bloc.dart deleted file mode 100644 index 87b5d6af..00000000 --- a/lib/blocs/tokens/tokens_bloc.dart +++ /dev/null @@ -1,24 +0,0 @@ -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 TokensBloc extends BaseBloc?> with RefreshBlocMixin { - TokensBloc() { - zenon!.wsClient.restartedStream.listen( - (bool restarted) { - if (restarted) { - getDataAsync(); - } - }, - ); - } - - Future getDataAsync() async { - try { - addEvent(null); - addEvent((await zenon!.embedded.token.getAll()).list); - } catch (e, stackTrace) { - addError(e, stackTrace); - } - } -} diff --git a/lib/blocs/tokens/transfer_ownership_bloc.dart b/lib/blocs/tokens/transfer_ownership_bloc.dart index 861c0226..71a1d5af 100644 --- a/lib/blocs/tokens/transfer_ownership_bloc.dart +++ b/lib/blocs/tokens/transfer_ownership_bloc.dart @@ -18,7 +18,7 @@ class TransferOwnershipBloc extends BaseBloc { isMintable, isBurnable, ); - AccountBlockUtils.createAccountBlock( + AccountBlockUtils().createAccountBlock( transactionParams, 'transfer token', waitForRequiredPlasma: true, diff --git a/lib/blocs/transfer/latest_transactions_bloc.dart b/lib/blocs/transfer/latest_transactions_bloc.dart deleted file mode 100644 index 4295b556..00000000 --- a/lib/blocs/transfer/latest_transactions_bloc.dart +++ /dev/null @@ -1,17 +0,0 @@ -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 LatestTransactionsBloc extends InfiniteScrollBloc { - @override - Future> getData(int pageKey, int pageSize) async => - (await zenon!.ledger.getAccountBlocksByPage( - Address.parse(kSelectedAddress!), - pageIndex: pageKey, - pageSize: pageSize, - )) - .list!; -} diff --git a/lib/blocs/transfer/pending_transactions_bloc.dart b/lib/blocs/transfer/pending_transactions_bloc.dart deleted file mode 100644 index b581d2ee..00000000 --- a/lib/blocs/transfer/pending_transactions_bloc.dart +++ /dev/null @@ -1,17 +0,0 @@ -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 deleted file mode 100644 index aca53414..00000000 --- a/lib/blocs/transfer/receive_transaction_bloc.dart +++ /dev/null @@ -1,17 +0,0 @@ -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 { - Future receiveTransaction(String id, BuildContext context) async { - try { - addEvent(null); - final AccountBlockTemplate? 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 deleted file mode 100644 index c224349a..00000000 --- a/lib/blocs/transfer/send_payment_bloc.dart +++ /dev/null @@ -1,50 +0,0 @@ -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:znn_sdk_dart/znn_sdk_dart.dart'; - -class SendPaymentBloc extends BaseBloc { - Future sendTransfer({ - // TODO: make this argument non-nullable - String? fromAddress, - String? toAddress, - BigInt? amount, - List? data, - Token? token, - AccountBlockTemplate? block, - }) async { - assert( - block == null && - fromAddress != null && - toAddress != null && - amount != null && - token != null || - block != null && fromAddress != null, - ); - try { - addEvent(null); - final AccountBlockTemplate accountBlock = block ?? - AccountBlockTemplate.send( - Address.parse(toAddress!), - token!.tokenStandard, - amount!, - data, - ); - AccountBlockUtils.createAccountBlock( - accountBlock, - 'send transaction', - address: Address.parse(fromAddress!), - waitForRequiredPlasma: true, - ).then( - (AccountBlockTemplate response) { - ZenonAddressUtils.refreshBalance(); - addEvent(response); - }, - ).onError( - addError, - ); - } catch (e, stackTrace) { - addError(e, stackTrace); - } - } -} diff --git a/lib/blocs/transfer/transfer.dart b/lib/blocs/transfer/transfer.dart deleted file mode 100644 index e2c5bb2d..00000000 --- a/lib/blocs/transfer/transfer.dart +++ /dev/null @@ -1,4 +0,0 @@ - -export 'latest_transactions_bloc.dart'; -export 'send_payment_bloc.dart'; -export 'transfer_widgets_balance_bloc.dart'; diff --git a/lib/blocs/transfer/transfer_widgets_balance_bloc.dart b/lib/blocs/transfer/transfer_widgets_balance_bloc.dart deleted file mode 100644 index bc89b40b..00000000 --- a/lib/blocs/transfer/transfer_widgets_balance_bloc.dart +++ /dev/null @@ -1,36 +0,0 @@ -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 TransferWidgetsBalanceBloc extends BaseBloc?> - with RefreshBlocMixin { - TransferWidgetsBalanceBloc() { - listenToWsRestart(getBalanceForAllAddresses); - } - - Future getBalanceForAllAddresses() async { - try { - addEvent(null); - final Map addressBalanceMap = {}; - final List accountInfoList = await Future.wait( - kDefaultAddressList.map( - (String? address) => _getBalancePerAddress(address!), - ), - ); - for (final AccountInfo accountInfo in accountInfoList) { - addressBalanceMap[accountInfo.address!] = accountInfo; - } - addEvent(addressBalanceMap); - } catch (e, stackTrace) { - addError(e, stackTrace); - } - } - - Future _getBalancePerAddress(String address) async => - zenon!.ledger.getAccountInfoByAddress( - Address.parse(address), - ); -} diff --git a/lib/blocs/wallet_connect/chains/nom_service.dart b/lib/blocs/wallet_connect/chains/nom_service.dart index b1dfe576..bf68aeea 100644 --- a/lib/blocs/wallet_connect/chains/nom_service.dart +++ b/lib/blocs/wallet_connect/chains/nom_service.dart @@ -1,9 +1,10 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.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/rearchitecture/features/features.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'; @@ -105,7 +106,7 @@ class NoMService extends IChain { if (kCurrentPage != Tabs.lock) { if (globalNavigatorKey.currentContext!.mounted) { - final actionWasAccepted = await showDialogWithNoAndYesOptions( + final bool? actionWasAccepted = await showDialogWithNoAndYesOptions( context: globalNavigatorKey.currentContext!, isBarrierDismissible: false, title: '${dAppMetadata.name} - Information', @@ -134,11 +135,9 @@ class NoMService extends IChain { ), ], ), - onYesButtonPressed: () async {}, - onNoButtonPressed: () {}, ); - if (actionWasAccepted) { + if (actionWasAccepted ?? false) { return { 'address': kSelectedAddress, 'nodeUrl': kCurrentNode, @@ -146,8 +145,9 @@ class NoMService extends IChain { }; } else { await NotificationUtils.sendNotificationError( - Errors.getSdkError(Errors.USER_REJECTED), - 'You have rejected the WalletConnect request',); + Errors.getSdkError(Errors.USER_REJECTED), + 'You have rejected the WalletConnect request', + ); throw Errors.getSdkError(Errors.USER_REJECTED); } } else { @@ -172,7 +172,7 @@ class NoMService extends IChain { final String message = params as String; if (globalNavigatorKey.currentContext!.mounted) { - final actionWasAccepted = await showDialogWithNoAndYesOptions( + final bool? actionWasAccepted = await showDialogWithNoAndYesOptions( context: globalNavigatorKey.currentContext!, isBarrierDismissible: false, title: '${dAppMetadata.name} - Sign Message', @@ -201,16 +201,15 @@ class NoMService extends IChain { ), ], ), - onYesButtonPressed: () async {}, - onNoButtonPressed: () {}, ); - if (actionWasAccepted) { + if (actionWasAccepted ?? false) { return walletSign(message.codeUnits); } else { await NotificationUtils.sendNotificationError( - Errors.getSdkError(Errors.USER_REJECTED), - 'You have rejected the WalletConnect request',); + Errors.getSdkError(Errors.USER_REJECTED), + 'You have rejected the WalletConnect request', + ); throw Errors.getSdkError(Errors.USER_REJECTED); } } else { @@ -233,19 +232,17 @@ class NoMService extends IChain { .metadata; if (kCurrentPage != Tabs.lock) { final AccountBlockTemplate accountBlock = - AccountBlockTemplate.fromJson(params['accountBlock']); + AccountBlockTemplate.fromJson(params['accountBlock']); final String toAddress = ZenonAddressUtils.getLabel( accountBlock.toAddress.toString(), ); final Token? token = - await zenon!.embedded.token.getByZts(accountBlock.tokenStandard); + await zenon!.embedded.token.getByZts(accountBlock.tokenStandard); final String amount = accountBlock.amount.addDecimals(token!.decimals); - final SendPaymentBloc sendPaymentBloc = SendPaymentBloc(); - if (globalNavigatorKey.currentContext!.mounted) { final wasActionAccepted = await showDialogWithNoAndYesOptions( context: globalNavigatorKey.currentContext!, @@ -280,25 +277,31 @@ class NoMService extends IChain { 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']), + if (wasActionAccepted ?? false) { + final SendTransactionBloc sendTransactionBloc = + globalNavigatorKey.currentContext!.read()..add( + SendTransactionInitiateFromBlock( + fromAddress: params['fromAddress'], + block: AccountBlockTemplate.fromJson(params['accountBlock']), + ), ); - final AccountBlockTemplate? result = await sendPaymentBloc.stream.firstWhere( - (AccountBlockTemplate? element) => element != null, + final SendTransactionState state = await sendTransactionBloc.stream + .firstWhere( + (SendTransactionState newState) => + newState.status == SendTransactionStatus.success, ); - return result!; + final AccountBlockTemplate result = state.data!; + + return result; } else { await NotificationUtils.sendNotificationError( - Errors.getSdkError(Errors.USER_REJECTED), - 'You have rejected the WalletConnect request',); + Errors.getSdkError(Errors.USER_REJECTED), + 'You have rejected the WalletConnect request', + ); throw Errors.getSdkError(Errors.USER_REJECTED); } } else { diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index d933fe67..11fe2cac 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -1,29 +1,68 @@ { "activePillars": "Active Pillars", "activeSentinels": "Active Sentinels", + "addressSearchDescription": "Addresses can be searched by label - \"Address 1\" - and by hex value - \"z1qxemdeddedxt0kenxxxxxxxxxxxxxxxxh9amk0\"", + "amount": "Amount", + "areYouSureTranfer": "Are you sure you want to transfer {amount} {symbol} to {recipient} ?", + "asset": "Asset", "balance": "Balance", + "coin": "Coin", + "couldNotSend": "Couldn't send {amount} {symbol} to {recipient}", "currentAmounts": "This card displays the current {kZnnCoinSymbol} and {kQsrCoinSymbol} amounts for the selected address", + "date":"Date", "delegationStats": "Delegation Stats", "delegationStatsDescription": "This card displays the amount of {kZnnCoinSymbol} and the name of the Pillar that you delegated to", "dualCoinStats": "Dual Coin Stats", "dualCoinStatsDescription": "This card displays the circulating {kZnnCoinSymbol} and {kQsrCoinSymbol} supply from the network", + "from": "from", + "hAgo": "h ago", + "hash": "Hash", + "hashValue": "Hash: {value}", + "latestTransactionsDescription": "This card displays the latest transactions (including ZTS tokens) of your selected address", + "latestTransactionsTitle": "Latest Transactions", + "latestTransactionsTransferDescription": "This card displays the latest transactions (including ZTS tokens) involving your wallet addresses", + "manageReceivingFunds": "Manage receiving funds", + "manageSendingFunds": "Manage sending funds", + "max": "Max", + "minAgo": "min ago", + "noItemsFound": "No items found", + "noMoreItems": "No more items", "password": "Password", + "pending": "Pending", + "pendingTransactionsDescription": "This card displays the pending transactions (including ZTS tokens) for the selected address", + "pendingTransactionsTitle": "Pending Transactions", "pillars": "Pillars", "pillarsDescription": "This card displays the number of active Pillars in the network", + "pressToReceive": "Press to receive the transaction", + "quasarTransactions": "{quasar} transactions", "realtimeStats": "Realtime Stats", "realtimeStatsDescription": "This card displays the number of {kZnnCoinSymbol} and {kQsrCoinSymbol} transactions. For example, a delegation is considered a {kZnnCoinSymbol} transaction from the network's perspective. Every interaction with the network embedded contracts is internally considered a transaction", + "receive": "Receive", + "receiver": "Receiver", + "recipientAddress": "Recipient Address", + "saveQr": "Save QR", "send": "Send", + "sendTransaction": "send transaction", + "sender": "Sender", + "senderAddressDescription": "The address from which the transaction will be sent", + "sent": "Sent", + "sentDetails": "Sent {amount} {symbol} from {sender} to {recipient}", "sentinels": "Sentinels", "sentinelsDescription": "This card displays the number of active Sentinels in the network", + "shareQr": "Share QR", "stakingStats": "Staking Stats", "stakingStatsDescription": "This card displays the number of staking entries and the total {kZnnCoinSymbol} that you are currently staking", - "receive": "Receive", + "to": "to", + "tokenTransactions": "Token Transactions", + "transactionError": "Error while receiving transaction", "transactions": "Transactions", "transactionsDescription": "This card displays the total number of transactions settled in the last hour across the network", "transactionsLastHour": "transactions in the last hour", "transfer": "Transfer", "transferDescription": "Redirects you to the Transfer tab where you can manage sending and receiving funds", + "type": "Type", + "usDateFormat": "MM/dd/yyyy", "waitingForDataFetching": "Waiting for data fetching", - "quasarTransactions": "{quasar} transactions", - "zenonTransactions": "{zenon} transactions" + "zenonTransactions": "{zenon} transactions", + "ztsSearchDescription": "Coins and tokens can be searched by name - \"Zenon\" -, by symbol - \"ZNN\" -, and by token standard - \"zts1znnxxxxxxxxxxxxx9z4ulx\"" } \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index 414ec7a5..499f77f3 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -5,6 +5,7 @@ import 'dart:isolate'; import 'package:flutter/foundation.dart' show kDebugMode, kIsWeb; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:get_it/get_it.dart'; import 'package:hive/hive.dart'; @@ -28,6 +29,8 @@ import 'package:zenon_syrius_wallet_flutter/blocs/wallet_connect/wallet_connect_ import 'package:zenon_syrius_wallet_flutter/blocs/wallet_connect/wallet_connect_sessions_bloc.dart'; import 'package:zenon_syrius_wallet_flutter/handlers/htlc_swaps_handler.dart'; import 'package:zenon_syrius_wallet_flutter/model/model.dart'; +import 'package:zenon_syrius_wallet_flutter/rearchitecture/features/features.dart'; +import 'package:zenon_syrius_wallet_flutter/rearchitecture/features/tokens/cubit/tokens_cubit.dart'; import 'package:zenon_syrius_wallet_flutter/rearchitecture/utils/utils.dart'; import 'package:zenon_syrius_wallet_flutter/screens/screens.dart'; import 'package:zenon_syrius_wallet_flutter/services/htlc_swaps_service.dart'; @@ -46,10 +49,12 @@ IWeb3WalletService? web3WalletService; final GetIt sl = GetIt.instance; -final GlobalKey globalNavigatorKey = GlobalKey(); +final GlobalKey globalNavigatorKey = + GlobalKey(); main() async { WidgetsFlutterBinding.ensureInitialized(); + Bloc.observer = CustomBlocObserver(); if (Platform.isWindows) { registerProtocolHandler(kDeepLinkingUrlScheme); } @@ -71,7 +76,8 @@ main() async { syriusLogDir.createSync(recursive: true); } final File logFile = File( - '${syriusLogDir.path}${path.separator}syrius-${DateTime.now().millisecondsSinceEpoch}.log',); + '${syriusLogDir.path}${path.separator}syrius-${DateTime.now().millisecondsSinceEpoch}.log', + ); Logger.root.level = Level.ALL; Logger.root.onRecord.listen((LogRecord record) { if (kDebugMode) { @@ -96,9 +102,11 @@ main() async { // Setup services setup(); - retry(() => web3WalletService!.init(), - retryIf: (Exception e) => e is SocketException || e is TimeoutException, - maxAttempts: 0x7FFFFFFFFFFFFFFF,); + retry( + () => web3WalletService!.init(), + retryIf: (Exception e) => e is SocketException || e is TimeoutException, + maxAttempts: 0x7FFFFFFFFFFFFFFF, + ); // Setup local_notifier await localNotifier.setup( @@ -129,8 +137,10 @@ main() async { await windowManager.show(); if (sharedPrefsService != null) { - final double? windowSizeWidth = sharedPrefsService!.get(kWindowSizeWidthKey); - final double? windowSizeHeight = sharedPrefsService!.get(kWindowSizeHeightKey); + final double? windowSizeWidth = + sharedPrefsService!.get(kWindowSizeWidthKey); + final double? windowSizeHeight = + sharedPrefsService!.get(kWindowSizeHeightKey); if (windowSizeWidth != null && windowSizeWidth >= 1200 && windowSizeHeight != null && @@ -149,7 +159,8 @@ main() async { .setPosition(Offset(windowPositionX, windowPositionY)); } - final bool? windowMaximized = sharedPrefsService!.get(kWindowMaximizedKey); + final bool? windowMaximized = + sharedPrefsService!.get(kWindowMaximizedKey); if (windowMaximized == true) { await windowManager.maximize(); } @@ -206,7 +217,9 @@ void setup() { sl.registerSingleton(Zenon()); zenon = sl(); sl.registerLazySingletonAsync( - () => SharedPrefsService.getInstance().then((SharedPrefsService? value) => value!),); + () => SharedPrefsService.getInstance() + .then((SharedPrefsService? value) => value!), + ); sl.registerSingleton(HtlcSwapsService.getInstance()); // Initialize WalletConnect service @@ -216,22 +229,36 @@ void setup() { instanceName: NoMChainId.mainnet.chain(), ); + sl.registerSingleton( + LatestTransactionsBloc( + zenon: zenon!, + ), + ); + sl.registerSingleton( + PendingTransactionsBloc( + zenon: zenon!, + ), + ); + sl.registerSingleton(MultipleBalanceBloc(zenon: zenon!)); + sl.registerSingleton(TokensCubit(zenon: zenon!)); sl.registerSingleton(AutoReceiveTxWorker.getInstance()); sl.registerSingleton( - AutoUnlockHtlcWorker.getInstance(),); + AutoUnlockHtlcWorker.getInstance(), + ); sl.registerSingleton(HtlcSwapsHandler.getInstance()); - sl.registerSingleton(ReceivePort(), - instanceName: 'embeddedStoppedPort',); + sl.registerSingleton( + ReceivePort(), + instanceName: 'embeddedStoppedPort', + ); sl.registerSingleton( - sl(instanceName: 'embeddedStoppedPort').asBroadcastStream(), - instanceName: 'embeddedStoppedStream',); + sl(instanceName: 'embeddedStoppedPort').asBroadcastStream(), + instanceName: 'embeddedStoppedStream', + ); sl.registerSingleton(PlasmaStatsBloc()); sl.registerSingleton(BalanceBloc()); - sl.registerSingleton( - TransferWidgetsBalanceBloc(),); sl.registerSingleton(NotificationsBloc()); sl.registerSingleton(AcceleratorBalanceBloc()); sl.registerSingleton(PowGeneratingStatusBloc()); @@ -271,103 +298,135 @@ class _MyAppState extends State with WindowListener, TrayListener { @override Widget build(BuildContext context) { - return MultiProvider( + return MultiBlocProvider( providers: [ - ChangeNotifierProvider( - create: (_) => SelectedAddressNotifier(), + BlocProvider( + create: (_) => sl.get() + ..add( + InfiniteListRequested( + address: Address.parse(kSelectedAddress!), + ), + ), ), - ChangeNotifierProvider( - create: (_) => PlasmaBeneficiaryAddressNotifier(), + BlocProvider( + create: (_) => sl.get() + ..add( + InfiniteListRequested( + address: Address.parse(kSelectedAddress!), + ), + ), ), - ChangeNotifierProvider( - create: (_) => PlasmaGeneratedNotifier(), + BlocProvider( + create: (_) => SendTransactionBloc(), ), - ChangeNotifierProvider( - create: (_) => TextScalingNotifier(), + BlocProvider( + create: (_) => sl.get(), ), - ChangeNotifierProvider( - create: (_) => AppThemeNotifier(), + BlocProvider( + create: (_) => sl.get()..fetch(), ), - ChangeNotifierProvider>>( - create: (_) => ValueNotifier>( - [], + ], + child: MultiProvider( + providers: [ + ChangeNotifierProvider( + create: (_) => SelectedAddressNotifier(), ), - ), - Provider( - create: (_) => LockBloc(), - builder: (BuildContext context, Widget? child) { - return Consumer( - builder: (_, AppThemeNotifier appThemeNotifier, __) { - final LockBloc lockBloc = - Provider.of(context, listen: false); - return OverlaySupport( - child: Listener( - onPointerSignal: (PointerSignalEvent event) { - if (event is PointerScrollEvent) { - lockBloc.addEvent(LockEvent.resetTimer); - } - }, - onPointerCancel: (_) => - lockBloc.addEvent(LockEvent.resetTimer), - onPointerDown: (_) => - lockBloc.addEvent(LockEvent.resetTimer), - onPointerHover: (_) => - lockBloc.addEvent(LockEvent.resetTimer), - onPointerMove: (_) => - lockBloc.addEvent(LockEvent.resetTimer), - onPointerUp: (_) => lockBloc.addEvent(LockEvent.resetTimer), - child: MouseRegion( - onEnter: (_) => lockBloc.addEvent(LockEvent.resetTimer), - onExit: (_) => lockBloc.addEvent(LockEvent.resetTimer), - child: KeyboardListener( - focusNode: FocusNode(), - onKeyEvent: (KeyEvent event) { + ChangeNotifierProvider( + create: (_) => PlasmaBeneficiaryAddressNotifier(), + ), + ChangeNotifierProvider( + create: (_) => PlasmaGeneratedNotifier(), + ), + ChangeNotifierProvider( + create: (_) => TextScalingNotifier(), + ), + ChangeNotifierProvider( + create: (_) => AppThemeNotifier(), + ), + ChangeNotifierProvider>>( + create: (_) => ValueNotifier>( + [], + ), + ), + Provider( + create: (_) => LockBloc(), + builder: (BuildContext context, Widget? child) { + return Consumer( + builder: (_, AppThemeNotifier appThemeNotifier, __) { + final LockBloc lockBloc = + Provider.of(context, listen: false); + return OverlaySupport( + child: Listener( + onPointerSignal: (PointerSignalEvent event) { + if (event is PointerScrollEvent) { lockBloc.addEvent(LockEvent.resetTimer); - }, - child: Layout( - child: MaterialApp( - title: 's y r i u s', - navigatorKey: globalNavigatorKey, - debugShowCheckedModeBanner: false, - theme: AppTheme.lightTheme, - darkTheme: AppTheme.darkTheme, - themeMode: appThemeNotifier.currentThemeMode, - initialRoute: SplashScreen.route, - scrollBehavior: RemoveOverscrollEffect(), - localizationsDelegates: AppLocalizations.localizationsDelegates, - supportedLocales: AppLocalizations.supportedLocales, - routes: { - AccessWalletScreen.route: (BuildContext context) => - const AccessWalletScreen(), - SplashScreen.route: (BuildContext context) => - const SplashScreen(), - MainAppContainer.route: (BuildContext context) => - const MainAppContainer(), - NodeManagementScreen.route: (_) => - const NodeManagementScreen(), - }, - onGenerateRoute: (RouteSettings settings) { - if (settings.name == SyriusErrorWidget.route) { - final CustomSyriusErrorWidgetArguments args = settings.arguments! - as CustomSyriusErrorWidgetArguments; - return MaterialPageRoute( - builder: (BuildContext context) => - SyriusErrorWidget(args.errorText), - ); - } - return null; - }, + } + }, + onPointerCancel: (_) => + lockBloc.addEvent(LockEvent.resetTimer), + onPointerDown: (_) => + lockBloc.addEvent(LockEvent.resetTimer), + onPointerHover: (_) => + lockBloc.addEvent(LockEvent.resetTimer), + onPointerMove: (_) => + lockBloc.addEvent(LockEvent.resetTimer), + onPointerUp: (_) => lockBloc.addEvent(LockEvent.resetTimer), + child: MouseRegion( + onEnter: (_) => lockBloc.addEvent(LockEvent.resetTimer), + onExit: (_) => lockBloc.addEvent(LockEvent.resetTimer), + child: KeyboardListener( + focusNode: FocusNode(), + onKeyEvent: (KeyEvent event) { + lockBloc.addEvent(LockEvent.resetTimer); + }, + child: Layout( + child: MaterialApp( + title: 's y r i u s', + navigatorKey: globalNavigatorKey, + debugShowCheckedModeBanner: false, + theme: AppTheme.lightTheme, + darkTheme: AppTheme.darkTheme, + themeMode: appThemeNotifier.currentThemeMode, + initialRoute: SplashScreen.route, + scrollBehavior: RemoveOverscrollEffect(), + localizationsDelegates: + AppLocalizations.localizationsDelegates, + supportedLocales: AppLocalizations.supportedLocales, + routes: { + AccessWalletScreen.route: + (BuildContext context) => + const AccessWalletScreen(), + SplashScreen.route: (BuildContext context) => + const SplashScreen(), + MainAppContainer.route: (BuildContext context) => + const MainAppContainer(), + NodeManagementScreen.route: (_) => + const NodeManagementScreen(), + }, + onGenerateRoute: (RouteSettings settings) { + if (settings.name == SyriusErrorWidget.route) { + final CustomSyriusErrorWidgetArguments args = + settings.arguments! + as CustomSyriusErrorWidgetArguments; + return MaterialPageRoute( + builder: (BuildContext context) => + SyriusErrorWidget(args.errorText), + ); + } + return null; + }, + ), ), ), ), ), - ), - ); - }, - ); - }, - ), - ], + ); + }, + ); + }, + ), + ], + ), ); } diff --git a/lib/main_dev.dart b/lib/main_dev.dart index f4413980..d584ead6 100644 --- a/lib/main_dev.dart +++ b/lib/main_dev.dart @@ -5,6 +5,7 @@ import 'dart:isolate'; import 'package:flutter/foundation.dart' show kDebugMode, kIsWeb; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:hive/hive.dart'; import 'package:hydrated_bloc/hydrated_bloc.dart'; @@ -28,6 +29,8 @@ import 'package:zenon_syrius_wallet_flutter/blocs/wallet_connect/wallet_connect_ 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/rearchitecture/features/features.dart'; +import 'package:zenon_syrius_wallet_flutter/rearchitecture/features/tokens/cubit/tokens_cubit.dart'; import 'package:zenon_syrius_wallet_flutter/rearchitecture/utils/utils.dart'; import 'package:zenon_syrius_wallet_flutter/screens/screens.dart'; import 'package:zenon_syrius_wallet_flutter/services/htlc_swaps_service.dart'; @@ -47,6 +50,7 @@ main() async { registerProtocolHandler(kDeepLinkingUrlScheme); } + Bloc.observer = CustomBlocObserver(); // Init hydrated bloc storage HydratedBloc.storage = await HydratedStorage.build( storageDirectory: kIsWeb @@ -60,12 +64,13 @@ main() async { // Setup logger final Directory syriusLogDir = - Directory(path.join(znnDefaultCacheDirectory.path, 'log')); + Directory(path.join(znnDefaultCacheDirectory.path, 'log')); if (!syriusLogDir.existsSync()) { syriusLogDir.createSync(recursive: true); } final File logFile = File( - '${syriusLogDir.path}${path.separator}syrius-${DateTime.now().millisecondsSinceEpoch}.log',); + '${syriusLogDir.path}${path.separator}syrius-${DateTime.now().millisecondsSinceEpoch}.log', + ); Logger.root.level = Level.ALL; Logger.root.onRecord.listen((LogRecord record) { if (kDebugMode) { @@ -75,7 +80,7 @@ main() async { } logFile.writeAsString( '${record.level.name} ${record.loggerName} ${record.message} ${record.time}: ' - '${record.error} ${record.stackTrace}\n', + '${record.error} ${record.stackTrace}\n', mode: FileMode.append, flush: true, ); @@ -90,9 +95,11 @@ main() async { // Setup services setup(); - retry(() => web3WalletService!.init(), - retryIf: (Exception e) => e is SocketException || e is TimeoutException, - maxAttempts: 0x7FFFFFFFFFFFFFFF,); + retry( + () => web3WalletService!.init(), + retryIf: (Exception e) => e is SocketException || e is TimeoutException, + maxAttempts: 0x7FFFFFFFFFFFFFFF, + ); // Setup local_notifier await localNotifier.setup( @@ -143,7 +150,8 @@ main() async { .setPosition(Offset(windowPositionX, windowPositionY)); } - final bool? windowMaximized = sharedPrefsService!.get(kWindowMaximizedKey); + final bool? windowMaximized = + sharedPrefsService!.get(kWindowMaximizedKey); if (windowMaximized == true) { await windowManager.maximize(); } @@ -185,7 +193,7 @@ Future _setupTrayManager() async { Future _loadDefaultCommunityNodes() async { try { final List nodes = await loadJsonFromAssets('assets/community-nodes.json') - as List; + as List; kDefaultCommunityNodes = nodes .map((node) => node.toString()) .where((String node) => InputValidators.node(node) == null) @@ -200,7 +208,9 @@ void setup() { sl.registerSingleton(Zenon()); zenon = sl(); sl.registerLazySingletonAsync( - () => SharedPrefsService.getInstance().then((SharedPrefsService? value) => value!),); + () => SharedPrefsService.getInstance() + .then((SharedPrefsService? value) => value!), + ); sl.registerSingleton(HtlcSwapsService.getInstance()); // Initialize WalletConnect service @@ -210,22 +220,36 @@ void setup() { instanceName: NoMChainId.mainnet.chain(), ); + sl.registerSingleton( + LatestTransactionsBloc( + zenon: zenon!, + ), + ); + sl.registerSingleton( + PendingTransactionsBloc( + zenon: zenon!, + ), + ); + sl.registerSingleton(MultipleBalanceBloc(zenon: zenon!)); + sl.registerSingleton(TokensCubit(zenon: zenon!)); sl.registerSingleton(AutoReceiveTxWorker.getInstance()); sl.registerSingleton( - AutoUnlockHtlcWorker.getInstance(),); + AutoUnlockHtlcWorker.getInstance(), + ); sl.registerSingleton(HtlcSwapsHandler.getInstance()); - sl.registerSingleton(ReceivePort(), - instanceName: 'embeddedStoppedPort',); + sl.registerSingleton( + ReceivePort(), + instanceName: 'embeddedStoppedPort', + ); sl.registerSingleton( - sl(instanceName: 'embeddedStoppedPort').asBroadcastStream(), - instanceName: 'embeddedStoppedStream',); + sl(instanceName: 'embeddedStoppedPort').asBroadcastStream(), + instanceName: 'embeddedStoppedStream', + ); sl.registerSingleton(PlasmaStatsBloc()); sl.registerSingleton(BalanceBloc()); - sl.registerSingleton( - TransferWidgetsBalanceBloc(),); sl.registerSingleton(NotificationsBloc()); sl.registerSingleton(AcceleratorBalanceBloc()); sl.registerSingleton(PowGeneratingStatusBloc()); @@ -258,110 +282,139 @@ class _MyAppState extends State with WindowListener, TrayListener { // Platform messages are asynchronous, so we initialize in an async method Future initPlatformState() async { kLocalIpAddress = - await NetworkUtils.getLocalIpAddress(InternetAddressType.IPv4); + await NetworkUtils.getLocalIpAddress(InternetAddressType.IPv4); if (!mounted) return; } @override Widget build(BuildContext context) { - return MultiProvider( + return MultiBlocProvider( providers: [ - ChangeNotifierProvider( - create: (_) => SelectedAddressNotifier(), + BlocProvider( + create: (_) => sl.get() + ..add( + InfiniteListRequested( + address: Address.parse(kSelectedAddress!), + ), + ), ), - ChangeNotifierProvider( - create: (_) => PlasmaBeneficiaryAddressNotifier(), + BlocProvider( + create: (_) => sl.get() + ..add( + InfiniteListRequested( + address: Address.parse(kSelectedAddress!), + ), + ), ), - ChangeNotifierProvider( - create: (_) => PlasmaGeneratedNotifier(), + BlocProvider( + create: (_) => SendTransactionBloc(), ), - ChangeNotifierProvider( - create: (_) => TextScalingNotifier(), + BlocProvider( + create: (_) => sl.get(), ), - ChangeNotifierProvider( - create: (_) => AppThemeNotifier(), + BlocProvider( + create: (_) => sl.get()..fetch(), ), - ChangeNotifierProvider>>( - create: (_) => ValueNotifier>( - [], + ], + child: MultiProvider( + providers: [ + ChangeNotifierProvider( + create: (_) => SelectedAddressNotifier(), ), - ), - Provider( - create: (_) => LockBloc(), - builder: (BuildContext context, Widget? child) { - return Consumer( - builder: (_, AppThemeNotifier appThemeNotifier, __) { - final LockBloc lockBloc = - Provider.of(context, listen: false); - return OverlaySupport( - child: Listener( - onPointerSignal: (PointerSignalEvent event) { - if (event is PointerScrollEvent) { - lockBloc.addEvent(LockEvent.resetTimer); - } - }, - onPointerCancel: (_) => - lockBloc.addEvent(LockEvent.resetTimer), - onPointerDown: (_) => - lockBloc.addEvent(LockEvent.resetTimer), - onPointerHover: (_) => - lockBloc.addEvent(LockEvent.resetTimer), - onPointerMove: (_) => - lockBloc.addEvent(LockEvent.resetTimer), - onPointerUp: (_) => lockBloc.addEvent(LockEvent.resetTimer), - child: MouseRegion( - onEnter: (_) => lockBloc.addEvent(LockEvent.resetTimer), - onExit: (_) => lockBloc.addEvent(LockEvent.resetTimer), - child: KeyboardListener( - focusNode: FocusNode(), - onKeyEvent: (KeyEvent event) { + ChangeNotifierProvider( + create: (_) => PlasmaBeneficiaryAddressNotifier(), + ), + ChangeNotifierProvider( + create: (_) => PlasmaGeneratedNotifier(), + ), + ChangeNotifierProvider( + create: (_) => TextScalingNotifier(), + ), + ChangeNotifierProvider( + create: (_) => AppThemeNotifier(), + ), + ChangeNotifierProvider>>( + create: (_) => ValueNotifier>( + [], + ), + ), + Provider( + create: (_) => LockBloc(), + builder: (BuildContext context, Widget? child) { + return Consumer( + builder: (_, AppThemeNotifier appThemeNotifier, __) { + final LockBloc lockBloc = + Provider.of(context, listen: false); + return OverlaySupport( + child: Listener( + onPointerSignal: (PointerSignalEvent event) { + if (event is PointerScrollEvent) { lockBloc.addEvent(LockEvent.resetTimer); - }, - child: Layout( - child: MaterialApp( - title: 's y r i u s', - navigatorKey: globalNavigatorKey, - debugShowCheckedModeBanner: false, - theme: AppTheme.lightTheme, - darkTheme: AppTheme.darkTheme, - themeMode: appThemeNotifier.currentThemeMode, - initialRoute: DevelopmentInitializationScreen.route, - scrollBehavior: RemoveOverscrollEffect(), - localizationsDelegates: AppLocalizations.localizationsDelegates, - supportedLocales: AppLocalizations.supportedLocales, - routes: { - AccessWalletScreen.route: (BuildContext context) => - const AccessWalletScreen(), - DevelopmentInitializationScreen.route: (BuildContext context) => - const DevelopmentInitializationScreen(), - MainAppContainer.route: (BuildContext context) => - const MainAppContainer(), - NodeManagementScreen.route: (_) => - const NodeManagementScreen(), - }, - onGenerateRoute: (RouteSettings settings) { - if (settings.name == SyriusErrorWidget.route) { - final CustomSyriusErrorWidgetArguments args = settings.arguments! - as CustomSyriusErrorWidgetArguments; - return MaterialPageRoute( - builder: (BuildContext context) => - SyriusErrorWidget(args.errorText), - ); - } - return null; - }, + } + }, + onPointerCancel: (_) => + lockBloc.addEvent(LockEvent.resetTimer), + onPointerDown: (_) => + lockBloc.addEvent(LockEvent.resetTimer), + onPointerHover: (_) => + lockBloc.addEvent(LockEvent.resetTimer), + onPointerMove: (_) => + lockBloc.addEvent(LockEvent.resetTimer), + onPointerUp: (_) => lockBloc.addEvent(LockEvent.resetTimer), + child: MouseRegion( + onEnter: (_) => lockBloc.addEvent(LockEvent.resetTimer), + onExit: (_) => lockBloc.addEvent(LockEvent.resetTimer), + child: KeyboardListener( + focusNode: FocusNode(), + onKeyEvent: (KeyEvent event) { + lockBloc.addEvent(LockEvent.resetTimer); + }, + child: Layout( + child: MaterialApp( + title: 's y r i u s', + navigatorKey: globalNavigatorKey, + debugShowCheckedModeBanner: false, + theme: AppTheme.lightTheme, + darkTheme: AppTheme.darkTheme, + themeMode: appThemeNotifier.currentThemeMode, + initialRoute: DevelopmentInitializationScreen.route, + scrollBehavior: RemoveOverscrollEffect(), + localizationsDelegates: AppLocalizations.localizationsDelegates, + supportedLocales: AppLocalizations.supportedLocales, + routes: { + AccessWalletScreen.route: (BuildContext context) => + const AccessWalletScreen(), + DevelopmentInitializationScreen.route: (BuildContext context) => + const DevelopmentInitializationScreen(), + MainAppContainer.route: (BuildContext context) => + const MainAppContainer(), + NodeManagementScreen.route: (_) => + const NodeManagementScreen(), + }, + onGenerateRoute: (RouteSettings settings) { + if (settings.name == SyriusErrorWidget.route) { + final CustomSyriusErrorWidgetArguments args = settings.arguments! + as CustomSyriusErrorWidgetArguments; + return MaterialPageRoute( + builder: (BuildContext context) => + SyriusErrorWidget(args.errorText), + ); + } + return null; + }, + ), ), ), ), ), - ), - ); - }, - ); - }, - ), - ], + ); + }, + ); + }, + ), + ], + ), ); } diff --git a/lib/rearchitecture/features/features.dart b/lib/rearchitecture/features/features.dart index cb45f5aa..1286d527 100644 --- a/lib/rearchitecture/features/features.dart +++ b/lib/rearchitecture/features/features.dart @@ -1,9 +1,14 @@ export 'balance/balance.dart'; export 'delegation/delegation.dart'; export 'dual_coin_stats/dual_coin_stats.dart'; +export 'latest_transactions/latest_transactions.dart'; +export 'multiple_balance/multiple_balance.dart'; export 'node_sync_status/node_sync_status.dart'; +export 'pending_transactions/pending_transactions.dart'; export 'pillars/pillars.dart'; export 'realtime_statistics/realtime_statistics.dart'; +export 'receive/receive.dart'; +export 'send/send.dart'; export 'sentinels/sentinels.dart'; export 'staking/staking.dart'; export 'total_hourly_transactions/total_hourly_transactions.dart'; diff --git a/lib/rearchitecture/features/latest_transactions/bloc/latest_transactions_bloc.dart b/lib/rearchitecture/features/latest_transactions/bloc/latest_transactions_bloc.dart new file mode 100644 index 00000000..566c6a97 --- /dev/null +++ b/lib/rearchitecture/features/latest_transactions/bloc/latest_transactions_bloc.dart @@ -0,0 +1,35 @@ +import 'dart:async'; + +import 'package:zenon_syrius_wallet_flutter/rearchitecture/utils/utils.dart'; +import 'package:znn_sdk_dart/znn_sdk_dart.dart'; + +/// A bloc that manages the state of the latest transactions for a specific +/// address. +class LatestTransactionsBloc extends InfiniteListBloc { + /// Creates an instance of [LatestTransactionsBloc]. + /// + /// The constructor requires a [Zenon] SDK instance. + LatestTransactionsBloc({required super.zenon, super.pageSize = kPageSize}) + : super( + fromJsonT: (Object? map) => AccountBlock.fromJson( + map! as Map, + ), + toJsonT: (AccountBlock block) => block.toJson(), + ); + + @override + Future> paginationFetch({ + required Address address, + required int pageIndex, + required int pageSize, + }) async { + final AccountBlockList accountBlock = + await zenon.ledger.getAccountBlocksByPage( + address, + pageIndex: pageIndex, + pageSize: pageSize, + ); + + return accountBlock.list!; + } +} diff --git a/lib/rearchitecture/features/latest_transactions/latest_transactions.dart b/lib/rearchitecture/features/latest_transactions/latest_transactions.dart new file mode 100644 index 00000000..6365ecd1 --- /dev/null +++ b/lib/rearchitecture/features/latest_transactions/latest_transactions.dart @@ -0,0 +1,2 @@ +export 'bloc/latest_transactions_bloc.dart'; +export 'view/view.dart'; diff --git a/lib/rearchitecture/features/latest_transactions/view/latest_transactions_card.dart b/lib/rearchitecture/features/latest_transactions/view/latest_transactions_card.dart new file mode 100644 index 00000000..0312dd28 --- /dev/null +++ b/lib/rearchitecture/features/latest_transactions/view/latest_transactions_card.dart @@ -0,0 +1,303 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:zenon_syrius_wallet_flutter/rearchitecture/features/features.dart'; +import 'package:zenon_syrius_wallet_flutter/rearchitecture/utils/utils.dart'; +import 'package:zenon_syrius_wallet_flutter/utils/global.dart'; +import 'package:zenon_syrius_wallet_flutter/widgets/widgets.dart' + hide + InfiniteScrollTable, + InfiniteScrollTableCell, + InfiniteScrollTableHeaderColumn; +import 'package:znn_sdk_dart/znn_sdk_dart.dart'; + +/// A widget that displayed the latest transactions for an address +class LatestTransactionsCard extends StatelessWidget { + /// Creates a new instance. + /// + /// There is also a check to make sure that the [type] can be found among + /// the supported list of types + LatestTransactionsCard({required this.type, super.key}) + : assert( + [ + CardType.latestTransactions, + CardType.latestTransactionsDashboard, + ].contains(type), + 'make sure that the type refers only to latest transactions types', + ); + + /// The card type, either [CardType.latestTransactions] or + /// [CardType.latestTransactionsDashboard] + final CardType type; + + @override + Widget build(BuildContext context) { + return NewCardScaffold( + data: CardType.latestTransactions.getData(context: context), + onRefreshPressed: () { + context.read().add( + InfiniteListRefreshRequested( + address: Address.parse(kSelectedAddress!), + ), + ); + }, + body: + BlocBuilder>( + builder: (_, InfiniteListState state) { + final InfiniteListStatus status = state.status; + + return switch (status) { + InfiniteListStatus.initial => const _LatestTransactionsInitial(), + InfiniteListStatus.failure => _LatestTransactionsFailure( + exception: state.error!, + ), + InfiniteListStatus.success => _LatestTransactionsPopulated( + hasReachedMax: state.hasReachedMax, + transactions: state.data!, + type: type, + ), + }; + }, + ), + ); + } +} + +class _LatestTransactionsInitial extends StatelessWidget { + const _LatestTransactionsInitial(); + + @override + Widget build(BuildContext context) { + return const SyriusLoadingWidget(); + } +} + +class _LatestTransactionsFailure extends StatelessWidget { + const _LatestTransactionsFailure({required this.exception}); + + final SyriusException exception; + + @override + Widget build(BuildContext context) { + return SyriusErrorWidget(exception); + } +} + +class _LatestTransactionsPopulated extends StatefulWidget { + const _LatestTransactionsPopulated({ + required this.hasReachedMax, + required this.transactions, + required this.type, + }); + + final bool hasReachedMax; + final List transactions; + final CardType type; + + @override + State<_LatestTransactionsPopulated> createState() => + _LatestTransactionsPopulatedState(); +} + +class _LatestTransactionsPopulatedState + extends State<_LatestTransactionsPopulated> { + late List _transactions; + + bool _sortAscending = true; + + @override + Widget build(BuildContext context) { + return InfiniteScrollTable( + items: widget.transactions, + hasReachedMax: widget.hasReachedMax, + columns: widget.type == CardType.latestTransactionsDashboard + ? _getHeaderColumnsForDashboardWidget() + : _getHeaderColumnsForTransferWidget(), + generateRowCells: _rowCellsGenerator, + onScrollReachedBottom: () { + context.read().add( + InfiniteListMoreRequested( + address: Address.parse(kSelectedAddress!), + ), + ); + }, + ); + } + + List _rowCellsGenerator( + AccountBlock transaction, + ) => + widget.type == CardType.latestTransactionsDashboard + ? _getCellsForDashboardWidget(transaction) + : _getCellsForTransferWidget(transaction); + + List _getCellsForTransferWidget( + AccountBlock transactionBlock, + ) { + final AccountBlock infoBlock = + BlockUtils.isReceiveBlock(transactionBlock.blockType) + ? transactionBlock.pairedAccountBlock! + : transactionBlock; + return [ + AddressCell( + address: infoBlock.address, + ), + AddressCell( + address: infoBlock.toAddress, + ), + HashCell(hash: infoBlock.hash), + AmountCell(block: infoBlock), + DateCell(block: infoBlock), + TypeCell(block: transactionBlock), + AssetCell(block: infoBlock), + ]; + } + + List _getHeaderColumnsForTransferWidget() { + return [ + InfiniteScrollTableColumnType.sender, + InfiniteScrollTableColumnType.receiver, + InfiniteScrollTableColumnType.hash, + InfiniteScrollTableColumnType.amount, + InfiniteScrollTableColumnType.date, + InfiniteScrollTableColumnType.type, + InfiniteScrollTableColumnType.asset, + ]; + } + + List _getHeaderColumnsForDashboardWidget() { + return [ + InfiniteScrollTableColumnType.sender, + InfiniteScrollTableColumnType.amount, + InfiniteScrollTableColumnType.date, + InfiniteScrollTableColumnType.type, + InfiniteScrollTableColumnType.asset, + ]; + } + + List _getCellsForDashboardWidget( + AccountBlock transactionBlock, + ) { + final AccountBlock infoBlock = + BlockUtils.isReceiveBlock(transactionBlock.blockType) + ? transactionBlock.pairedAccountBlock! + : transactionBlock; + + return [ + AddressCell(address: infoBlock.address), + AmountCell(block: infoBlock), + DateCell(block: infoBlock), + TypeCell(block: transactionBlock), + AssetCell(block: infoBlock), + ]; + } + + // TODO(maznnwell): to be used when sorting is enabled + // ignore: unused_element + void _onSortArrowsPressed(InfiniteScrollTableColumnType columnType) { + switch (columnType) { + case InfiniteScrollTableColumnType.sender: + _sortAscending + ? _transactions.sort( + (AccountBlock a, AccountBlock b) => + a.address.toString().compareTo( + b.address.toString(), + ), + ) + : _transactions.sort( + (AccountBlock a, AccountBlock b) => + b.address.toString().compareTo( + a.address.toString(), + ), + ); + case InfiniteScrollTableColumnType.receiver: + _sortAscending + ? _transactions.sort( + (AccountBlock a, AccountBlock b) => + a.toAddress.toString().compareTo( + b.toAddress.toString(), + ), + ) + : _transactions.sort( + (AccountBlock a, AccountBlock b) => + b.toAddress.toString().compareTo( + a.toAddress.toString(), + ), + ); + case InfiniteScrollTableColumnType.hash: + _sortAscending + ? _transactions.sort( + (AccountBlock a, AccountBlock b) => a.hash.toString().compareTo( + b.hash.toString(), + ), + ) + : _transactions.sort( + (AccountBlock a, AccountBlock b) => b.hash.toString().compareTo( + a.hash.toString(), + ), + ); + case InfiniteScrollTableColumnType.amount: + _sortAscending + ? _transactions.sort( + (AccountBlock a, AccountBlock b) => + a.amount.compareTo(b.amount), + ) + : _transactions.sort( + (AccountBlock a, AccountBlock b) => + b.amount.compareTo(a.amount), + ); + case InfiniteScrollTableColumnType.date: + _sortAscending + ? _transactions.sort( + (AccountBlock a, AccountBlock b) => + a.confirmationDetail!.momentumTimestamp.compareTo( + b.confirmationDetail!.momentumTimestamp, + ), + ) + : _transactions.sort( + (AccountBlock a, AccountBlock b) => + b.confirmationDetail!.momentumTimestamp.compareTo( + a.confirmationDetail!.momentumTimestamp, + ), + ); + case InfiniteScrollTableColumnType.type: + _sortAscending + ? _transactions.sort( + (AccountBlock a, AccountBlock b) => + a.blockType.compareTo(b.blockType), + ) + : _transactions.sort( + (AccountBlock a, AccountBlock b) => + b.blockType.compareTo(a.blockType), + ); + case InfiniteScrollTableColumnType.asset: + _sortAscending + ? _transactions.sort( + (AccountBlock a, AccountBlock b) => + a.token!.symbol.compareTo(b.token!.symbol), + ) + : _transactions.sort( + (AccountBlock a, AccountBlock b) => + b.token!.symbol.compareTo(a.token!.symbol), + ); + default: + _sortAscending + ? _transactions.sort( + (AccountBlock a, AccountBlock b) => + a.tokenStandard.toString().compareTo( + b.tokenStandard.toString(), + ), + ) + : _transactions.sort( + (AccountBlock a, AccountBlock b) => + b.tokenStandard.toString().compareTo( + a.tokenStandard.toString(), + ), + ); + break; + } + + setState(() { + _sortAscending = !_sortAscending; + }); + } +} diff --git a/lib/rearchitecture/features/latest_transactions/view/view.dart b/lib/rearchitecture/features/latest_transactions/view/view.dart new file mode 100644 index 00000000..04e20686 --- /dev/null +++ b/lib/rearchitecture/features/latest_transactions/view/view.dart @@ -0,0 +1 @@ +export 'latest_transactions_card.dart'; diff --git a/lib/rearchitecture/features/multiple_balance/bloc/multiple_balance_bloc.dart b/lib/rearchitecture/features/multiple_balance/bloc/multiple_balance_bloc.dart new file mode 100644 index 00000000..298e2bc9 --- /dev/null +++ b/lib/rearchitecture/features/multiple_balance/bloc/multiple_balance_bloc.dart @@ -0,0 +1,89 @@ +import 'dart:async'; +import 'package:equatable/equatable.dart'; +import 'package:hydrated_bloc/hydrated_bloc.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:zenon_syrius_wallet_flutter/blocs/refresh_bloc_mixin.dart'; +import 'package:zenon_syrius_wallet_flutter/rearchitecture/utils/exceptions/exceptions.dart'; +import 'package:zenon_syrius_wallet_flutter/utils/global.dart'; +import 'package:znn_sdk_dart/znn_sdk_dart.dart'; + +part 'multiple_balance_bloc.g.dart'; + +part 'multiple_balance_event.dart'; + +part 'multiple_balance_state.dart'; + +/// A bloc responsible for managing transfer balances for a list of addresses. +class MultipleBalanceBloc + extends HydratedBloc + with RefreshBlocMixin { + /// Creates a new instance of [MultipleBalanceBloc]. + MultipleBalanceBloc({required this.zenon}) + : super(const MultipleBalanceState()) { + on(_onFetchBalances); + listenToWsRestart( + () => add( + MultipleBalanceFetch( + addresses: kDefaultAddressList.map((String? e) => e!).toList(), + ), + ), + ); + } + + /// The Zenon SDK instance for ledger interactions. + final Zenon zenon; + + /// Handles the [MultipleBalanceFetch] event to fetch balances for all + /// addresses. + Future _onFetchBalances( + MultipleBalanceFetch event, + Emitter emit, + ) async { + emit(state.copyWith(status: MultipleBalanceStatus.loading)); + + try { + final Map addressBalanceMap = + {}; + final List accountInfoList = await Future.wait( + event.addresses.map( + _getBalancePerAddress, + ), + ); + + for (final AccountInfo accountInfo in accountInfoList) { + addressBalanceMap[accountInfo.address!] = accountInfo; + } + + emit( + state.copyWith( + status: MultipleBalanceStatus.success, + data: addressBalanceMap, + ), + ); + } catch (error, stackTrace) { + addError(error, stackTrace); + emit( + state.copyWith( + status: MultipleBalanceStatus.failure, + error: FailureException(), + ), + ); + } + } + + /// Retrieves the account information for a specific [address]. + Future _getBalancePerAddress(String address) async { + return zenon.ledger.getAccountInfoByAddress( + Address.parse(address), + ); + } + + /// Deserializes the state from a JSON map. + @override + MultipleBalanceState? fromJson(Map json) => + MultipleBalanceState.fromJson(json); + + /// Serializes the current state into a JSON map for persistence. + @override + Map? toJson(MultipleBalanceState state) => state.toJson(); +} diff --git a/lib/rearchitecture/features/multiple_balance/bloc/multiple_balance_bloc.g.dart b/lib/rearchitecture/features/multiple_balance/bloc/multiple_balance_bloc.g.dart new file mode 100644 index 00000000..a06d5030 --- /dev/null +++ b/lib/rearchitecture/features/multiple_balance/bloc/multiple_balance_bloc.g.dart @@ -0,0 +1,36 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'multiple_balance_bloc.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +MultipleBalanceState _$MultipleBalanceStateFromJson( + Map json) => + MultipleBalanceState( + status: + $enumDecodeNullable(_$MultipleBalanceStatusEnumMap, json['status']) ?? + MultipleBalanceStatus.initial, + data: (json['data'] as Map?)?.map( + (k, e) => MapEntry(k, AccountInfo.fromJson(e as Map)), + ), + error: json['error'] == null + ? null + : SyriusException.fromJson(json['error'] as Map), + ); + +Map _$MultipleBalanceStateToJson( + MultipleBalanceState instance) => + { + 'status': _$MultipleBalanceStatusEnumMap[instance.status]!, + 'data': instance.data?.map((k, e) => MapEntry(k, e.toJson())), + 'error': instance.error?.toJson(), + }; + +const _$MultipleBalanceStatusEnumMap = { + MultipleBalanceStatus.failure: 'failure', + MultipleBalanceStatus.initial: 'initial', + MultipleBalanceStatus.loading: 'loading', + MultipleBalanceStatus.success: 'success', +}; diff --git a/lib/rearchitecture/features/multiple_balance/bloc/multiple_balance_event.dart b/lib/rearchitecture/features/multiple_balance/bloc/multiple_balance_event.dart new file mode 100644 index 00000000..120ee098 --- /dev/null +++ b/lib/rearchitecture/features/multiple_balance/bloc/multiple_balance_event.dart @@ -0,0 +1,19 @@ +part of 'multiple_balance_bloc.dart'; + +/// The base class for events in [MultipleBalanceBloc]. +/// +/// Events extending this class are used to trigger state changes in the Bloc. +sealed class MultipleBalanceEvent extends Equatable { + /// Constructs a new `TransferBalanceEvent`. + const MultipleBalanceEvent(); +} + +/// Event to initiate fetching balances for all addresses. +class MultipleBalanceFetch extends MultipleBalanceEvent { + /// Creates a new instance. + const MultipleBalanceFetch({required this.addresses}); + /// The list of addresses whose balances are being managed. + final List addresses; + @override + List get props => []; +} diff --git a/lib/rearchitecture/features/multiple_balance/bloc/multiple_balance_state.dart b/lib/rearchitecture/features/multiple_balance/bloc/multiple_balance_state.dart new file mode 100644 index 00000000..5dc43ccd --- /dev/null +++ b/lib/rearchitecture/features/multiple_balance/bloc/multiple_balance_state.dart @@ -0,0 +1,62 @@ +part of 'multiple_balance_bloc.dart'; + +/// Represents the status of the balance fetching process. +enum MultipleBalanceStatus { + /// Indicates that an error occurred during balance fetching. + failure, + /// The initial state before any balance fetching has occurred. + initial, + /// Indicates that balance fetching is currently in progress. + loading, + /// Indicates that balance fetching was successful. + success, +} + +/// Holds the state of [MultipleBalanceBloc], including status, data, +/// and error information. +@JsonSerializable(explicitToJson: true) +class MultipleBalanceState extends Equatable { + /// Creates a new instance of [MultipleBalanceState]. + /// + /// The [status] defaults to [MultipleBalanceStatus.initial] if not specified. + const MultipleBalanceState({ + this.status = MultipleBalanceStatus.initial, + this.data, + this.error, + }); + + /// Creates a new instance from a JSON map. + factory MultipleBalanceState.fromJson(Map json) => + _$MultipleBalanceStateFromJson(json); + + /// The current status of the balance fetching operation. + final MultipleBalanceStatus status; + + /// A map of addresses to their corresponding [AccountInfo]. + /// + /// Contains the balance information for each address. + final Map? data; + + /// An object representing any error that occurred during balance fetching. + final SyriusException? error; + + /// {@macro state_copy_with} + MultipleBalanceState copyWith({ + MultipleBalanceStatus? status, + Map? data, + SyriusException? error, + }) { + return MultipleBalanceState( + status: status ?? this.status, + data: data ?? this.data, + error: error ?? this.error, + ); + } + + /// {@macro state_to_json} + Map toJson() => _$MultipleBalanceStateToJson(this); + + + @override + List get props => [status, data, error]; +} diff --git a/lib/rearchitecture/features/multiple_balance/multiple_balance.dart b/lib/rearchitecture/features/multiple_balance/multiple_balance.dart new file mode 100644 index 00000000..cabb2462 --- /dev/null +++ b/lib/rearchitecture/features/multiple_balance/multiple_balance.dart @@ -0,0 +1 @@ +export 'bloc/multiple_balance_bloc.dart'; diff --git a/lib/rearchitecture/features/pending_transactions/bloc/pending_transactions_bloc.dart b/lib/rearchitecture/features/pending_transactions/bloc/pending_transactions_bloc.dart new file mode 100644 index 00000000..708a964e --- /dev/null +++ b/lib/rearchitecture/features/pending_transactions/bloc/pending_transactions_bloc.dart @@ -0,0 +1,38 @@ +import 'dart:async'; + +import 'package:zenon_syrius_wallet_flutter/rearchitecture/utils/utils.dart'; +import 'package:znn_sdk_dart/znn_sdk_dart.dart'; + + +/// A cubit responsible for fetching and managing the list of pending +/// transactions for an address. +class PendingTransactionsBloc extends InfiniteListBloc { + /// Creates a new [PendingTransactionsBloc] instance. + /// + /// Requires a [Zenon] instance to interact with the ledger + PendingTransactionsBloc({ + required super.zenon, + super.pageSize = kPageSize, + }) : super( + fromJsonT: (Object? map) => AccountBlock.fromJson( + map! as Map, + ), + toJsonT: (AccountBlock block) => block.toJson(), + ); + + @override + Future> paginationFetch({ + required Address address, + required int pageIndex, + required int pageSize, + }) async { + final AccountBlockList accountBlock = + await zenon.ledger.getUnreceivedBlocksByAddress( + address, + pageIndex: pageIndex, + pageSize: pageSize, + ); + + return accountBlock.list!; + } +} diff --git a/lib/rearchitecture/features/pending_transactions/pending_transactions.dart b/lib/rearchitecture/features/pending_transactions/pending_transactions.dart new file mode 100644 index 00000000..f3e186ed --- /dev/null +++ b/lib/rearchitecture/features/pending_transactions/pending_transactions.dart @@ -0,0 +1,2 @@ +export 'bloc/pending_transactions_bloc.dart'; +export 'view/view.dart'; diff --git a/lib/rearchitecture/features/pending_transactions/view/pending_transactions_card.dart b/lib/rearchitecture/features/pending_transactions/view/pending_transactions_card.dart new file mode 100644 index 00000000..ccdb4796 --- /dev/null +++ b/lib/rearchitecture/features/pending_transactions/view/pending_transactions_card.dart @@ -0,0 +1,253 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:zenon_syrius_wallet_flutter/rearchitecture/features/features.dart'; +import 'package:zenon_syrius_wallet_flutter/rearchitecture/utils/utils.dart'; +import 'package:zenon_syrius_wallet_flutter/utils/global.dart'; +import 'package:zenon_syrius_wallet_flutter/widgets/widgets.dart' + hide + InfiniteScrollTable, + InfiniteScrollTableCell, + InfiniteScrollTableHeaderColumn; +import 'package:znn_sdk_dart/znn_sdk_dart.dart'; + +/// A widget that displays the current pending transactions. +/// +/// Optionally, the user can request for a specific transaction to be received. +class PendingTransactionsCard extends StatelessWidget { + /// Creates a new instance. + const PendingTransactionsCard({super.key}); + + @override + Widget build(BuildContext context) { + final PendingTransactionsBloc bloc = + context.read(); + + return NewCardScaffold( + data: CardType.pendingTransactions.getData(context: context), + onRefreshPressed: () { + bloc.add( + InfiniteListRefreshRequested( + address: Address.parse(kSelectedAddress!), + ), + ); + }, + body: + BlocBuilder>( + builder: (_, InfiniteListState state) { + final InfiniteListStatus status = state.status; + + return switch (status) { + InfiniteListStatus.initial => const _PendingTransactionsInitial(), + InfiniteListStatus.failure => _PendingTransactionsFailure( + exception: state.error!, + ), + InfiniteListStatus.success => _PendingTransactionsPopulated( + bloc: bloc, + hasReachedMax: state.hasReachedMax, + transactions: state.data!, + ), + }; + }, + ), + ); + } +} + +class _PendingTransactionsInitial extends StatelessWidget { + const _PendingTransactionsInitial(); + + @override + Widget build(BuildContext context) { + return const SyriusLoadingWidget(); + } +} + +class _PendingTransactionsFailure extends StatelessWidget { + const _PendingTransactionsFailure({required this.exception}); + + final SyriusException exception; + + @override + Widget build(BuildContext context) { + return SyriusErrorWidget(exception.message); + } +} + +class _PendingTransactionsPopulated extends StatefulWidget { + const _PendingTransactionsPopulated({ + required this.bloc, + required this.hasReachedMax, + required this.transactions, + }); + + final PendingTransactionsBloc bloc; + final bool hasReachedMax; + final List transactions; + + @override + State<_PendingTransactionsPopulated> createState() => + _PendingTransactionsPopulatedState(); +} + +class _PendingTransactionsPopulatedState + extends State<_PendingTransactionsPopulated> { + bool _sortAscending = true; + + @override + Widget build(BuildContext context) { + return InfiniteScrollTable( + generateRowCells: _rowCellsGenerator, + hasReachedMax: widget.hasReachedMax, + columns: _getHeaderColumnsForPendingTransactions(), + items: widget.transactions, + onScrollReachedBottom: () { + widget.bloc.add( + InfiniteListRequested( + address: Address.parse(kSelectedAddress!), + ), + ); + }, + ); + } + + List _rowCellsGenerator(AccountBlock transaction) => + _getCellsForPendingTransactions(transaction); + + List _getCellsForPendingTransactions( + AccountBlock transaction, + ) { + final AccountBlock infoBlock = + BlockUtils.isReceiveBlock(transaction.blockType) + ? transaction.pairedAccountBlock! + : transaction; + return [ + AddressCell( + address: infoBlock.address, + ), + AddressCell( + address: infoBlock.toAddress, + ), + HashCell( + hash: infoBlock.hash, + ), + AmountCell(block: infoBlock), + DateCell( + block: infoBlock, + ), + AssetCell(block: infoBlock), + ReceiveCell(hash: infoBlock.hash), + ]; + } + + List + _getHeaderColumnsForPendingTransactions() { + return [ + InfiniteScrollTableColumnType.sender, + InfiniteScrollTableColumnType.receiver, + InfiniteScrollTableColumnType.hash, + InfiniteScrollTableColumnType.amount, + InfiniteScrollTableColumnType.date, + InfiniteScrollTableColumnType.asset, + InfiniteScrollTableColumnType.blank, + ]; + } + + // TODO(maznnwell): to be used when sorting is enabled + // ignore: unused_element + void _onSortArrowsPressed(InfiniteScrollTableColumnType columnType) { + switch (columnType) { + case InfiniteScrollTableColumnType.sender: + _sortAscending + ? widget.transactions.sort( + (AccountBlock a, AccountBlock b) => + a.address.toString().compareTo( + b.address.toString(), + ), + ) + : widget.transactions.sort( + (AccountBlock a, AccountBlock b) => + b.address.toString().compareTo( + a.address.toString(), + ), + ); + case InfiniteScrollTableColumnType.receiver: + _sortAscending + ? widget.transactions.sort( + (AccountBlock a, AccountBlock b) => + a.toAddress.toString().compareTo( + b.toAddress.toString(), + ), + ) + : widget.transactions.sort( + (AccountBlock a, AccountBlock b) => + b.toAddress.toString().compareTo( + a.toAddress.toString(), + ), + ); + case InfiniteScrollTableColumnType.hash: + _sortAscending + ? widget.transactions.sort( + (AccountBlock a, AccountBlock b) => a.hash.toString().compareTo( + b.hash.toString(), + ), + ) + : widget.transactions.sort( + (AccountBlock a, AccountBlock b) => b.hash.toString().compareTo( + a.hash.toString(), + ), + ); + case InfiniteScrollTableColumnType.amount: + _sortAscending + ? widget.transactions.sort( + (AccountBlock a, AccountBlock b) => + a.amount.compareTo(b.amount), + ) + : widget.transactions.sort( + (AccountBlock a, AccountBlock b) => + b.amount.compareTo(a.amount), + ); + case InfiniteScrollTableColumnType.date: + _sortAscending + ? widget.transactions.sort( + (AccountBlock a, AccountBlock b) => + a.confirmationDetail!.momentumTimestamp.compareTo( + b.confirmationDetail!.momentumTimestamp, + ), + ) + : widget.transactions.sort( + (AccountBlock a, AccountBlock b) => + b.confirmationDetail!.momentumTimestamp.compareTo( + a.confirmationDetail!.momentumTimestamp, + ), + ); + case InfiniteScrollTableColumnType.asset: + _sortAscending + ? widget.transactions.sort( + (AccountBlock a, AccountBlock b) => + a.token!.symbol.compareTo(b.token!.symbol), + ) + : widget.transactions.sort( + (AccountBlock a, AccountBlock b) => + b.token!.symbol.compareTo(a.token!.symbol), + ); + default: + _sortAscending + ? widget.transactions.sort( + (AccountBlock a, AccountBlock b) => + a.tokenStandard.toString().compareTo( + b.tokenStandard.toString(), + ), + ) + : widget.transactions.sort( + (AccountBlock a, AccountBlock b) => + b.tokenStandard.toString().compareTo( + a.tokenStandard.toString(), + ), + ); + } + + setState(() { + _sortAscending = !_sortAscending; + }); + } +} diff --git a/lib/rearchitecture/features/pending_transactions/view/view.dart b/lib/rearchitecture/features/pending_transactions/view/view.dart new file mode 100644 index 00000000..3493792f --- /dev/null +++ b/lib/rearchitecture/features/pending_transactions/view/view.dart @@ -0,0 +1 @@ +export 'pending_transactions_card.dart'; diff --git a/lib/rearchitecture/features/receive/receive.dart b/lib/rearchitecture/features/receive/receive.dart new file mode 100644 index 00000000..3b46d13d --- /dev/null +++ b/lib/rearchitecture/features/receive/receive.dart @@ -0,0 +1,2 @@ +export 'view/view.dart'; +export 'widgets/widgets.dart'; diff --git a/lib/rearchitecture/features/receive/view/receive_card.dart b/lib/rearchitecture/features/receive/view/receive_card.dart new file mode 100644 index 00000000..b2411da0 --- /dev/null +++ b/lib/rearchitecture/features/receive/view/receive_card.dart @@ -0,0 +1,32 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:zenon_syrius_wallet_flutter/rearchitecture/features/features.dart'; +import 'package:zenon_syrius_wallet_flutter/rearchitecture/features/tokens/cubit/tokens_cubit.dart'; +import 'package:zenon_syrius_wallet_flutter/rearchitecture/utils/utils.dart'; + +/// A card that listens to state updates from the [TokensCubit] and rebuilds, +/// feeding a specific widget, depending on the [TokensState.status]. +class ReceiveCard extends StatelessWidget { + /// Creates a new instance. + const ReceiveCard({super.key}); + + @override + Widget build(BuildContext context) { + return NewCardScaffold( + data: CardType.receive.getData(context: context), + onRefreshPressed: () { + context.read().fetch(); + }, + body: BlocBuilder( + builder: (_, TokensState state) { + final TokensStatus status = state.status; + return switch (status) { + TokensStatus.failure => ReceiveError(error: state.error!), + TokensStatus.initial => const ReceiveInitial(), + TokensStatus.success => ReceivePopulated(assets: state.data!), + }; + }, + ), + ); + } +} diff --git a/lib/rearchitecture/features/receive/view/view.dart b/lib/rearchitecture/features/receive/view/view.dart new file mode 100644 index 00000000..52139741 --- /dev/null +++ b/lib/rearchitecture/features/receive/view/view.dart @@ -0,0 +1 @@ +export 'receive_card.dart'; diff --git a/lib/rearchitecture/features/receive/widgets/receive_error.dart b/lib/rearchitecture/features/receive/widgets/receive_error.dart new file mode 100644 index 00000000..48796ca7 --- /dev/null +++ b/lib/rearchitecture/features/receive/widgets/receive_error.dart @@ -0,0 +1,17 @@ +import 'package:flutter/material.dart'; +import 'package:zenon_syrius_wallet_flutter/rearchitecture/utils/exceptions/exceptions.dart'; +import 'package:zenon_syrius_wallet_flutter/widgets/reusable_widgets/error_widget.dart'; + +/// A widget that displays an error message +class ReceiveError extends StatelessWidget { + /// Creates a new instance. + const ReceiveError({required this.error, super.key}); + + /// The field that holds the error message. + final SyriusException error; + + @override + Widget build(BuildContext context) { + return SyriusErrorWidget(error.message); + } +} diff --git a/lib/rearchitecture/features/receive/widgets/receive_initial.dart b/lib/rearchitecture/features/receive/widgets/receive_initial.dart new file mode 100644 index 00000000..992c0a2b --- /dev/null +++ b/lib/rearchitecture/features/receive/widgets/receive_initial.dart @@ -0,0 +1,13 @@ +import 'package:flutter/material.dart'; +import 'package:zenon_syrius_wallet_flutter/widgets/reusable_widgets/loading_widget.dart'; + +/// A widget that displays a loading indicator +class ReceiveInitial extends StatelessWidget { + /// Creates a new instance. + const ReceiveInitial({super.key}); + + @override + Widget build(BuildContext context) { + return const SyriusLoadingWidget(); + } +} diff --git a/lib/rearchitecture/features/receive/widgets/receive_populated.dart b/lib/rearchitecture/features/receive/widgets/receive_populated.dart new file mode 100644 index 00000000..69afd8e1 --- /dev/null +++ b/lib/rearchitecture/features/receive/widgets/receive_populated.dart @@ -0,0 +1,145 @@ +import 'package:flutter/material.dart'; +import 'package:zenon_syrius_wallet_flutter/rearchitecture/utils/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/global.dart'; +import 'package:zenon_syrius_wallet_flutter/utils/input_validators.dart'; +import 'package:zenon_syrius_wallet_flutter/widgets/widgets.dart'; +import 'package:znn_sdk_dart/znn_sdk_dart.dart'; + +/// A widget that helps the user generate a QR for requesting funds. +/// +/// It has two dropdowns: +/// - one for the address on which the funds will be received. +/// - one for the asset that will be transferred - token or native coin. +/// +/// And a [TextField] for inputting the transfer amount. +/// +/// This data, destination address, asset and amount, is found in the QR. +class ReceivePopulated extends StatefulWidget { + /// Creates a new instance. + const ReceivePopulated({ + required this.assets, + super.key, + }); + + /// The list of available assets on the network - includes tokens and coins + final List assets; + + @override + State createState() => _ReceivePopulatedState(); +} + +class _ReceivePopulatedState extends State { + final TextEditingController _amountController = TextEditingController(); + + String _selectedSenderAddress = kSelectedAddress!; + + late Token _selectedToken; + + String get _amount => _amountController.text; + + String? get _amountErrorText => InputValidators.correctValue( + _amount, + kBigP255m1, + _selectedToken.decimals, + BigInt.zero, + ); + + @override + void initState() { + super.initState(); + _selectedToken = widget.assets.firstWhere( + (Token asset) => asset.tokenStandard.toString() == znnTokenStandard, + ); + } + + @override + Widget build(BuildContext context) { + final List sortedAssets = sortAssets(widget.assets); + + return Padding( + padding: const EdgeInsets.all(16), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ReceiveQrImage( + data: _getQrString(), + size: 150, + tokenStandard: _selectedToken.tokenStandard, + ), + kHorizontalGap16, + Expanded( + child: Column( + children: [ + Row( + children: [ + Expanded( + child: _getDefaultAddressDropdown(), + ), + CopyToClipboardButton( + _selectedSenderAddress, + ), + ], + ), + kVerticalGap16, + ZtsDropdown( + availableTokens: sortedAssets, + onChangeCallback: (Token token) => setState(() { + _selectedToken = token; + }), + selectedToken: _selectedToken, + ), + kVerticalGap16, + TextField( + decoration: InputDecoration( + errorText: _amount.isNotEmpty ? _amountErrorText : null, + hintText: context.l10n.amount, + ), + onChanged: (String value) => setState(() {}), + inputFormatters: FormatUtils.getAmountTextInputFormatters( + _amountController.text, + ), + controller: _amountController, + ), + ], + ), + ), + ], + ), + ); + } + + String _getQrString() { + return '${_selectedToken.symbol.toLowerCase()}:' + '$_selectedSenderAddress?zts=${_selectedToken.tokenStandard}' + '&amount=${_getAmount()}'; + } + + BigInt _getAmount() { + try { + return _amountController.text.extractDecimals(_selectedToken.decimals); + } catch (e) { + return BigInt.zero; + } + } + + Widget _getDefaultAddressDropdown() { + return NewAddressesDropdown( + addresses: kDefaultAddressList.map((String? e) => e!).toList(), + selectedAddress: _selectedSenderAddress, + onSelectedCallback: (String value) => setState( + () { + _selectedSenderAddress = value; + }, + ), + ); + } + + @override + void dispose() { + _amountController.dispose(); + super.dispose(); + } +} diff --git a/lib/rearchitecture/features/receive/widgets/widgets.dart b/lib/rearchitecture/features/receive/widgets/widgets.dart new file mode 100644 index 00000000..d49335ba --- /dev/null +++ b/lib/rearchitecture/features/receive/widgets/widgets.dart @@ -0,0 +1,3 @@ +export 'receive_error.dart'; +export 'receive_initial.dart'; +export 'receive_populated.dart'; diff --git a/lib/rearchitecture/features/send/bloc/bloc.dart b/lib/rearchitecture/features/send/bloc/bloc.dart new file mode 100644 index 00000000..91235bd1 --- /dev/null +++ b/lib/rearchitecture/features/send/bloc/bloc.dart @@ -0,0 +1 @@ +export 'send_transaction/send_transaction_bloc.dart'; diff --git a/lib/rearchitecture/features/send/bloc/send_transaction/send_transaction_bloc.dart b/lib/rearchitecture/features/send/bloc/send_transaction/send_transaction_bloc.dart new file mode 100644 index 00000000..da32b19e --- /dev/null +++ b/lib/rearchitecture/features/send/bloc/send_transaction/send_transaction_bloc.dart @@ -0,0 +1,129 @@ +import 'dart:async'; + +import 'package:equatable/equatable.dart'; +import 'package:hydrated_bloc/hydrated_bloc.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:logging/logging.dart'; +import 'package:zenon_syrius_wallet_flutter/rearchitecture/utils/utils.dart'; +import 'package:zenon_syrius_wallet_flutter/utils/utils.dart'; +import 'package:znn_sdk_dart/znn_sdk_dart.dart'; + +part 'send_transaction_bloc.g.dart'; + +part 'send_transaction_event.dart'; + +part 'send_transaction_state.dart'; + +/// A bloc that handles sending payments. +class SendTransactionBloc + extends HydratedBloc { + /// Creates a new instance of [SendTransactionBloc]. + /// + /// Initializes the Bloc with the initial state and sets up event handlers for + /// [SendTransactionInitiate] and [SendTransactionInitiateFromBlock] events. + SendTransactionBloc({ + AccountBlockUtils? accountBlockUtilsHelper, + ZenonAddressUtils? zenonAddressUtils, + }) : + accountBlockUtilsHelper = + accountBlockUtilsHelper ?? AccountBlockUtils(), + zenonAddressUtilsHelper = zenonAddressUtils ?? ZenonAddressUtils(), + super(const SendTransactionState()) { + on(_onSendTransfer); + on(_onSendTransferWithBlock); + } + + /// Helper class with the purpose of facilitating dependency injections. + final AccountBlockUtils accountBlockUtilsHelper; + + /// Helper class with the purpose of facilitating dependency injections. + final ZenonAddressUtils zenonAddressUtilsHelper; + + Future _onSendTransfer( + SendTransactionInitiate event, + Emitter emit, + ) async { + try { + emit(state.copyWith(status: SendTransactionStatus.loading)); + + final AccountBlockTemplate accountBlock = AccountBlockTemplate.send( + Address.parse(event.toAddress), + event.token.tokenStandard, + event.amount, + event.data, + ); + + final AccountBlockTemplate response = + await accountBlockUtilsHelper.createAccountBlock( + accountBlock, + 'send transaction', + address: Address.parse(event.fromAddress), + waitForRequiredPlasma: true, + ); + + zenonAddressUtilsHelper.refreshBalance(); + emit( + state.copyWith( + status: SendTransactionStatus.success, + data: response, + ), + ); + } catch (error, stackTrace) { + emit( + state.copyWith( + status: SendTransactionStatus.failure, + error: FailureException(), + ), + ); + addError(error, stackTrace); + } + } + + Future _onSendTransferWithBlock( + SendTransactionInitiateFromBlock event, + Emitter emit, + ) async { + try { + emit(state.copyWith(status: SendTransactionStatus.loading)); + final AccountBlockTemplate response = + await accountBlockUtilsHelper.createAccountBlock( + event.block, + 'send transaction', + address: Address.parse(event.fromAddress), + waitForRequiredPlasma: true, + ); + + zenonAddressUtilsHelper.refreshBalance(); + emit( + state.copyWith( + status: SendTransactionStatus.success, + data: response, + ), + ); + } catch (error) { + emit( + state.copyWith( + status: SendTransactionStatus.failure, + error: FailureException(), + ), + ); + } + } + + @override + SendTransactionState? fromJson(Map json) => + SendTransactionState.fromJson(json); + + @override + Map? toJson(SendTransactionState state) => state.toJson(); + + @override + void onError(Object error, StackTrace stackTrace) { + Logger('SendTransactionBloc').warning( + 'onError triggered', + error, + stackTrace, + ); + super.onError(error, stackTrace); + } +} diff --git a/lib/rearchitecture/features/send/bloc/send_transaction/send_transaction_bloc.g.dart b/lib/rearchitecture/features/send/bloc/send_transaction/send_transaction_bloc.g.dart new file mode 100644 index 00000000..13cc7ae5 --- /dev/null +++ b/lib/rearchitecture/features/send/bloc/send_transaction/send_transaction_bloc.g.dart @@ -0,0 +1,36 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'send_transaction_bloc.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +SendTransactionState _$SendTransactionStateFromJson( + Map json) => + SendTransactionState( + status: + $enumDecodeNullable(_$SendTransactionStatusEnumMap, json['status']) ?? + SendTransactionStatus.initial, + data: json['data'] == null + ? null + : AccountBlockTemplate.fromJson(json['data'] as Map), + error: json['error'] == null + ? null + : SyriusException.fromJson(json['error'] as Map), + ); + +Map _$SendTransactionStateToJson( + SendTransactionState instance) => + { + 'status': _$SendTransactionStatusEnumMap[instance.status]!, + 'data': instance.data?.toJson(), + 'error': instance.error?.toJson(), + }; + +const _$SendTransactionStatusEnumMap = { + SendTransactionStatus.initial: 'initial', + SendTransactionStatus.loading: 'loading', + SendTransactionStatus.success: 'success', + SendTransactionStatus.failure: 'failure', +}; diff --git a/lib/rearchitecture/features/send/bloc/send_transaction/send_transaction_event.dart b/lib/rearchitecture/features/send/bloc/send_transaction/send_transaction_event.dart new file mode 100644 index 00000000..b3e65f1b --- /dev/null +++ b/lib/rearchitecture/features/send/bloc/send_transaction/send_transaction_event.dart @@ -0,0 +1,53 @@ +part of 'send_transaction_bloc.dart'; + +/// The base class for events in `SendPaymentBloc`. +sealed class SendTransactionEvent extends Equatable {} + +/// Event to initiate sending a transfer. +class SendTransactionInitiate extends SendTransactionEvent { + /// Creates a [SendTransactionInitiate] event. + SendTransactionInitiate({ + required this.fromAddress, + required this.toAddress, + required this.amount, + required this.token, + this.data, + }); + + /// The address from which the payment is sent. + final String fromAddress; + + /// The address to which the payment is sent. + final String toAddress; + + /// The amount to be transferred. + final BigInt amount; + + /// Optional data associated with the transfer. + final List? data; + + /// The token being transferred. + final Token token; + + @override + List get props => + [fromAddress, toAddress, amount, token, data]; +} + +/// Event to initiate sending a transfer using an existing account block. +class SendTransactionInitiateFromBlock extends SendTransactionEvent { + /// Creates a [SendTransactionInitiateFromBlock] event. + SendTransactionInitiateFromBlock({ + required this.block, + required this.fromAddress, + }); + + /// The account block template representing the transfer. + final AccountBlockTemplate block; + + /// The address from which the payment is sent. + final String fromAddress; + + @override + List get props => [block, fromAddress]; +} diff --git a/lib/rearchitecture/features/send/bloc/send_transaction/send_transaction_state.dart b/lib/rearchitecture/features/send/bloc/send_transaction/send_transaction_state.dart new file mode 100644 index 00000000..a14879a8 --- /dev/null +++ b/lib/rearchitecture/features/send/bloc/send_transaction/send_transaction_state.dart @@ -0,0 +1,64 @@ +part of 'send_transaction_bloc.dart'; + +/// Represents the possible statuses of the send payment operation. +enum SendTransactionStatus { + /// The initial state before any payment action has been taken. + initial, + + /// Indicates that the payment process is currently in progress. + loading, + + /// Indicates that the payment was successfully processed. + success, + + /// Indicates that an error occurred during the payment process. + failure +} + +/// Holds the state for [SendTransactionBloc], including status, data, +/// and error information. +@JsonSerializable(explicitToJson: true) +class SendTransactionState extends Equatable { + /// Creates a new instance of [SendTransactionState]. + /// + /// The [status] defaults to [SendTransactionStatus.initial] if not specified. + const SendTransactionState({ + this.status = SendTransactionStatus.initial, + this.data, + this.error, + }); + + /// Creates a new instance from a JSON map. + factory SendTransactionState.fromJson(Map json) => + _$SendTransactionStateFromJson(json); + + /// The current status of the send payment operation. + final SendTransactionStatus status; + + /// The response data from the send payment operation. + /// + /// Contains the `AccountBlockTemplate` representing the transaction. + final AccountBlockTemplate? data; + + /// An object representing any error occurring during the payment operation. + final SyriusException? error; + + /// {@macro state_copy_with} + SendTransactionState copyWith({ + SendTransactionStatus? status, + AccountBlockTemplate? data, + SyriusException? error, + }) { + return SendTransactionState( + status: status ?? this.status, + data: data ?? this.data, + error: error ?? this.error, + ); + } + + /// {@macro state_to_json} + Map toJson() => _$SendTransactionStateToJson(this); + + @override + List get props => [status, data, error]; +} diff --git a/lib/rearchitecture/features/send/send.dart b/lib/rearchitecture/features/send/send.dart new file mode 100644 index 00000000..9dc206ba --- /dev/null +++ b/lib/rearchitecture/features/send/send.dart @@ -0,0 +1,3 @@ +export 'bloc/bloc.dart'; +export 'view/view.dart'; +export 'widgets/widgets.dart'; diff --git a/lib/rearchitecture/features/send/view/send_card.dart b/lib/rearchitecture/features/send/view/send_card.dart new file mode 100644 index 00000000..336ca161 --- /dev/null +++ b/lib/rearchitecture/features/send/view/send_card.dart @@ -0,0 +1,36 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:zenon_syrius_wallet_flutter/main.dart'; +import 'package:zenon_syrius_wallet_flutter/rearchitecture/features/features.dart'; +import 'package:zenon_syrius_wallet_flutter/rearchitecture/utils/utils.dart'; +import 'package:zenon_syrius_wallet_flutter/utils/utils.dart'; + +/// A widget to be used along with `Receive` card. +class SendCard extends StatelessWidget { + /// Creates a new instance. + const SendCard({super.key}); + + @override + Widget build(BuildContext context) { + return NewCardScaffold( + data: CardType.send.getData(context: context), + onRefreshPressed: () { + sl.get().add( + MultipleBalanceFetch( + addresses: kDefaultAddressList.map((String? e) => e!).toList(), + ), + ); + }, + body: BlocBuilder( + builder: (_, MultipleBalanceState state) => switch (state.status) { + MultipleBalanceStatus.failure => SendError(error: state.error!), + MultipleBalanceStatus.initial => const SendEmpty(), + MultipleBalanceStatus.loading => const SendLoading(), + MultipleBalanceStatus.success => SendPopulated( + balances: state.data!, + ), + }, + ), + ); + } +} diff --git a/lib/rearchitecture/features/send/view/view.dart b/lib/rearchitecture/features/send/view/view.dart new file mode 100644 index 00000000..24cb798a --- /dev/null +++ b/lib/rearchitecture/features/send/view/view.dart @@ -0,0 +1 @@ +export 'send_card.dart'; diff --git a/lib/rearchitecture/features/send/widgets/send_button.dart b/lib/rearchitecture/features/send/widgets/send_button.dart new file mode 100644 index 00000000..63af7ea8 --- /dev/null +++ b/lib/rearchitecture/features/send/widgets/send_button.dart @@ -0,0 +1,18 @@ +import 'package:flutter/material.dart'; +import 'package:zenon_syrius_wallet_flutter/widgets/widgets.dart'; + +/// A custom [LoadingButton] that has a fixed horizontal padding around the +/// child widget +class SendButton extends LoadingButton { + /// Creates a new instance, with a default [minimumSize] + const SendButton({ + required super.onPressed, + required super.key, + required super.text, + super.minimumSize = const Size(100, 48), + }) : super( + paddingAroundChild: const EdgeInsets.symmetric( + horizontal: 10, + ), + ); +} diff --git a/lib/rearchitecture/features/send/widgets/send_empty.dart b/lib/rearchitecture/features/send/widgets/send_empty.dart new file mode 100644 index 00000000..73e8cbab --- /dev/null +++ b/lib/rearchitecture/features/send/widgets/send_empty.dart @@ -0,0 +1,15 @@ +import 'package:flutter/material.dart'; +import 'package:zenon_syrius_wallet_flutter/rearchitecture/utils/utils.dart'; +import 'package:zenon_syrius_wallet_flutter/widgets/reusable_widgets/reusable_widgets.dart'; + +/// A widget that display a error message - indicating that no data was yet +/// loaded +class SendEmpty extends StatelessWidget { + /// Creates new instance. + const SendEmpty({super.key}); + + @override + Widget build(BuildContext context) { + return SyriusErrorWidget(context.l10n.waitingForDataFetching); + } +} diff --git a/lib/rearchitecture/features/send/widgets/send_error.dart b/lib/rearchitecture/features/send/widgets/send_error.dart new file mode 100644 index 00000000..c926d04f --- /dev/null +++ b/lib/rearchitecture/features/send/widgets/send_error.dart @@ -0,0 +1,16 @@ +import 'package:flutter/material.dart'; +import 'package:zenon_syrius_wallet_flutter/rearchitecture/utils/exceptions/exceptions.dart'; +import 'package:zenon_syrius_wallet_flutter/widgets/reusable_widgets/error_widget.dart'; + +/// A widget that display an error message +class SendError extends StatelessWidget { + /// Creates a new instance. + const SendError({required this.error, super.key}); + /// The object containing the error message + final SyriusException error; + + @override + Widget build(BuildContext context) { + return SyriusErrorWidget(error); + } +} diff --git a/lib/rearchitecture/features/send/widgets/send_loading.dart b/lib/rearchitecture/features/send/widgets/send_loading.dart new file mode 100644 index 00000000..98c55907 --- /dev/null +++ b/lib/rearchitecture/features/send/widgets/send_loading.dart @@ -0,0 +1,13 @@ +import 'package:flutter/material.dart'; +import 'package:zenon_syrius_wallet_flutter/widgets/reusable_widgets/loading_widget.dart'; + +/// A widget that display a loading indicator. +class SendLoading extends StatelessWidget { + /// Creates a new instance. + const SendLoading({super.key}); + + @override + Widget build(BuildContext context) { + return const SyriusLoadingWidget(); + } +} diff --git a/lib/rearchitecture/features/send/widgets/send_populated.dart b/lib/rearchitecture/features/send/widgets/send_populated.dart new file mode 100644 index 00000000..ddd29924 --- /dev/null +++ b/lib/rearchitecture/features/send/widgets/send_populated.dart @@ -0,0 +1,369 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.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/rearchitecture/features/send/send.dart'; +import 'package:zenon_syrius_wallet_flutter/rearchitecture/utils/utils.dart'; +import 'package:zenon_syrius_wallet_flutter/utils/address_utils.dart'; +import 'package:zenon_syrius_wallet_flutter/utils/clipboard_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/global.dart'; +import 'package:zenon_syrius_wallet_flutter/utils/input_validators.dart'; +import 'package:zenon_syrius_wallet_flutter/utils/notification_utils.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'; + +/// A widget with multiple [DropdownMenu] and [TextField] that allow the user +/// to select and input the information needed for sending a transaction +class SendPopulated extends StatefulWidget { + /// Creates a new instance. + const SendPopulated({ + required this.balances, + super.key, + }); + + /// A map with wallet addresses as keys, and account info objects as values + final Map balances; + + @override + State createState() => _SendPopulatedState(); +} + +class _SendPopulatedState extends State { + final TextEditingController _recipientController = TextEditingController(); + final TextEditingController _amountController = TextEditingController(); + + final FocusNode _recipientFocusNode = FocusNode(); + final FocusNode _amountFocusNode = FocusNode(); + + final GlobalKey _sendPaymentButtonKey = GlobalKey(); + + final List _availableAssets = []; + + Token _selectedToken = kDualCoin.first; + + String _selectedSenderAddress = kSelectedAddress!; + + // The amount as inputted by the user + String get _amount => _amountController.text; + + String get _recipient => _recipientController.text; + + AccountInfo get _accountInfo => widget.balances[_selectedSenderAddress]!; + + String? get _recipientErrorText => + _recipient.isNotEmpty ? InputValidators.checkAddress(_recipient) : null; + + String? get _amountErrorText => _amountController.text.isNotEmpty + ? InputValidators.correctValue( + _amountController.text, + _accountInfo.getBalance( + _selectedToken.tokenStandard, + ), + _selectedToken.decimals, + BigInt.zero, + ) + : null; + + bool get _isInputValid => + _recipientErrorText == null && + _amountErrorText == null && + _amountController.text.isNotEmpty && + _recipient.isNotEmpty; + + bool get _isValidTransaction => _hasBalance(_accountInfo) && _isInputValid; + + @override + Widget build(BuildContext context) { + if (_availableAssets.isEmpty) { + _fillAvailableTokens( + initialTokens: kDualCoin, + list: _availableAssets, + tokensWithBalance: _getTokensWithBalance( + widget.balances[_selectedSenderAddress]!, + ), + ); + + } + + return BlocListener( + listener: (_, SendTransactionState state) { + if (state.status == SendTransactionStatus.loading) { + _sendPaymentButtonKey.currentState?.animateForward(); + } else if (state.status == SendTransactionStatus.success) { + _sendConfirmationNotification(block: state.data!); + _sendPaymentButtonKey.currentState?.animateReverse(); + _amountController.clear(); + _recipientController.clear(); + } else if (state.status == SendTransactionStatus.failure) { + _sendPaymentButtonKey.currentState?.animateReverse(); + _sendErrorNotification(state.error!); + } + }, + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: _getDefaultAddressDropdown(), + ), + kHorizontalGap8, + Expanded( + child: _getCoinDropdown(), + ), + ], + ), + kVerticalGap8, + AvailableBalance( + _selectedToken, + _accountInfo, + ), + kVerticalGap8, + ValueListenableBuilder( + valueListenable: _recipientController, + builder: (_, TextEditingValue recipient, __) { + return TextField( + controller: _recipientController, + decoration: InputDecoration( + errorText: _recipientErrorText, + hintText: context.l10n.recipientAddress, + suffixIcon: IconButton( + onPressed: () { + ClipboardUtils.pasteToClipboard(context, + (String value) { + _recipientController.text = value; + }); + }, + icon: const Icon( + Icons.content_paste, + ), + ), + ), + focusNode: _recipientFocusNode, + onSubmitted: (_) { + FocusScope.of(context).requestFocus(_amountFocusNode); + }, + ); + }, + ), + kVerticalGap16, + ValueListenableBuilder( + valueListenable: _amountController, + builder: (_, TextEditingValue amount, __) { + return TextField( + controller: _amountController, + decoration: InputDecoration( + errorText: _amountErrorText, + hintText: context.l10n.amount, + suffixIcon: TextButton( + onPressed: () => _onMaxPressed(_accountInfo), + child: Text(context.l10n.max.toUpperCase()), + ), + ), + focusNode: _amountFocusNode, + inputFormatters: FormatUtils.getAmountTextInputFormatters( + _amountController.text, + ), + onSubmitted: (String value) { + if (_isValidTransaction) { + _onSendPaymentPressed(); + } + }, + ); + }, + ), + kVerticalGap16, + Center( + child: ListenableBuilder( + listenable: Listenable.merge([ + _amountController, + _recipientController, + ]), + builder: (_, __) { + return SendButton( + key: _sendPaymentButtonKey, + text: context.l10n.send, + onPressed: + _isValidTransaction ? _onSendPaymentPressed : null, + ); + }, + ), + ), + ], + ), + ), + ); + } + + Future _onSendPaymentPressed() async { + final String title = context.l10n.send; + + final String symbol = _selectedToken.symbol; + + final String recipient = ZenonAddressUtils.getLabel(_recipient); + + final String description = context.l10n.areYouSureTranfer( + _amount, + recipient, + symbol, + ); + + final bool? txConfirmed = await showDialogWithNoAndYesOptions( + isBarrierDismissible: false, + context: context, + title: title, + description: description, + ); + + if (txConfirmed ?? false) { + _sendPayment(); + } + } + + void _sendPayment() { + context.read().add( + SendTransactionInitiate( + amount: _amount.extractDecimals(_selectedToken.decimals), + fromAddress: _selectedSenderAddress, + toAddress: _recipient, + token: _selectedToken, + ), + ); + } + + Widget _getDefaultAddressDropdown() { + return Tooltip( + message: context.l10n.senderAddressDescription, + child: NewAddressesDropdown( + addresses: kDefaultAddressList.map((String? e) => e!).toList(), + onSelectedCallback: (String value) => setState( + () { + _selectedSenderAddress = value; + _selectedToken = kDualCoin.first; + _availableAssets.clear(); + }, + ), + selectedAddress: _selectedSenderAddress, + ), + ); + } + + Widget _getCoinDropdown() => ZtsDropdown( + availableTokens: _availableAssets, + selectedToken: _selectedToken, + onChangeCallback: (Token value) { + if (_selectedToken != value) { + setState( + () { + _selectedToken = value; + }, + ); + } + }, + ); + + void _onMaxPressed(AccountInfo accountInfo) { + final BigInt maxBalance = accountInfo.getBalance( + _selectedToken.tokenStandard, + ); + + final BigInt currentBalance = _amount.isEmpty + ? BigInt.zero + : _amount.extractDecimals(_selectedToken.decimals); + + if (currentBalance < maxBalance) { + _amountController.text = maxBalance.addDecimals(_selectedToken.decimals); + } + } + + Future _sendErrorNotification(SyriusException error) async { + final String recipient = ZenonAddressUtils.getLabel(_recipient); + + final String symbol = _selectedToken.symbol; + + final String title = context.l10n.couldNotSend(_amount, recipient, symbol); + + await NotificationUtils.sendNotificationError( + error, + title, + ); + } + + Future _sendConfirmationNotification({ + required AccountBlockTemplate block, + }) async { + final String recipient = ZenonAddressUtils.getLabel(_recipient); + + final String sender = ZenonAddressUtils.getLabel(_selectedSenderAddress); + + final String symbol = _selectedToken.symbol; + + final String title = context.l10n.sentDetails( + _amount, + recipient, + sender, + symbol, + ); + + await sl.get().addNotification( + WalletNotification( + title: title, + timestamp: DateTime.now().millisecondsSinceEpoch, + details: context.l10n.hashValue(block.hash.toString()), + type: NotificationType.paymentSent, + ), + ); + } + + bool _hasBalance(AccountInfo accountInfo) => + accountInfo.getBalance( + _selectedToken.tokenStandard, + ) > + BigInt.zero; + + List _getTokensWithBalance(AccountInfo accountInfo) { + final List tokens = []; + final List balanceInfoList = + accountInfo.balanceInfoList!; + + for (final BalanceInfoListItem balanceInfo in balanceInfoList) { + final BigInt balance = balanceInfo.balance!; + final Token token = balanceInfo.token!; + if (balance > BigInt.zero) { + tokens.add(token); + } + } + + return tokens; + } + + @override + void dispose() { + _recipientController.dispose(); + _amountController.dispose(); + super.dispose(); + } + + void _fillAvailableTokens({ + required List initialTokens, + required List list, + required List tokensWithBalance, + }) { + final List emptyList = [...tokensWithBalance]; + // The available tokens should always contain the two coins + if (!emptyList.contains(kZnnCoin)) { + emptyList.insert(0, kZnnCoin); + } + if (!emptyList.contains(kQsrCoin)) { + emptyList.insert(0, kQsrCoin); + } + + list.addAll(sortAssets(emptyList)); + } +} diff --git a/lib/rearchitecture/features/send/widgets/widgets.dart b/lib/rearchitecture/features/send/widgets/widgets.dart new file mode 100644 index 00000000..8e37b708 --- /dev/null +++ b/lib/rearchitecture/features/send/widgets/widgets.dart @@ -0,0 +1,5 @@ +export 'send_button.dart'; +export 'send_empty.dart'; +export 'send_error.dart'; +export 'send_loading.dart'; +export 'send_populated.dart'; diff --git a/lib/rearchitecture/features/tokens/cubit/tokens_cubit.dart b/lib/rearchitecture/features/tokens/cubit/tokens_cubit.dart new file mode 100644 index 00000000..5ea73c61 --- /dev/null +++ b/lib/rearchitecture/features/tokens/cubit/tokens_cubit.dart @@ -0,0 +1,51 @@ +import 'package:equatable/equatable.dart'; +import 'package:hydrated_bloc/hydrated_bloc.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:zenon_syrius_wallet_flutter/blocs/blocs.dart'; +import 'package:zenon_syrius_wallet_flutter/rearchitecture/utils/utils.dart'; +import 'package:znn_sdk_dart/znn_sdk_dart.dart'; + +part 'tokens_state.dart'; + +part 'tokens_cubit.g.dart'; + +/// A cubit who's purpose is to retrieve the available list of tokens +/// +/// It uses the class [TokensState] to send updates to the UI +class TokensCubit extends HydratedCubit with RefreshBlocMixin { + /// Creates a new instance. + TokensCubit({required this.zenon}) : super(const TokensState.initial()) { + listenToWsRestart(fetch); + } + + /// The client used to interact with the Zenon network + final Zenon zenon; + + /// A function that retrieves the list of tokens and emits state updates + Future fetch() async { + try { + final TokenList tokenList = await zenon.embedded.token.getAll(); + final List tokens = tokenList.list ?? []; + emit( + state.copyWith( + status: TokensStatus.success, + data: tokens, + ), + ); + } catch (error, stackTrace) { + emit( + state.copyWith( + error: FailureException(), + ), + ); + addError(error, stackTrace); + } + } + + @override + TokensState? fromJson(Map json) => + TokensState.fromJson(json); + + @override + Map? toJson(TokensState state) => state.toJson(); +} diff --git a/lib/rearchitecture/features/tokens/cubit/tokens_cubit.g.dart b/lib/rearchitecture/features/tokens/cubit/tokens_cubit.g.dart new file mode 100644 index 00000000..8777fdaa --- /dev/null +++ b/lib/rearchitecture/features/tokens/cubit/tokens_cubit.g.dart @@ -0,0 +1,30 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'tokens_cubit.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +TokensState _$TokensStateFromJson(Map json) => TokensState( + status: $enumDecode(_$TokensStatusEnumMap, json['status']), + error: json['error'] == null + ? null + : SyriusException.fromJson(json['error'] as Map), + data: (json['data'] as List?) + ?.map((e) => Token.fromJson(e as Map)) + .toList(), + ); + +Map _$TokensStateToJson(TokensState instance) => + { + 'error': instance.error, + 'status': _$TokensStatusEnumMap[instance.status]!, + 'data': instance.data, + }; + +const _$TokensStatusEnumMap = { + TokensStatus.failure: 'failure', + TokensStatus.initial: 'initial', + TokensStatus.success: 'success', +}; diff --git a/lib/rearchitecture/features/tokens/cubit/tokens_state.dart b/lib/rearchitecture/features/tokens/cubit/tokens_state.dart new file mode 100644 index 00000000..f5a068e3 --- /dev/null +++ b/lib/rearchitecture/features/tokens/cubit/tokens_state.dart @@ -0,0 +1,62 @@ +part of 'tokens_cubit.dart'; + +/// A class that tells what is the status of fetching the available tokens +enum TokensStatus { + /// Fetching failed + failure, + + /// Fetching has not started + initial, + + /// Fetching completed successfully + success, +} + +/// A class that defines the data hold to update the UI when it relies on +/// the available list of tokens +@JsonSerializable() +class TokensState extends Equatable { + /// Creates a new constant instance. + const TokensState({ + required this.status, + this.error, + this.data, + }); + + /// Creates a new instance with the [status] of [TokensState.initial] + const TokensState.initial() + : this( + status: TokensStatus.initial, + ); + + /// {@macro state_from_json} + factory TokensState.fromJson(Map json) => + _$TokensStateFromJson(json); + + /// Exception encountered while fetching the available tokens + final SyriusException? error; + + /// The status of fetching the available tokens + final TokensStatus status; + + /// The available tokens, in case fetching was successful + final List? data; + + /// {@macro state_copy_with} + TokensState copyWith({ + List? data, + TokensStatus? status, + SyriusException? error, + }) => + TokensState( + data: data ?? this.data, + error: error ?? this.error, + status: status ?? this.status, + ); + + @override + List get props => [error, status, data]; + + /// {@macro state_to_json} + Map toJson() => _$TokensStateToJson(this); +} diff --git a/lib/rearchitecture/features/transfer/view/transfer_card.dart b/lib/rearchitecture/features/transfer/view/transfer_card.dart index 6ffa4b2c..54ecc7a0 100644 --- a/lib/rearchitecture/features/transfer/view/transfer_card.dart +++ b/lib/rearchitecture/features/transfer/view/transfer_card.dart @@ -4,7 +4,7 @@ import 'package:zenon_syrius_wallet_flutter/rearchitecture/utils/utils.dart'; import 'package:zenon_syrius_wallet_flutter/utils/app_colors.dart'; import 'package:zenon_syrius_wallet_flutter/widgets/widgets.dart'; -/// A card with two [IconButton] widgets that redirect to the Transfer tab. +/// A card with two [IconButton] widgets that redirect to the Transfer tab class TransferCard extends StatefulWidget { /// Creates a Transfer object. const TransferCard({ @@ -12,12 +12,8 @@ class TransferCard extends StatefulWidget { this.changePage, }); - /// Function that triggers the redirect to the Transfer tab. - final Function( - Tabs, { - bool redirectWithSendContainerLarge, - bool redirectWithReceiveContainerLarge, - })? changePage; + /// Function that triggers the redirect to the Transfer tab + final Function(Tabs)? changePage; @override State createState() => _TransferCardState(); @@ -35,7 +31,6 @@ class _TransferCardState extends State { onPressed: () { widget.changePage!( Tabs.transfer, - redirectWithSendContainerLarge: true, ); }, icon: const Icon( @@ -51,7 +46,6 @@ class _TransferCardState extends State { onPressed: () { widget.changePage!( Tabs.transfer, - redirectWithReceiveContainerLarge: true, ); }, icon: const Icon( diff --git a/lib/rearchitecture/transfer/receive_transaction/cubit/receive_transaction_cubit.dart b/lib/rearchitecture/transfer/receive_transaction/cubit/receive_transaction_cubit.dart new file mode 100644 index 00000000..81fbd9cd --- /dev/null +++ b/lib/rearchitecture/transfer/receive_transaction/cubit/receive_transaction_cubit.dart @@ -0,0 +1,63 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:hydrated_bloc/hydrated_bloc.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:zenon_syrius_wallet_flutter/blocs/auto_receive_tx_worker.dart'; +import 'package:znn_sdk_dart/znn_sdk_dart.dart'; + +part 'receive_transaction_cubit.g.dart'; + +part 'receive_transaction_state.dart'; + +/// A cubit responsible for handling the reception of transactions. +/// +/// This cubit uses an [AutoReceiveTxWorker] to automatically receive a +/// transaction given its [id]. +class ReceiveTransactionCubit extends HydratedCubit { + /// Creates a new [ReceiveTransactionCubit] instance. + /// + /// Requires an [AutoReceiveTxWorker] to process the transactions. + ReceiveTransactionCubit(this.autoReceiveTxWorker) + : super(const ReceiveTransactionState()); + + /// The worker responsible for automatically receiving transactions. + final AutoReceiveTxWorker autoReceiveTxWorker; + + /// Receives a transaction with the given [id]. + /// + /// The [context] is used for any UI interactions or navigation if necessary. + Future receiveTransaction(String id, BuildContext context) async { + try { + emit(state.copyWith(status: ReceiveTransactionStatus.loading)); + + final AccountBlockTemplate? response = + await autoReceiveTxWorker.autoReceiveTransactionHash(Hash.parse(id)); + + emit( + state.copyWith( + status: ReceiveTransactionStatus.success, + data: response, + ), + ); + } catch (e) { + emit( + state.copyWith( + status: ReceiveTransactionStatus.failure, + error: e, + ), + ); + } + } + + + /// Deserializes the [ReceiveTransactionState] from the provided JSON [Map]. + @override + ReceiveTransactionState? fromJson(Map json) => + ReceiveTransactionState.fromJson(json); + + + /// Serializes the current [ReceiveTransactionState] into a JSON [Map]. + @override + Map? toJson(ReceiveTransactionState state) => + state.toJson(); + } diff --git a/lib/rearchitecture/transfer/receive_transaction/cubit/receive_transaction_cubit.g.dart b/lib/rearchitecture/transfer/receive_transaction/cubit/receive_transaction_cubit.g.dart new file mode 100644 index 00000000..6b839e0a --- /dev/null +++ b/lib/rearchitecture/transfer/receive_transaction/cubit/receive_transaction_cubit.g.dart @@ -0,0 +1,34 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'receive_transaction_cubit.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +ReceiveTransactionState _$ReceiveTransactionStateFromJson( + Map json) => + ReceiveTransactionState( + status: $enumDecodeNullable( + _$ReceiveTransactionStatusEnumMap, json['status']) ?? + ReceiveTransactionStatus.initial, + data: json['data'] == null + ? null + : AccountBlockTemplate.fromJson(json['data'] as Map), + error: json['error'], + ); + +Map _$ReceiveTransactionStateToJson( + ReceiveTransactionState instance) => + { + 'status': _$ReceiveTransactionStatusEnumMap[instance.status]!, + 'data': instance.data?.toJson(), + 'error': instance.error, + }; + +const _$ReceiveTransactionStatusEnumMap = { + ReceiveTransactionStatus.initial: 'initial', + ReceiveTransactionStatus.loading: 'loading', + ReceiveTransactionStatus.failure: 'failure', + ReceiveTransactionStatus.success: 'success', +}; diff --git a/lib/rearchitecture/transfer/receive_transaction/cubit/receive_transaction_state.dart b/lib/rearchitecture/transfer/receive_transaction/cubit/receive_transaction_state.dart new file mode 100644 index 00000000..1f467d55 --- /dev/null +++ b/lib/rearchitecture/transfer/receive_transaction/cubit/receive_transaction_state.dart @@ -0,0 +1,66 @@ +part of 'receive_transaction_cubit.dart'; + +/// Represents the possible statuses for receiving a transaction. +enum ReceiveTransactionStatus { + /// The initial state before any action has been taken. + initial, + + /// Indicates that the transaction is currently being processed. + loading, + + /// Indicates that an error occurred during the transaction process. + failure, + + /// Indicates that the transaction was successfully received. + success, +} + +/// Holds the state for receiving a transaction, including status, data, +/// and error information. +@JsonSerializable(explicitToJson: true) +class ReceiveTransactionState extends Equatable { + /// Creates a new instance of [ReceiveTransactionState]. + /// + /// The [status] defaults to [ReceiveTransactionStatus.initial]. + const ReceiveTransactionState({ + this.status = ReceiveTransactionStatus.initial, + this.data, + this.error, + }); + + /// Creates a new instance from a JSON map. + factory ReceiveTransactionState.fromJson(Map json) => + _$ReceiveTransactionStateFromJson(json); + + /// The current status of the receive transaction operation. + final ReceiveTransactionStatus status; + + /// The [AccountBlockTemplate] representing the transaction data. + /// + /// It is populated when the [status] is [ReceiveTransactionStatus.success]. + final AccountBlockTemplate? data; + + /// An object representing any error occurring during the transaction process. + /// + /// Populated when [status] is [ReceiveTransactionStatus.failure]. + final Object? error; + + /// {@macro state_copy_with} + ReceiveTransactionState copyWith({ + ReceiveTransactionStatus? status, + AccountBlockTemplate? data, + Object? error, + }) { + return ReceiveTransactionState( + status: status ?? this.status, + data: data ?? this.data, + error: error ?? this.error, + ); + } + + /// {@macro state_to_json} + Map toJson() => _$ReceiveTransactionStateToJson(this); + + @override + List get props => [status, data, error]; +} diff --git a/lib/rearchitecture/utils/bloc_observers/bloc_observers.dart b/lib/rearchitecture/utils/bloc_observers/bloc_observers.dart new file mode 100644 index 00000000..8e9626c0 --- /dev/null +++ b/lib/rearchitecture/utils/bloc_observers/bloc_observers.dart @@ -0,0 +1 @@ +export 'custom_bloc_observer.dart'; diff --git a/lib/rearchitecture/utils/bloc_observers/custom_bloc_observer.dart b/lib/rearchitecture/utils/bloc_observers/custom_bloc_observer.dart new file mode 100644 index 00000000..4d770b50 --- /dev/null +++ b/lib/rearchitecture/utils/bloc_observers/custom_bloc_observer.dart @@ -0,0 +1,11 @@ +import 'package:bloc/bloc.dart'; +import 'package:logging/logging.dart'; + +/// A custom bloc observer that helps log the errors +class CustomBlocObserver extends BlocObserver { + @override + void onError(BlocBase bloc, Object error, StackTrace stackTrace) { + Logger('Bloc: ${bloc.runtimeType}').warning('onError: ', error, stackTrace); + super.onError(bloc, error, stackTrace); + } +} diff --git a/lib/rearchitecture/utils/blocs/blocs.dart b/lib/rearchitecture/utils/blocs/blocs.dart new file mode 100644 index 00000000..12334886 --- /dev/null +++ b/lib/rearchitecture/utils/blocs/blocs.dart @@ -0,0 +1 @@ +export 'infinite_list/infinite_list_bloc.dart'; diff --git a/lib/rearchitecture/utils/blocs/infinite_list/infinite_list_bloc.dart b/lib/rearchitecture/utils/blocs/infinite_list/infinite_list_bloc.dart new file mode 100644 index 00000000..27fb64db --- /dev/null +++ b/lib/rearchitecture/utils/blocs/infinite_list/infinite_list_bloc.dart @@ -0,0 +1,173 @@ +import 'dart:async'; + +import 'package:equatable/equatable.dart'; +import 'package:hydrated_bloc/hydrated_bloc.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:zenon_syrius_wallet_flutter/blocs/blocs.dart'; +import 'package:zenon_syrius_wallet_flutter/rearchitecture/utils/constants/api.dart'; +import 'package:zenon_syrius_wallet_flutter/rearchitecture/utils/exceptions/exceptions.dart'; +import 'package:zenon_syrius_wallet_flutter/rearchitecture/utils/functions/functions.dart'; +import 'package:zenon_syrius_wallet_flutter/utils/global.dart'; +import 'package:znn_sdk_dart/znn_sdk_dart.dart'; + +part 'infinite_list_bloc.g.dart'; + +part 'infinite_list_event.dart'; + +part 'infinite_list_state.dart'; + +/// A bloc that manages the state of the latest transactions for a specific +/// address. +/// +/// Each time the app starts, an [InfiniteListRequested] event should be +/// sent. This checks if there are any new data, if so, the current list is +/// updated. In this scenario we replaced the current data with the new data. +/// +/// Too fetch additional data, the [InfiniteListMoreRequested] event can +/// be used. It's sent when the user scrolled to the bottom of the list. In +/// this scenario, the new data is appended to the old data. +/// +/// Eventually, if we want to refresh the data completely, we can used the +/// [InfiniteListRefreshRequested] event. This is sent when a new default +/// address was selected and we want to load the latest transactions for that +/// address. In this case, an initial state is emitted, with an empty list, +/// followed by an event of [InfiniteListRequested] that restarts the +/// fetching process for the new address +abstract class InfiniteListBloc + extends HydratedBloc> + with RefreshBlocMixin { + /// Creates a new instance. + InfiniteListBloc({ + required this.fromJsonT, + required this.toJsonT, + required this.zenon, + required this.pageSize, + }) : super( + InfiniteListState.initial(), + ) { + listenToWsRestart( + () { + add( + InfiniteListRefreshRequested( + address: Address.parse(kSelectedAddress!), + ), + ); + }, + ); + on( + _onInfiniteListRequested, + transformer: throttleDroppable(kThrottleDuration), + ); + on( + _onInfiniteListMoreRequested, + ); + on( + _onInfiniteListRefreshRequested, + ); + } + + /// The [Zenon] SDK instance used for ledger interactions. + final Zenon zenon; + + /// THe number of results to be returned per page. + final int pageSize; + /// Method that helps deserializing the generic [T] type + final T Function(Object?) fromJsonT; + /// Method that helps serializing the generic [T] type + final Object? Function(T) toJsonT; + + /// Retrieves the data in a pagination manner, with the first page being 0 + Future> paginationFetch({ + required Address address, + required int pageIndex, + required int pageSize, + }); + + Future _onInfiniteListRequested( + InfiniteListRequested event, + Emitter> emit, + ) async { + try { + final List newData = await paginationFetch( + address: event.address, + pageIndex: 0, + pageSize: pageSize, + ); + + final bool hasReachedMax = newData.length < pageSize; + + emit( + state.copyWith( + data: newData, + hasReachedMax: hasReachedMax, + status: InfiniteListStatus.success, + ), + ); + } catch (error, stackTrace) { + addError(error, stackTrace); + emit( + state.copyWith( + status: InfiniteListStatus.failure, + error: FailureException(), + ), + ); + } + } + + Future _onInfiniteListMoreRequested( + InfiniteListMoreRequested event, + Emitter> emit, + ) async { + if (state.hasReachedMax) return; + final List currentData = state.data ?? []; + final int previousNumOfItems = currentData.length; + final int pageIndex = previousNumOfItems ~/ pageSize; + try { + final List data = await paginationFetch( + address: event.address, + pageIndex: pageIndex, + pageSize: pageSize, + ); + + final bool hasReachedMax = data.length < pageSize; + + emit( + state.copyWith( + data: [ + ...currentData, + ...data, + ], + hasReachedMax: hasReachedMax, + status: InfiniteListStatus.success, + ), + ); + } catch (error, stackTrace) { + addError(error, stackTrace); + emit( + state.copyWith( + status: InfiniteListStatus.failure, + error: FailureException(), + ), + ); + } + } + + FutureOr _onInfiniteListRefreshRequested( + InfiniteListRefreshRequested event, + Emitter> emit, + ) { + emit(InfiniteListState.initial()); + add(InfiniteListRequested(address: event.address)); + } + + /// Deserializes the JSON map into a [InfiniteListState]. + @override + InfiniteListState? fromJson(Map json) => + InfiniteListState.fromJson(json, fromJsonT); + + /// Serializes the current state into a JSON map. + @override + Map? toJson(InfiniteListState state) => state.toJson( + toJsonT, + ); +} diff --git a/lib/rearchitecture/utils/blocs/infinite_list/infinite_list_bloc.g.dart b/lib/rearchitecture/utils/blocs/infinite_list/infinite_list_bloc.g.dart new file mode 100644 index 00000000..765050e5 --- /dev/null +++ b/lib/rearchitecture/utils/blocs/infinite_list/infinite_list_bloc.g.dart @@ -0,0 +1,37 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'infinite_list_bloc.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +InfiniteListState _$InfiniteListStateFromJson( + Map json, + T Function(Object? json) fromJsonT, +) => + InfiniteListState( + status: $enumDecode(_$InfiniteListStatusEnumMap, json['status']), + data: (json['data'] as List?)?.map(fromJsonT).toList(), + error: json['error'] == null + ? null + : SyriusException.fromJson(json['error'] as Map), + hasReachedMax: json['hasReachedMax'] as bool? ?? false, + ); + +Map _$InfiniteListStateToJson( + InfiniteListState instance, + Object? Function(T value) toJsonT, +) => + { + 'status': _$InfiniteListStatusEnumMap[instance.status]!, + 'data': instance.data?.map(toJsonT).toList(), + 'error': instance.error?.toJson(), + 'hasReachedMax': instance.hasReachedMax, + }; + +const _$InfiniteListStatusEnumMap = { + InfiniteListStatus.initial: 'initial', + InfiniteListStatus.failure: 'failure', + InfiniteListStatus.success: 'success', +}; diff --git a/lib/rearchitecture/utils/blocs/infinite_list/infinite_list_event.dart b/lib/rearchitecture/utils/blocs/infinite_list/infinite_list_event.dart new file mode 100644 index 00000000..4923cab1 --- /dev/null +++ b/lib/rearchitecture/utils/blocs/infinite_list/infinite_list_event.dart @@ -0,0 +1,40 @@ +part of 'infinite_list_bloc.dart'; + +/// The generic class for the events used in [InfiniteListBloc] +sealed class InfiniteListEvent extends Equatable { + /// Creates a new instance. + const InfiniteListEvent({required this.address}); + + /// The [address] for which data will be fetched. + final Address address; +} + +/// Event to be used when we want to fetch the latest transactions of an +/// [address] +class InfiniteListRequested extends InfiniteListEvent { + /// Creates a new instance. + const InfiniteListRequested({required super.address}); + + @override + List get props => [address]; +} + +/// Event to be used when we want to fetch more latest transactions of an +/// [address] +class InfiniteListMoreRequested extends InfiniteListEvent { + /// Creates a new instance. + const InfiniteListMoreRequested({required super.address}); + + @override + List get props => [address]; +} + +/// Event to be used when we want to refresh the list of the latest +/// transactions +class InfiniteListRefreshRequested extends InfiniteListEvent { + /// Creates a new instance. + const InfiniteListRefreshRequested({required super.address}); + + @override + List get props => [address]; +} diff --git a/lib/rearchitecture/utils/blocs/infinite_list/infinite_list_state.dart b/lib/rearchitecture/utils/blocs/infinite_list/infinite_list_state.dart new file mode 100644 index 00000000..1df1da15 --- /dev/null +++ b/lib/rearchitecture/utils/blocs/infinite_list/infinite_list_state.dart @@ -0,0 +1,87 @@ +part of 'infinite_list_bloc.dart'; + +/// Represents the possible statuses for the latest transactions operation. +enum InfiniteListStatus { + /// The initial state before any action has been taken. + initial, + + /// Indicates that an error occurred during the data fetching process. + failure, + + /// Indicates that data has been successfully fetched. + success, +} + +/// Holds the state for the latest transactions, including status, data, +/// and error information. +@JsonSerializable(explicitToJson: true, genericArgumentFactories: true) +class InfiniteListState extends Equatable { + /// Creates a new instance of LatestTransactionsState. + /// + /// The [status] defaults to [InfiniteListStatus.initial]. + const InfiniteListState({ + required this.status, + this.data, + this.error, + this.hasReachedMax = false, + }); + + /// Creates a new instance, with the [status] of [InfiniteListStatus.initial] + const InfiniteListState.initial() + : this( + status: InfiniteListStatus.initial, + ); + + /// Creates a new instance from a JSON object. + factory InfiniteListState.fromJson( + Map json, + T Function(Object?) fromJsonT, + ) => + _$InfiniteListStateFromJson(json, fromJsonT); + + /// The current status of the latest transactions operation. + final InfiniteListStatus status; + + /// The list of [AccountBlock] instances representing the latest transactions. + /// + /// It is populated when the [status] is [InfiniteListStatus.success]. + final List? data; + + /// An object representing any error that occurred during data fetching. + /// + /// This is typically an exception or error message. It is populated when + /// the [status] is [InfiniteListStatus.failure]. + final SyriusException? error; + + /// Whether there are more account blocks to be fetched. + final bool hasReachedMax; + + ///{@macro state_copy_with} + InfiniteListState copyWith({ + InfiniteListStatus? status, + List? data, + SyriusException? error, + bool? hasReachedMax, + }) { + return InfiniteListState( + status: status ?? this.status, + data: data ?? this.data, + error: error ?? this.error, + hasReachedMax: hasReachedMax ?? this.hasReachedMax, + ); + } + + /// {@template state_to_json} + /// Converts this state into a JSON map for persistence. + /// + /// This is used during serialization to save the state across app restarts. + /// {@endtemplate} + Map toJson(Object? Function(T) toJsonT) => + _$InfiniteListStateToJson( + this, + toJsonT, + ); + + @override + List get props => [status, data, error, hasReachedMax]; +} diff --git a/lib/rearchitecture/utils/constants/api.dart b/lib/rearchitecture/utils/constants/api.dart new file mode 100644 index 00000000..10e22acd --- /dev/null +++ b/lib/rearchitecture/utils/constants/api.dart @@ -0,0 +1,5 @@ +/// Page size to be used when making paginated API calls +const int kPageSize = 10; + +/// The interval at which to emit events +const Duration kThrottleDuration = Duration(milliseconds: 100); diff --git a/lib/rearchitecture/utils/constants/app_sizes.dart b/lib/rearchitecture/utils/constants/app_sizes.dart index 7772e473..0f27936e 100644 --- a/lib/rearchitecture/utils/constants/app_sizes.dart +++ b/lib/rearchitecture/utils/constants/app_sizes.dart @@ -2,9 +2,13 @@ import 'package:flutter/widgets.dart'; // ignore_for_file: public_member_api_docs /// Constant vertical gaps +const SizedBox kVerticalGap8 = SizedBox(height: 8); const SizedBox kVerticalGap16 = SizedBox(height: 16); /// Constants horizontal gaps const SizedBox kHorizontalGap4 = SizedBox(width: 4); const SizedBox kHorizontalGap8 = SizedBox(width: 8); const SizedBox kHorizontalGap16 = SizedBox(width: 16); + +const double kInfiniteTableHorizontalPadding = 16; +const double kDropdownMenuHeight = 400; diff --git a/lib/rearchitecture/utils/constants/constants.dart b/lib/rearchitecture/utils/constants/constants.dart index cef4e8b6..efae056f 100644 --- a/lib/rearchitecture/utils/constants/constants.dart +++ b/lib/rearchitecture/utils/constants/constants.dart @@ -1,3 +1,4 @@ +export 'api.dart'; export 'app_sizes.dart'; export 'durations.dart'; export 'strings.dart'; diff --git a/lib/rearchitecture/utils/cubits/hide_widget/hide_widget_cubit.dart b/lib/rearchitecture/utils/cubits/hide_widget/hide_widget_cubit.dart index 85af2495..436ab135 100644 --- a/lib/rearchitecture/utils/cubits/hide_widget/hide_widget_cubit.dart +++ b/lib/rearchitecture/utils/cubits/hide_widget/hide_widget_cubit.dart @@ -54,7 +54,7 @@ class HideWidgetCubit extends HydratedCubit { emit( state.copyWith( status: HideWidgetStatus.failure, - exception: CubitFailureException(), + exception: FailureException(), ), ); addError(error, stackTrace); diff --git a/lib/rearchitecture/utils/cubits/hide_widget/hide_widget_state.dart b/lib/rearchitecture/utils/cubits/hide_widget/hide_widget_state.dart index 7979045c..62ca4ef9 100644 --- a/lib/rearchitecture/utils/cubits/hide_widget/hide_widget_state.dart +++ b/lib/rearchitecture/utils/cubits/hide_widget/hide_widget_state.dart @@ -12,8 +12,9 @@ enum HideWidgetStatus { success, } +/// A class designed to tell us the current state of the two operations: +/// hiding or un-hiding a widget @JsonSerializable() -/// A class that holds the state emitted by the [HideWidgetCubit] class HideWidgetState extends Equatable { /// Creates a new instance. const HideWidgetState({ @@ -32,12 +33,11 @@ class HideWidgetState extends Equatable { factory HideWidgetState.fromJson(Map json) => _$HideWidgetStateFromJson(json); - /// The exception that can be contained by a state emitted when the cubit - /// encounters an error + /// An exception that occurred during the hiding or un-hiding operation. final SyriusException? exception; - /// A field that tells if a widget should be hidden or not + /// Specifies if the current status is hidden or not. final bool? isHidden; - /// The current status of hiding or un-hiding a widget + /// The status of hiding or un-hiding the widget. final HideWidgetStatus status; @override diff --git a/lib/rearchitecture/utils/cubits/timer_cubit.dart b/lib/rearchitecture/utils/cubits/timer_cubit.dart index 3c706e8d..194572de 100644 --- a/lib/rearchitecture/utils/cubits/timer_cubit.dart +++ b/lib/rearchitecture/utils/cubits/timer_cubit.dart @@ -67,6 +67,10 @@ abstract class TimerCubit> extends HydratedCubit { /// the outcome. /// If the WebSocket client is closed, it throws a [noConnectionException]. Future fetchDataPeriodically() async { + if (isClosed) { + _autoRefresher?.cancel(); + return; + } try { if (state.status != TimerStatus.success) { emit(state.copyWith(status: TimerStatus.loading) as S); @@ -83,7 +87,7 @@ abstract class TimerCubit> extends HydratedCubit { emit( state.copyWith( status: TimerStatus.failure, - error: CubitFailureException(), + error: FailureException(), ) as S, ); // Reports only the unexpected errors diff --git a/lib/rearchitecture/utils/exceptions/exceptions.dart b/lib/rearchitecture/utils/exceptions/exceptions.dart index f99c26b0..1c3af7f3 100644 --- a/lib/rearchitecture/utils/exceptions/exceptions.dart +++ b/lib/rearchitecture/utils/exceptions/exceptions.dart @@ -1,2 +1,2 @@ -export 'cubit_failure_exception.dart'; +export 'failure_exception.dart'; export 'syrius_exception.dart'; diff --git a/lib/rearchitecture/utils/exceptions/failure_exception.dart b/lib/rearchitecture/utils/exceptions/failure_exception.dart new file mode 100644 index 00000000..cfd83564 --- /dev/null +++ b/lib/rearchitecture/utils/exceptions/failure_exception.dart @@ -0,0 +1,35 @@ +import 'package:flutter/cupertino.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:zenon_syrius_wallet_flutter/rearchitecture/utils/utils.dart'; + +part 'failure_exception.g.dart'; + +/// A class to be used as a generic exception when something unexpected goes +/// wrong. +@immutable +@JsonSerializable() +class FailureException extends SyriusException { + /// Creates a [FailureException] instance. + FailureException({ + String message = 'Something went wrong', + }) : super(message); + + /// Creates a [FailureException] instance from a JSON map. + factory FailureException.fromJson(Map json) => + _$FailureExceptionFromJson(json); + + /// Converts this [FailureException] instance to a JSON map. + @override + Map toJson() => _$FailureExceptionToJson(this) + ..['runtimeType'] = 'FailureException'; + + @override + bool operator ==(Object other) { + return other is FailureException && + other.runtimeType == runtimeType && + other.message == message; + } + + @override + int get hashCode => message.hashCode; +} diff --git a/lib/rearchitecture/utils/exceptions/failure_exception.g.dart b/lib/rearchitecture/utils/exceptions/failure_exception.g.dart new file mode 100644 index 00000000..2c69b9c6 --- /dev/null +++ b/lib/rearchitecture/utils/exceptions/failure_exception.g.dart @@ -0,0 +1,17 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'failure_exception.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +FailureException _$FailureExceptionFromJson(Map json) => + FailureException( + message: json['message'] as String? ?? 'Something went wrong', + ); + +Map _$FailureExceptionToJson(FailureException instance) => + { + 'message': instance.message, + }; diff --git a/lib/rearchitecture/utils/exceptions/syrius_exception.dart b/lib/rearchitecture/utils/exceptions/syrius_exception.dart index 1d789b5d..9c2b3333 100644 --- a/lib/rearchitecture/utils/exceptions/syrius_exception.dart +++ b/lib/rearchitecture/utils/exceptions/syrius_exception.dart @@ -25,8 +25,8 @@ class SyriusException implements Exception { return NoBalanceException.fromJson(json); case 'NoBlocksAvailableException': return NoBlocksAvailableException.fromJson(json); - case 'CubitFailureException': - return CubitFailureException.fromJson(json); + case 'FailureException': + return FailureException.fromJson(json); case 'NoDelegationStatsException': return NoDelegationStatsException.fromJson(json); case 'NotEnoughMomentumsException': diff --git a/lib/rearchitecture/utils/extensions/extensions.dart b/lib/rearchitecture/utils/extensions/extensions.dart index 97e02ab1..b9d38cf7 100644 --- a/lib/rearchitecture/utils/extensions/extensions.dart +++ b/lib/rearchitecture/utils/extensions/extensions.dart @@ -1,2 +1,3 @@ export 'buildcontext_extension.dart'; export 'sync_state_extension.dart'; +export 'token_extension.dart'; diff --git a/lib/rearchitecture/utils/extensions/token_extension.dart b/lib/rearchitecture/utils/extensions/token_extension.dart new file mode 100644 index 00000000..783985dd --- /dev/null +++ b/lib/rearchitecture/utils/extensions/token_extension.dart @@ -0,0 +1,7 @@ +import 'package:znn_sdk_dart/znn_sdk_dart.dart'; + +/// An extension that adds functionality to the [Token] class +extension TokenExtension on Token { + /// Whether a [Token] is a coin - ZNN, QSR - or an user-made token + bool get isCoin => [znnZts, qsrZts].contains(tokenStandard); +} diff --git a/lib/rearchitecture/utils/functions/api.dart b/lib/rearchitecture/utils/functions/api.dart new file mode 100644 index 00000000..8b6f82e9 --- /dev/null +++ b/lib/rearchitecture/utils/functions/api.dart @@ -0,0 +1,11 @@ +import 'package:bloc_concurrency/bloc_concurrency.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:stream_transform/stream_transform.dart'; + +/// Helps with filtering events, making sure that only one is emitted in the +/// interval defined by [duration] parameter +EventTransformer throttleDroppable(Duration duration) { + return (Stream events, EventMapper mapper) { + return droppable().call(events.throttle(duration), mapper); + }; +} diff --git a/lib/rearchitecture/utils/functions/functions.dart b/lib/rearchitecture/utils/functions/functions.dart new file mode 100644 index 00000000..69fa71d3 --- /dev/null +++ b/lib/rearchitecture/utils/functions/functions.dart @@ -0,0 +1,2 @@ +export 'api.dart'; +export 'tokens.dart'; diff --git a/lib/rearchitecture/utils/functions/tokens.dart b/lib/rearchitecture/utils/functions/tokens.dart new file mode 100644 index 00000000..d506d9f5 --- /dev/null +++ b/lib/rearchitecture/utils/functions/tokens.dart @@ -0,0 +1,22 @@ +import 'package:zenon_syrius_wallet_flutter/rearchitecture/utils/utils.dart'; +import 'package:znn_sdk_dart/znn_sdk_dart.dart'; + +/// Takes in a list of assets - tokens and coins - and returns a list with the +/// coins at the beginning of the list +/// +/// If no coins are found in the list, then the order is preserved +List sortAssets(List assets) { + assets.sort((Token a, Token b) { + if (a.isCoin && !b.isCoin) return -1; // Coins come first + if (!a.isCoin && b.isCoin) return 1; // Tokens come second + if (a.isCoin && b.isCoin) { + if (a.tokenStandard == znnZts) { + return -1; // Zenon comes first + } else { + return 1; // Quasar comes second + } + } + return 0; // Preserve original order within the same type + }); + return assets; +} diff --git a/lib/rearchitecture/utils/models/card/card_type.dart b/lib/rearchitecture/utils/models/card/card_type.dart index b31d182c..54bbad46 100644 --- a/lib/rearchitecture/utils/models/card/card_type.dart +++ b/lib/rearchitecture/utils/models/card/card_type.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:zenon_syrius_wallet_flutter/rearchitecture/utils/utils.dart'; import 'package:zenon_syrius_wallet_flutter/utils/zts_utils.dart'; +import 'package:zenon_syrius_wallet_flutter/widgets/tab_children_widgets/tab_children_widgets.dart'; /// A class that helps distinguish between the card widgets enum CardType { @@ -13,12 +14,28 @@ enum CardType { /// A type for a card displaying information related to ZNN and QSR dualCoinStats, + /// A type for a card displaying the latest transactions for an address + latestTransactions, + + /// A type for a card displaying the latest transactions for an address, + /// adjusted to be displayed in the [DashboardTabChild] tab. + latestTransactionsDashboard, + + /// A type for a card displaying the pending transactions. + pendingTransactions, + /// A type for a card displaying information related to pillars pillars, /// A type for a card displaying information in realtime realtimeStatistics, + /// A type for a card that has input fields for receiving a transaction + receive, + + /// A type for a card that handles the process of sending a transaction + send, + /// A type for a card displaying information related to sentinels sentinels, @@ -57,6 +74,18 @@ enum CardType { kZnnCoin.symbol, ), ), + CardType.latestTransactions => CardData( + description: context.l10n.latestTransactionsDescription, + title: context.l10n.latestTransactionsTitle, + ), + CardType.latestTransactionsDashboard => CardData( + description: context.l10n.latestTransactionsDescription, + title: context.l10n.latestTransactionsTitle, + ), + CardType.pendingTransactions => CardData( + description: context.l10n.pendingTransactionsDescription, + title: context.l10n.pendingTransactionsTitle, + ), CardType.pillars => CardData( title: context.l10n.pillars, description: context.l10n.pillarsDescription, @@ -66,6 +95,18 @@ enum CardType { description: context.l10n .realtimeStatsDescription(kQsrCoin.symbol, kZnnCoin.symbol), ), + CardType.receive => CardData( + description: '${context.l10n.manageReceivingFunds}\n' + '${context.l10n.addressSearchDescription}\n' + '${context.l10n.ztsSearchDescription}', + title: context.l10n.receive, + ), + CardType.send => CardData( + title: context.l10n.send, + description: '${context.l10n.manageSendingFunds}\n' + '${context.l10n.addressSearchDescription}\n' + '${context.l10n.ztsSearchDescription}', + ), CardType.sentinels => CardData( title: context.l10n.sentinels, description: context.l10n.sentinelsDescription, diff --git a/lib/rearchitecture/utils/utils.dart b/lib/rearchitecture/utils/utils.dart index 47022aff..d3beac93 100644 --- a/lib/rearchitecture/utils/utils.dart +++ b/lib/rearchitecture/utils/utils.dart @@ -1,7 +1,10 @@ +export 'bloc_observers/bloc_observers.dart'; +export 'blocs/blocs.dart'; export 'constants/constants.dart'; export 'cubits/cubits.dart'; export 'exceptions/exceptions.dart'; export 'extensions/extensions.dart'; +export 'functions/functions.dart'; export 'models/models.dart'; export 'url_protocol/api.dart'; export 'widgets/widgets.dart'; diff --git a/lib/rearchitecture/utils/widgets/dropdowns/dropdowns.dart b/lib/rearchitecture/utils/widgets/dropdowns/dropdowns.dart new file mode 100644 index 00000000..86407b93 --- /dev/null +++ b/lib/rearchitecture/utils/widgets/dropdowns/dropdowns.dart @@ -0,0 +1,2 @@ +export 'new_addresses_dropdown.dart'; +export 'zts_dropdown.dart'; diff --git a/lib/rearchitecture/utils/widgets/dropdowns/new_addresses_dropdown.dart b/lib/rearchitecture/utils/widgets/dropdowns/new_addresses_dropdown.dart new file mode 100644 index 00000000..3940d94c --- /dev/null +++ b/lib/rearchitecture/utils/widgets/dropdowns/new_addresses_dropdown.dart @@ -0,0 +1,88 @@ +import 'package:flutter/material.dart'; +import 'package:zenon_syrius_wallet_flutter/rearchitecture/utils/utils.dart'; +import 'package:zenon_syrius_wallet_flutter/utils/app_colors.dart'; +import 'package:zenon_syrius_wallet_flutter/utils/global.dart'; + +/// A dropdown created from the list of addresses generated by the user +class NewAddressesDropdown extends StatefulWidget { + /// Creates a new instance. + const NewAddressesDropdown({ + required List addresses, + required void Function(String) onSelectedCallback, + required String selectedAddress, + super.key, + }) : + _addresses = addresses, + _onSelectedCallback = onSelectedCallback, + _selectedAddress = selectedAddress; + final List _addresses; + final void Function(String) _onSelectedCallback; + final String _selectedAddress; + + @override + State createState() => _NewAddressesDropdownState(); +} + +class _NewAddressesDropdownState extends State { + final TextEditingController _searchController = TextEditingController(); + + @override + Widget build(BuildContext context) { + const Color color = AppColors.znnColor; + + final List> entries = widget._addresses + .map( + (String address) => DropdownMenuEntry( + label: kAddressLabelMap[address]!, + style: MenuItemButton.styleFrom( + foregroundColor: address == widget._selectedAddress ? color : null, + ), + value: address, + ), + ) + .toList(); + + return DropdownMenu( + controller: _searchController, + expandedInsets: EdgeInsets.zero, + initialSelection: widget._selectedAddress, + inputDecorationTheme: const InputDecorationTheme( + filled: true, + ), + leadingIcon: const Icon( + Icons.search, + color: color, + ), + dropdownMenuEntries: entries, + menuHeight: kDropdownMenuHeight, + onSelected: (String? address) { + if (address != null) { + widget._onSelectedCallback(address); + } + }, + searchCallback: _searchCallback, + textStyle: const TextStyle( + color: color, + ), + trailingIcon: const Icon( + Icons.keyboard_arrow_down_rounded, + color: color, + ), + ); + } + + int? _searchCallback(List> entries, String query) { + final String searchText = query.toLowerCase(); + if (searchText.isEmpty) { + return null; + } + final int index = entries.indexWhere( + (DropdownMenuEntry entry) => _matchTest(entry, searchText), + ); + return index != -1 ? index : null; + } + + bool _matchTest(DropdownMenuEntry entry, String searchText) => + entry.label.toLowerCase().contains(searchText) || + entry.value.toLowerCase().contains(searchText); +} diff --git a/lib/rearchitecture/utils/widgets/dropdowns/zts_dropdown.dart b/lib/rearchitecture/utils/widgets/dropdowns/zts_dropdown.dart new file mode 100644 index 00000000..082a0d20 --- /dev/null +++ b/lib/rearchitecture/utils/widgets/dropdowns/zts_dropdown.dart @@ -0,0 +1,98 @@ +import 'package:flutter/material.dart'; +import 'package:zenon_syrius_wallet_flutter/rearchitecture/utils/utils.dart'; +import 'package:zenon_syrius_wallet_flutter/utils/utils.dart'; +import 'package:znn_sdk_dart/znn_sdk_dart.dart'; + +/// A dropdown for all the network assets - tokens and coins +class ZtsDropdown extends StatefulWidget { + /// Creates a new instance. + const ZtsDropdown({ + required List availableTokens, + required void Function(Token) onChangeCallback, + required Token selectedToken, + super.key, + }) : _onChangeCallback = onChangeCallback, + _availableTokens = availableTokens, + _selectedToken = selectedToken; + + final void Function(Token) _onChangeCallback; + final Token _selectedToken; + final List _availableTokens; + + @override + State createState() => _ZtsDropdownState(); +} + +class _ZtsDropdownState extends State { + final TextEditingController _searchController = TextEditingController(); + + @override + Widget build(BuildContext context) { + final List> entries = widget._availableTokens.map( + (Token token) { + final String labelSuffix = token.isCoin + ? '' + : ' (${token.tokenStandard.toString().short})'; + + final String label = '${token.symbol} $labelSuffix'; + + return DropdownMenuEntry( + label: label, + style: MenuItemButton.styleFrom( + foregroundColor: ColorUtils.getTokenColor(token.tokenStandard), + ), + value: token, + ); + }, + ).toList(); + + final Color color = ColorUtils.getTokenColor( + widget._selectedToken.tokenStandard, + ); + + return DropdownMenu( + controller: _searchController, + expandedInsets: EdgeInsets.zero, + initialSelection: widget._selectedToken, + inputDecorationTheme: const InputDecorationTheme( + filled: true, + ), + leadingIcon: Icon( + Icons.search, + color: color, + ), + dropdownMenuEntries: entries, + menuHeight: kDropdownMenuHeight, + onSelected: (Token? token) { + if (token != null) { + widget._onChangeCallback(token); + } + }, + searchCallback: _searchCallback, + textStyle: TextStyle( + color: color, + ), + trailingIcon: Icon( + Icons.keyboard_arrow_down_rounded, + color: color, + ), + ); + } + + int? _searchCallback(List> entries, String query) { + final String searchText = query.toLowerCase(); + if (searchText.isEmpty) { + return null; + } + final int index = entries.indexWhere( + (DropdownMenuEntry entry) => _matchTest(entry, searchText), + ); + + return index != -1 ? index : null; + } + + bool _matchTest(DropdownMenuEntry entry, String searchText) => + entry.label.toLowerCase().contains(searchText) || + entry.value.symbol.toLowerCase().contains(searchText) || + entry.value.tokenStandard.toString().toLowerCase().contains(searchText); +} diff --git a/lib/rearchitecture/utils/widgets/infinite_scroll_table/cells/address_cell.dart b/lib/rearchitecture/utils/widgets/infinite_scroll_table/cells/address_cell.dart new file mode 100644 index 00000000..afb70e94 --- /dev/null +++ b/lib/rearchitecture/utils/widgets/infinite_scroll_table/cells/address_cell.dart @@ -0,0 +1,16 @@ +import 'package:flutter/material.dart'; +import 'package:zenon_syrius_wallet_flutter/rearchitecture/utils/utils.dart'; +import 'package:znn_sdk_dart/znn_sdk_dart.dart'; + +class AddressCell extends StatelessWidget { + const AddressCell({required this.address, super.key}); + + final Address address; + + @override + Widget build(BuildContext context) { + return InfiniteScrollTableCell.textFromAddress( + address: address, + ); + } +} diff --git a/lib/rearchitecture/utils/widgets/infinite_scroll_table/cells/amount_cell.dart b/lib/rearchitecture/utils/widgets/infinite_scroll_table/cells/amount_cell.dart new file mode 100644 index 00000000..4624f082 --- /dev/null +++ b/lib/rearchitecture/utils/widgets/infinite_scroll_table/cells/amount_cell.dart @@ -0,0 +1,29 @@ +import 'package:flutter/material.dart'; +import 'package:zenon_syrius_wallet_flutter/rearchitecture/utils/widgets/infinite_scroll_table/infinite_scroll_table.dart'; +import 'package:zenon_syrius_wallet_flutter/utils/utils.dart'; +import 'package:zenon_syrius_wallet_flutter/widgets/reusable_widgets/formatted_amount_with_tooltip.dart'; +import 'package:znn_sdk_dart/znn_sdk_dart.dart'; + +class AmountCell extends StatelessWidget { + const AmountCell({required this.block, super.key}); + + final AccountBlock block; + + @override + Widget build(BuildContext context) { + return InfiniteScrollTableCell( + child: Padding( + padding: const EdgeInsets.only(right: 10), + child: FormattedAmountWithTooltip( + amount: block.amount.addDecimals( + block.token?.decimals ?? 0, + ), + tokenSymbol: block.token?.symbol ?? '', + builder: (String formattedAmount, String tokenSymbol) => Text( + formattedAmount, + ), + ), + ), + ); + } +} diff --git a/lib/rearchitecture/utils/widgets/infinite_scroll_table/cells/analysis_options.yaml b/lib/rearchitecture/utils/widgets/infinite_scroll_table/cells/analysis_options.yaml new file mode 100644 index 00000000..360a563a --- /dev/null +++ b/lib/rearchitecture/utils/widgets/infinite_scroll_table/cells/analysis_options.yaml @@ -0,0 +1,3 @@ +linter: + rules: + public_member_api_docs: false diff --git a/lib/rearchitecture/utils/widgets/infinite_scroll_table/cells/asset_cell.dart b/lib/rearchitecture/utils/widgets/infinite_scroll_table/cells/asset_cell.dart new file mode 100644 index 00000000..92a2d83c --- /dev/null +++ b/lib/rearchitecture/utils/widgets/infinite_scroll_table/cells/asset_cell.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; +import 'package:zenon_syrius_wallet_flutter/rearchitecture/utils/widgets/infinite_scroll_table/infinite_scroll_table.dart'; +import 'package:zenon_syrius_wallet_flutter/utils/utils.dart'; +import 'package:znn_sdk_dart/znn_sdk_dart.dart'; + +class AssetCell extends StatelessWidget { + const AssetCell({required this.block, super.key}); + + final AccountBlock block; + + @override + Widget build(BuildContext context) { + final String content = block.token!.symbol; + + final Color textColor = ColorUtils.getTokenColor(block.tokenStandard); + + final String tooltipMessage = block.token!.tokenStandard.toString(); + + return InfiniteScrollTableCell.withText( + content: content, + tooltipMessage: tooltipMessage, + textStyle: TextStyle( + color: textColor, + ), + ); + } +} diff --git a/lib/rearchitecture/utils/widgets/infinite_scroll_table/cells/cells.dart b/lib/rearchitecture/utils/widgets/infinite_scroll_table/cells/cells.dart new file mode 100644 index 00000000..d843617f --- /dev/null +++ b/lib/rearchitecture/utils/widgets/infinite_scroll_table/cells/cells.dart @@ -0,0 +1,7 @@ +export 'address_cell.dart'; +export 'amount_cell.dart'; +export 'asset_cell.dart'; +export 'date_cell.dart'; +export 'hash_cell.dart'; +export 'receive_cell.dart'; +export 'type_cell.dart'; diff --git a/lib/rearchitecture/utils/widgets/infinite_scroll_table/cells/date_cell.dart b/lib/rearchitecture/utils/widgets/infinite_scroll_table/cells/date_cell.dart new file mode 100644 index 00000000..871bfbb9 --- /dev/null +++ b/lib/rearchitecture/utils/widgets/infinite_scroll_table/cells/date_cell.dart @@ -0,0 +1,29 @@ +import 'package:flutter/material.dart'; +import 'package:zenon_syrius_wallet_flutter/rearchitecture/utils/utils.dart'; +import 'package:zenon_syrius_wallet_flutter/utils/utils.dart'; +import 'package:znn_sdk_dart/znn_sdk_dart.dart'; + +class DateCell extends StatelessWidget { + const DateCell({required this.block, super.key}); + + final AccountBlock block; + + @override + Widget build(BuildContext context) { + final int millis = (block.confirmationDetail?.momentumTimestamp ?? 0) * 1000; + + return InfiniteScrollTableCell.withText( + content: millis == 0 + ? context.l10n.pending + : FormatUtils.formatDateForTable( + millis, + ), + tooltipMessage: millis == 0 + ? '' + : FormatUtils.formatDate( + millis, + dateFormat: 'MMM d, y HH:mm:ss' + ), + ); + } +} diff --git a/lib/rearchitecture/utils/widgets/infinite_scroll_table/cells/hash_cell.dart b/lib/rearchitecture/utils/widgets/infinite_scroll_table/cells/hash_cell.dart new file mode 100644 index 00000000..f847a9ce --- /dev/null +++ b/lib/rearchitecture/utils/widgets/infinite_scroll_table/cells/hash_cell.dart @@ -0,0 +1,19 @@ +import 'package:flutter/material.dart'; +import 'package:zenon_syrius_wallet_flutter/rearchitecture/utils/widgets/infinite_scroll_table/infinite_scroll_table.dart'; +import 'package:znn_sdk_dart/znn_sdk_dart.dart'; + +class HashCell extends StatelessWidget { + const HashCell({required this.hash, super.key}); + + final Hash hash; + + @override + Widget build(BuildContext context) { + return InfiniteScrollTableCell.withText( + content: hash.toShortString(), + flex: 2, + tooltipMessage: hash.toString(), + textToBeCopied: hash.toString(), + ); + } +} diff --git a/lib/rearchitecture/utils/widgets/infinite_scroll_table/cells/receive_cell.dart b/lib/rearchitecture/utils/widgets/infinite_scroll_table/cells/receive_cell.dart new file mode 100644 index 00000000..673cf22a --- /dev/null +++ b/lib/rearchitecture/utils/widgets/infinite_scroll_table/cells/receive_cell.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; +import 'package:zenon_syrius_wallet_flutter/blocs/blocs.dart'; +import 'package:zenon_syrius_wallet_flutter/main.dart'; +import 'package:zenon_syrius_wallet_flutter/rearchitecture/utils/utils.dart'; +import 'package:zenon_syrius_wallet_flutter/utils/utils.dart'; +import 'package:znn_sdk_dart/znn_sdk_dart.dart'; + +class ReceiveCell extends StatelessWidget { + const ReceiveCell({required this.hash, super.key}); + + final Hash hash; + + @override + Widget build(BuildContext context) { + return InfiniteScrollTableCell( + child: Align( + alignment: Alignment.centerLeft, + child: IconButton( + tooltip: context.l10n.pressToReceive, + icon: const Icon(Icons.call_received_rounded), + color: AppColors.znnColor, + onPressed: () { + sl().autoReceiveTransactionHash( + hash, + ); + }, + ), + ), + ); + } +} diff --git a/lib/rearchitecture/utils/widgets/infinite_scroll_table/cells/type_cell.dart b/lib/rearchitecture/utils/widgets/infinite_scroll_table/cells/type_cell.dart new file mode 100644 index 00000000..484768e8 --- /dev/null +++ b/lib/rearchitecture/utils/widgets/infinite_scroll_table/cells/type_cell.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; +import 'package:zenon_syrius_wallet_flutter/rearchitecture/utils/utils.dart'; +import 'package:zenon_syrius_wallet_flutter/utils/utils.dart'; +import 'package:znn_sdk_dart/znn_sdk_dart.dart'; + +class TypeCell extends StatelessWidget { + const TypeCell({required this.block, super.key}); + + final AccountBlock block; + + @override + Widget build(BuildContext context) { + late final Widget child; + + if (BlockUtils.isSendBlock(block.blockType)) { + child = Tooltip( + message: context.l10n.send, + child: const Icon( + Icons.call_made_rounded, + color: AppColors.errorColor, + ), + ); + } else { + child = Tooltip( + message: context.l10n.receive, + child: const Icon( + Icons.call_received_rounded, + color: AppColors.znnColor, + ), + ); + } + + return InfiniteScrollTableCell( + child: Align( + alignment: Alignment.centerLeft, + child: child, + ), + ); + } +} diff --git a/lib/rearchitecture/utils/widgets/infinite_scroll_table/export.dart b/lib/rearchitecture/utils/widgets/infinite_scroll_table/export.dart new file mode 100644 index 00000000..517b4f75 --- /dev/null +++ b/lib/rearchitecture/utils/widgets/infinite_scroll_table/export.dart @@ -0,0 +1,2 @@ +export './cells/cells.dart'; +export 'infinite_scroll_table.dart'; diff --git a/lib/rearchitecture/utils/widgets/infinite_scroll_table/infinite_scroll_table.dart b/lib/rearchitecture/utils/widgets/infinite_scroll_table/infinite_scroll_table.dart new file mode 100644 index 00000000..3746ae6b --- /dev/null +++ b/lib/rearchitecture/utils/widgets/infinite_scroll_table/infinite_scroll_table.dart @@ -0,0 +1,185 @@ +import 'package:flutter/material.dart'; +import 'package:zenon_syrius_wallet_flutter/rearchitecture/utils/constants/app_sizes.dart'; +import 'package:zenon_syrius_wallet_flutter/rearchitecture/utils/extensions/buildcontext_extension.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'; + +part 'infinite_scroll_table_column_type.dart'; + +part 'infinite_scroll_table_cell.dart'; + +part 'infinite_scroll_table_column.dart'; + +/// A table for displaying a large set of items +/// +/// It should receive an [onScrollReachedBottom] callback that is triggered +/// when the user scrolls to the bottom of the list. The callback should load +/// the next batch of items. +/// +/// There is the case that the first batch can fit inside the view port of the +/// widget, without any need of scrolling. These scenario will block the +/// automatic loading of the next batches, because the [ScrollController] +/// listener won't be triggered. The [onScrollReachedBottom] callback is called +/// after the widget has finished rendering in order to immediately fetch more +/// items, if possible, so that the scroll actives +class InfiniteScrollTable extends StatefulWidget { + /// Creates a new instance. + const InfiniteScrollTable({ + required this.items, + required this.hasReachedMax, + required this.generateRowCells, + required this.onScrollReachedBottom, + required this.columns, + super.key, + }); + + /// The columns that will the organized in an fixed header + final List columns; + + /// Function that takes in an item and returns the cells consisting the row + final List Function(T) generateRowCells; + + /// Callback to be executed when the bottom of the table was reached. + final VoidCallback onScrollReachedBottom; + + /// List of items that constitutes the rows. + final List items; + + /// Whether there are still items that can be fetched. + final bool hasReachedMax; + + @override + State createState() => _InfiniteScrollTableState(); +} + +class _InfiniteScrollTableState extends State> { + final ScrollController _scrollController = ScrollController(); + + @override + void initState() { + super.initState(); + _scrollController.addListener(_onScroll); + WidgetsBinding.instance.addPostFrameCallback((_) { + if (_scrollController.hasClients && !widget.hasReachedMax) { + if (_scrollController.position.maxScrollExtent == 0) { + widget.onScrollReachedBottom.call(); + } + } + }); + } + + @override + void dispose() { + _scrollController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + if (widget.items.isEmpty) { + return SyriusErrorWidget(context.l10n.noItemsFound); + } + + return CustomScrollView( + controller: _scrollController, + slivers: [ + PinnedHeaderSliver( + child: _Header(columns: widget.columns), + ), + SliverList.separated( + separatorBuilder: (_, __) => const Divider( + thickness: 0.75, + ), + itemCount: widget.items.length + 1, + itemBuilder: (BuildContext context, int index) { + final Widget lastChild = widget.hasReachedMax + ? SyriusErrorWidget(context.l10n.noMoreItems) + : const SyriusLoadingWidget(); + + return index == widget.items.length + ? lastChild + : _getTableRow( + widget.items[index], + index, + ); + }, + ), + ], + ); + } + + void _onScroll() { + if (_isBottom) { + widget.onScrollReachedBottom.call(); + } + } + + bool get _isBottom { + if (!_scrollController.hasClients) return false; + final double maxScroll = _scrollController.position.maxScrollExtent; + final double currentScroll = _scrollController.offset; + return currentScroll >= (maxScroll * 0.9); + } + + Widget _getTableRow(T item, int indexOfRow) { + return Padding( + padding: const EdgeInsets.symmetric( + horizontal: kInfiniteTableHorizontalPadding, + vertical: kInfiniteTableHorizontalPadding / 2, + ), + child: Row( + children: widget.generateRowCells(item), + ), + ); + } +} + +class _Header extends StatelessWidget { + const _Header({required this.columns}); + + final List columns; + + @override + Widget build(BuildContext context) { + // The content is scrolled under the header, hence we need to cover it up + final Color background = + context.isDarkMode ? AppColors.darkPrimary : Colors.white; + + final List children = List.generate( + columns.length, + (int index) { + final InfiniteScrollTableColumnType column = columns[index]; + + final int flex = column.flex; + + final String name = column.name(context: context); + + return InfiniteScrollTableColumn( + name: name, + flex: flex, + ); + }, + ); + + return ColoredBox( + color: background, + child: Column( + children: [ + SizedBox( + height: 50, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: kInfiniteTableHorizontalPadding, + ), + child: Row( + children: children, + ), + ), + ), + const Divider(), + ], + ), + ); + } +} diff --git a/lib/rearchitecture/utils/widgets/infinite_scroll_table/infinite_scroll_table_cell.dart b/lib/rearchitecture/utils/widgets/infinite_scroll_table/infinite_scroll_table_cell.dart new file mode 100644 index 00000000..b5f7da5e --- /dev/null +++ b/lib/rearchitecture/utils/widgets/infinite_scroll_table/infinite_scroll_table_cell.dart @@ -0,0 +1,106 @@ +part of 'infinite_scroll_table.dart'; + +/// A cell in a table +/// +/// It's based on the [Expanded] widgets, so the [child] will be forced to +/// occupy the entire available space. The space in the row is determined by +/// the [flex]. +class InfiniteScrollTableCell extends StatelessWidget { + /// Creates a new instance. + const InfiniteScrollTableCell({ + required this.child, + this.flex = 1, + super.key, + }); + + /// A constructor used when the main child widget is a [Text]. + /// + /// If [tooltipMessage] is not empty, then a tooltip message will appear + /// If [textToBeCopied] is not null, then the cell will also have a + /// [CopyToClipboardButton] widget that copies to clipboard the + /// [textToBeCopied] - which is not already the same as the [content] value. + factory InfiniteScrollTableCell.withText({ + required String content, + String? textToBeCopied, + TextStyle? textStyle, + int flex = 1, + String tooltipMessage = '', + }) => + InfiniteScrollTableCell( + flex: flex, + child: Row( + children: [ + Flexible( + child: Tooltip( + message: tooltipMessage, + child: Text( + content, + overflow: TextOverflow.ellipsis, + maxLines: 1, + style: textStyle, + ), + ), + ), + if (textToBeCopied != null) + Row( + children: [ + CopyToClipboardButton( + textToBeCopied, + iconSize: 15, + ), + const SizedBox( + width: 10, + ), + ], + ), + ], + ), + ); + + /// A constructor that helps creating cell content starting from an [address]. + /// + /// Useful especially for styling cell content when the address is one of the + /// user's wallet + factory InfiniteScrollTableCell.textFromAddress({ + required Address address, + bool isStakeAddress = false, + bool isShortVersion = true, + }) { + final TextStyle? textStyle = address.isEmbedded() || + (isStakeAddress && address.toString() == kSelectedAddress) + ? const TextStyle( + color: AppColors.znnColor, + fontWeight: FontWeight.bold, + ) + : null; + + final bool hasLabel = kAddressLabelMap[address.toString()] != null; + + final String content = hasLabel + ? ZenonAddressUtils.getLabel(address.toString()) + : isShortVersion + ? address.toShortString() + : address.toString(); + + return InfiniteScrollTableCell.withText( + content: content, + flex: 2, + textStyle: textStyle, + tooltipMessage: address.toString(), + textToBeCopied: address.toString(), + ); + } + + /// The child that represents the content of the cell + final Widget child; + /// Represents the space amount in a row assign to the cell + final int flex; + + @override + Widget build(BuildContext context) { + return Expanded( + flex: flex, + child: child, + ); + } +} diff --git a/lib/rearchitecture/utils/widgets/infinite_scroll_table/infinite_scroll_table_column.dart b/lib/rearchitecture/utils/widgets/infinite_scroll_table/infinite_scroll_table_column.dart new file mode 100644 index 00000000..c480b2da --- /dev/null +++ b/lib/rearchitecture/utils/widgets/infinite_scroll_table/infinite_scroll_table_column.dart @@ -0,0 +1,52 @@ +part of 'infinite_scroll_table.dart'; + +/// A widget that represents a column inside the [InfiniteScrollTable] header. +/// +/// Currently the sort arrows are not visible, by default, so the row sorting +/// is disabled +class InfiniteScrollTableColumn extends StatelessWidget { + /// Creates a new instance. + const InfiniteScrollTableColumn({ + required this.name, + this.onSortArrowsPressed, + this.flex = 1, + super.key, + }); + /// The name of the column. + final String name; + /// Callback to be executed when the sort arrows are pressed. + final Function(String)? onSortArrowsPressed; + /// Defines how much space the column should take. + final int flex; + + @override + Widget build(BuildContext context) { + return Expanded( + flex: flex, + child: Row( + children: [ + Expanded( + child: Text( + name, + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: AppColors.znnColor, + ), + ), + ), + // TODO(community): enable row sorting + Visibility( + visible: false, + child: InkWell( + onTap: () => onSortArrowsPressed!(name), + child: Icon( + Icons.sort, + size: 15, + color: Theme.of(context).iconTheme.color, + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/rearchitecture/utils/widgets/infinite_scroll_table/infinite_scroll_table_column_type.dart b/lib/rearchitecture/utils/widgets/infinite_scroll_table/infinite_scroll_table_column_type.dart new file mode 100644 index 00000000..a5f418a3 --- /dev/null +++ b/lib/rearchitecture/utils/widgets/infinite_scroll_table/infinite_scroll_table_column_type.dart @@ -0,0 +1,35 @@ +part of 'infinite_scroll_table.dart'; + +// ignore_for_file: public_member_api_docs +enum InfiniteScrollTableColumnType { + amount, + asset, + blank, + date, + hash, + receiver, + sender, + type; + + int get flex => switch (this) { + amount => 1, + asset => 1, + blank => 1, + date => 1, + hash => 2, + receiver => 2, + sender => 2, + type => 1, + }; + + String name({required BuildContext context}) => switch (this) { + amount => context.l10n.amount, + asset => context.l10n.asset, + blank => '', + date => context.l10n.date, + hash => context.l10n.hash, + receiver => context.l10n.receiver, + sender => context.l10n.sender, + type => context.l10n.type, + }; +} diff --git a/lib/rearchitecture/utils/widgets/new_card_scaffold.dart b/lib/rearchitecture/utils/widgets/new_card_scaffold.dart index a270062a..5875b575 100644 --- a/lib/rearchitecture/utils/widgets/new_card_scaffold.dart +++ b/lib/rearchitecture/utils/widgets/new_card_scaffold.dart @@ -9,8 +9,6 @@ import 'package:lottie/lottie.dart'; import 'package:zenon_syrius_wallet_flutter/main.dart'; import 'package:zenon_syrius_wallet_flutter/rearchitecture/utils/theming/new_app_themes.dart'; import 'package:zenon_syrius_wallet_flutter/rearchitecture/utils/utils.dart'; -import 'package:zenon_syrius_wallet_flutter/rearchitecture/utils/widgets/card_scaffold_header.dart'; -import 'package:zenon_syrius_wallet_flutter/rearchitecture/utils/widgets/card_scaffold_password_field.dart'; import 'package:zenon_syrius_wallet_flutter/utils/utils.dart'; import 'package:zenon_syrius_wallet_flutter/widgets/widgets.dart'; diff --git a/lib/rearchitecture/utils/widgets/widgets.dart b/lib/rearchitecture/utils/widgets/widgets.dart index 0a687079..1e8b09bb 100644 --- a/lib/rearchitecture/utils/widgets/widgets.dart +++ b/lib/rearchitecture/utils/widgets/widgets.dart @@ -1 +1,5 @@ +export './dropdowns/dropdowns.dart'; +export './infinite_scroll_table/export.dart'; +export 'card_scaffold_header.dart'; +export 'card_scaffold_password_field.dart'; export 'new_card_scaffold.dart'; diff --git a/lib/screens/splash_screen.dart b/lib/screens/splash_screen.dart index 87c32ca4..5706c7f9 100644 --- a/lib/screens/splash_screen.dart +++ b/lib/screens/splash_screen.dart @@ -33,6 +33,11 @@ class SplashScreen extends StatefulWidget { class _SplashScreenState extends State { late final Future _composition; + // The lottie uses a cache to load a file, if the file was previously loaded + // the Future.builder will fire twice with the snapshot.hasData = true, and + // it will call _splashInits method twice + bool _splashInitsCalled = false; + @override void initState() { super.initState(); @@ -46,7 +51,7 @@ class _SplashScreenState extends State { builder: (BuildContext context, AsyncSnapshot snapshot) { final LottieComposition? composition = snapshot.data; if (composition != null) { - Future.delayed(composition.duration, _splashInits); + Future.delayed(composition.duration, _splashInits); return Lottie( composition: composition, repeat: false, @@ -60,12 +65,15 @@ class _SplashScreenState extends State { Future _splashInits() async { try { - widget.resetWalletFlow - ? await _resetWallet() - : widget.deleteCacheFlow - ? await _deleteCache().then((value) => exit(0)) - : await InitUtils.initApp(context); - _navigateToNextScreen(); + if (!_splashInitsCalled) { + _splashInitsCalled = true; + widget.resetWalletFlow + ? await _resetWallet() + : widget.deleteCacheFlow + ? await _deleteCache().then((value) => exit(0)) + : await InitUtils.initApp(context); + _navigateToNextScreen(); + } } on Exception catch (e) { Navigator.pushReplacementNamed( context, @@ -138,8 +146,10 @@ class _SplashScreenState extends State { await Hive.deleteBoxFromDisk(kKeyStoreBox); if (kWalletFile != null) kWalletFile!.close(); kWalletFile = null; - await FileUtils.deleteFile(kWalletPath!); - kWalletPath = null; + if (kWalletPath != null) { + await FileUtils.deleteFile(kWalletPath!); + kWalletPath = null; + } } void _checkForDefaultNode() => sharedPrefsService!.get( diff --git a/lib/services/web3wallet_service.dart b/lib/services/web3wallet_service.dart index 6f1742bf..6b0a9ebc 100644 --- a/lib/services/web3wallet_service.dart +++ b/lib/services/web3wallet_service.dart @@ -293,7 +293,7 @@ class Web3WalletService extends IWeb3WalletService { final PairingMetadata dAppMetadata = event.params.proposer.metadata; - final actionWasAccepted = await showDialogWithNoAndYesOptions( + final bool? actionWasAccepted = await showDialogWithNoAndYesOptions( context: globalNavigatorKey.currentContext!, isBarrierDismissible: false, title: 'Approve session', @@ -322,11 +322,9 @@ class Web3WalletService extends IWeb3WalletService { ), ], ), - onYesButtonPressed: () async {}, - onNoButtonPressed: () async {}, ); - if (actionWasAccepted) { + if (actionWasAccepted ?? false) { if (!_idSessionsApproved.contains(event.id)) { _idSessionsApproved.add(event.id); try { diff --git a/lib/utils/account_block_utils.dart b/lib/utils/account_block_utils.dart index aac17c4c..27561a66 100644 --- a/lib/utils/account_block_utils.dart +++ b/lib/utils/account_block_utils.dart @@ -9,7 +9,7 @@ import 'package:zenon_syrius_wallet_flutter/utils/global.dart'; import 'package:znn_sdk_dart/znn_sdk_dart.dart'; class AccountBlockUtils { - static Future createAccountBlock( + Future createAccountBlock( AccountBlockTemplate transactionParams, String purposeOfGeneratingPlasma, { Address? address, @@ -53,9 +53,6 @@ class AccountBlockUtils { }, waitForRequiredPlasma: waitForRequiredPlasma, ); - if (BlockUtils.isReceiveBlock(transactionParams.blockType)) { - sl.get().getBalanceForAllAddresses(); - } await sl.get().addNotification( WalletNotification( title: 'Account-block published', diff --git a/lib/utils/address_utils.dart b/lib/utils/address_utils.dart index 107a50c2..3911a9a7 100644 --- a/lib/utils/address_utils.dart +++ b/lib/utils/address_utils.dart @@ -6,6 +6,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/rearchitecture/features/features.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'; @@ -15,9 +16,14 @@ import 'package:znn_ledger_dart/znn_ledger_dart.dart'; import 'package:znn_sdk_dart/znn_sdk_dart.dart'; class ZenonAddressUtils { - static void refreshBalance() => - sl.get().getBalanceForAllAddresses(); - + void refreshBalance() { + sl.get().getBalanceForAllAddresses(); + sl.get().add( + MultipleBalanceFetch( + addresses: kDefaultAddressList.map((String? e) => e!).toList(), + ), + ); + } static String getLabel(String address) => kAddressLabelMap[address] ?? address; @@ -51,6 +57,7 @@ class ZenonAddressUtils { final Box addressesBox = Hive.box(kAddressesBox); await addressesBox.add(listAddr.elementAt(i).toString()); _initAddresses(addressesBox); + final Box addressLabelsBox = Hive.box(kAddressLabelsBox); await addressLabelsBox.put( listAddr.elementAt(i).toString(), @@ -60,6 +67,11 @@ class ZenonAddressUtils { NodeUtils.getUnreceivedTransactionsByAddress(listAddr[i]!); callback?.call(); } + sl.get().add( + MultipleBalanceFetch( + addresses: kDefaultAddressList.map((String? e) => e!).toList(), + ), + ); listAddr.clear(); } catch (e) { await NotificationUtils.sendNotificationError( diff --git a/lib/utils/app_colors.dart b/lib/utils/app_colors.dart index ea0175f3..e4ad417b 100644 --- a/lib/utils/app_colors.dart +++ b/lib/utils/app_colors.dart @@ -2,10 +2,10 @@ import 'package:flutter/material.dart'; class AppColors { static const Color znnColor = Color(0xFF4FD166); - static const Color qsrColor = Color(0xFF005FE3); - static const Color ztsColor = Color(0xFFF91690); + static const Color qsrColor = Color(0xFF006AFF); + static const Color ztsColor = Color(0xFFFC54AE); - static const Color errorColor = Color.fromRGBO(244, 4, 88, 1); + static const Color errorColor = Color(0xFFF40442); static const Color backgroundLight = Color(0xFFEAEAEA); static const Color backgroundDark = Color(0xFF1E1E1E); diff --git a/lib/utils/color_utils.dart b/lib/utils/color_utils.dart index e9443ac2..0b38a30f 100644 --- a/lib/utils/color_utils.dart +++ b/lib/utils/color_utils.dart @@ -7,19 +7,13 @@ import 'package:znn_sdk_dart/znn_sdk_dart.dart'; class ColorUtils { static Color getTokenColor(TokenStandard tokenStandard) { return kCoinIdColor[tokenStandard] ?? - getColorFromHexCode(_getHexCodeFromTokenZts(tokenStandard)); + AppColors.ztsColor; } static Color getColorFromHexCode(String hexCode) { return Color(int.parse(hexCode, radix: 16) + 0xFF000000); } - static String _getHexCodeFromTokenZts(TokenStandard tokenStandard) { - final List bytes = - tokenStandard.getBytes().sublist(tokenStandard.getBytes().length - 3); - return BytesUtils.bytesToHex(bytes); - } - static Color getPlasmaColor(PlasmaInfo plasmaInfo) { if (plasmaInfo.currentPlasma >= kPillarPlasmaAmountNeeded) { return AppColors.znnColor; diff --git a/lib/utils/format_utils.dart b/lib/utils/format_utils.dart index e6dbae36..766c69b1 100644 --- a/lib/utils/format_utils.dart +++ b/lib/utils/format_utils.dart @@ -9,8 +9,10 @@ class FormatUtils { String replacementString, ) => [ - FilteringTextInputFormatter.allow(RegExp(r'^\d*\.?\d*?$'), - replacementString: replacementString,), + FilteringTextInputFormatter.allow( + RegExp(r'^\d*\.?\d*?$'), + replacementString: replacementString, + ), FilteringTextInputFormatter.deny( RegExp(r'^0\d+'), replacementString: replacementString, @@ -29,10 +31,13 @@ class FormatUtils { ]; static String encodeHexString(List input) => HEX.encode(input); + static List decodeHexString(String input) => HEX.decode(input); - static String formatDate(int timestampMillis, - {String dateFormat = kDefaultDateFormat,}) { + static String formatDate( + int timestampMillis, { + String dateFormat = kDefaultDateFormat, + }) { final DateTime date = DateTime.fromMillisecondsSinceEpoch(timestampMillis); return DateFormat(dateFormat).format(date); } @@ -60,13 +65,24 @@ class FormatUtils { .millisecondsSinceEpoch; } - static String formatData(int transactionMillis) { + static String formatDateForTable(int transactionMillis) { final 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'); + + final DateTime now = DateTime.now(); + final DateTime date = DateTime.fromMillisecondsSinceEpoch( + transactionMillis, + ); + final bool isCurrentYear = date.year == now.year; + + // Use different formats based on the year condition + final String dateFormat = isCurrentYear + ? 'MMM d' // Format without the year + : 'MMM d, y'; // Format with the year + return FormatUtils.formatDate(transactionMillis, dateFormat: dateFormat); } static String formatDataShort(int i) { diff --git a/lib/utils/widget_utils.dart b/lib/utils/widget_utils.dart index 0e7e8061..4595e999 100644 --- a/lib/utils/widget_utils.dart +++ b/lib/utils/widget_utils.dart @@ -16,7 +16,8 @@ class WidgetUtils { listen: false, ); final ThemeMode savedThemeMode = ThemeMode.values.firstWhere( - (ThemeMode element) => element.toString() == sharedPrefsService!.get(kThemeModeKey), + (ThemeMode element) => + element.toString() == sharedPrefsService!.get(kThemeModeKey), orElse: () => kDefaultThemeMode, ); if (appThemeNotifier.currentThemeMode != savedThemeMode) { @@ -25,7 +26,8 @@ class WidgetUtils { } static void setTextScale(BuildContext context) { - final TextScalingNotifier textScalingNotifier = Provider.of( + final TextScalingNotifier textScalingNotifier = + Provider.of( context, listen: false, ); @@ -48,7 +50,7 @@ class WidgetUtils { BuildContext context, ) { final TextStyle? textStyle = address != null && address.isEmbedded() - ? Theme.of(context).textTheme.titleMedium!.copyWith( + ? Theme.of(context).textTheme.bodyMedium?.copyWith( color: AppColors.znnColor, fontWeight: FontWeight.bold, ) @@ -90,21 +92,14 @@ class WidgetUtils { bool isShortVersion = true, bool showCopyToClipboardIcon = false, }) { - TextStyle? textStyle = address != null && address.isEmbedded() - ? Theme.of(context).textTheme.titleMedium!.copyWith( + final TextStyle? textStyle = address != null && address.isEmbedded() || + (checkIfStakeAddress && address.toString() == kSelectedAddress) + ? Theme.of(context).textTheme.bodyMedium?.copyWith( color: AppColors.znnColor, fontWeight: FontWeight.bold, ) : null; - if (checkIfStakeAddress) { - textStyle = address != null && address.toString() == kSelectedAddress - ? Theme.of(context).textTheme.titleMedium!.copyWith( - color: AppColors.znnColor, - fontWeight: FontWeight.bold, - ) - : null; - } if (address != null && kAddressLabelMap.containsKey(address.toString())) { return InfiniteScrollTableCell.tooltipWithText( context, diff --git a/lib/utils/zts_utils.dart b/lib/utils/zts_utils.dart index 22cf0ba7..2789ac3a 100644 --- a/lib/utils/zts_utils.dart +++ b/lib/utils/zts_utils.dart @@ -9,31 +9,35 @@ final List kDualCoin = [ kQsrCoin, ]; -final Token kZnnCoin = Token( - 'Zenon', - 'ZNN', - 'zenon.network', - BigInt.zero, - coinDecimals, - pillarAddress, - TokenStandard.parse(znnTokenStandard), - BigInt.zero, - true, - true, - true, +final Token kZnnCoin = Token.fromJson( + { + 'name': 'ZNN', + 'symbol': 'ZNN', + 'domain': 'zenon.network', + 'totalSupply': '1190555916718187', + 'decimals': 8, + 'owner': 'z1qxemdeddedxt0kenxxxxxxxxxxxxxxxxh9amk0', + 'tokenStandard': 'zts1znnxxxxxxxxxxxxx9z4ulx', + 'maxSupply': '9007199254740991', + 'isBurnable': true, + 'isMintable': true, + 'isUtility': true, + }, ); -final Token kQsrCoin = Token( - 'Quasar', - 'QSR', - 'zenon.network', - BigInt.zero, - coinDecimals, - stakeAddress, - TokenStandard.parse(qsrTokenStandard), - BigInt.zero, - true, - true, - true, +final Token kQsrCoin = Token.fromJson( + { + 'name': 'QSR', + 'symbol': 'QSR', + 'domain': 'zenon.network', + 'totalSupply': '3049289789638539', + 'decimals': 8, + 'owner': 'z1qxemdeddedxt0kenxxxxxxxxxxxxxxxxh9amk0', + 'tokenStandard': 'zts1qsrxxxxxxxxxxxxxmrhjll', + 'maxSupply': '9007199254740991', + 'isBurnable': true, + 'isMintable': true, + 'isUtility': true, + }, ); final Map kCoinIdColor = { diff --git a/lib/widgets/main_app_container.dart b/lib/widgets/main_app_container.dart index 85d62ec4..c38eef5a 100644 --- a/lib/widgets/main_app_container.dart +++ b/lib/widgets/main_app_container.dart @@ -75,7 +75,6 @@ class _MainAppContainerState extends State Timer? _navigateToLockTimer; TabController? _tabController; - TransferTabChild? _transferTabChild; bool _initialUriIsHandled = false; final AppLinks _appLinks = AppLinks(); @@ -94,7 +93,6 @@ class _MainAppContainerState extends State ClipboardUtils.toggleClipboardWatcherStatus(); - _transferTabChild = TransferTabChild(); _initTabController(); _animationController = AnimationController( vsync: this, @@ -113,10 +111,13 @@ class _MainAppContainerState extends State @override Widget build(BuildContext context) { return Consumer( - builder: (BuildContext context, TextScalingNotifier textScalingNotifier, Widget? child) => MediaQuery( + builder: (BuildContext context, TextScalingNotifier textScalingNotifier, + Widget? child) => + MediaQuery( data: MediaQuery.of(context).copyWith( textScaler: TextScaler.linear( - textScalingNotifier.getTextScaleFactor(context),), + textScalingNotifier.getTextScaleFactor(context), + ), ), child: Scaffold( body: Container( @@ -127,8 +128,9 @@ class _MainAppContainerState extends State children: [ _getDesktopNavigationContainer(), SizedBox( - height: - NotificationUtils.shouldShowNotification() ? 15.0 : 20.0, + height: NotificationUtils.shouldShowNotification() + ? 15.0 + : 20.0, ), NotificationWidget( onSeeMorePressed: () { @@ -402,7 +404,7 @@ class _MainAppContainerState extends State controller: _tabController, children: [ DashboardTabChild(changePage: _navigateTo), - _transferTabChild!, + TransferTabChild(), PillarsTabChild( onStepperNotificationSeeMorePressed: () => _navigateTo(Tabs.notifications), @@ -505,18 +507,7 @@ class _MainAppContainerState extends State int _getTabChildIndex(Tabs page) => kTabs.indexOf(page); - void _navigateTo( - Tabs page, { - bool redirectWithSendContainerLarge = false, - bool redirectWithReceiveContainerLarge = false, - }) { - if (redirectWithSendContainerLarge) { - _transferTabChild!.sendCard = DimensionCard.large; - _transferTabChild!.receiveCard = DimensionCard.small; - } else if (redirectWithReceiveContainerLarge) { - _transferTabChild!.sendCard = DimensionCard.small; - _transferTabChild!.receiveCard = DimensionCard.large; - } + void _navigateTo(Tabs page) { if (kCurrentPage != page) { kCurrentPage = page; _tabController!.animateTo(kTabs.indexOf(page)); @@ -582,7 +573,8 @@ class _MainAppContainerState extends State } Timer _createAutoLockTimer() { - return Timer.periodic(Duration(minutes: kAutoLockWalletMinutes!), (Timer timer) { + return Timer.periodic(Duration(minutes: kAutoLockWalletMinutes!), + (Timer timer) { if (!sl().hasActiveIncomingSwaps) { _lockBloc.addEvent(LockEvent.navigateToLock); } @@ -609,7 +601,8 @@ class _MainAppContainerState extends State if (Platform.isWindows) { uriRaw = uriRaw.replaceAll('/?', '?'); } - final String wcUri = Uri.decodeFull(uriRaw.split('wc?uri=').last); + final String wcUri = + Uri.decodeFull(uriRaw.split('wc?uri=').last); if (WalletConnectUri.tryParse(wcUri) != null) { await _updateWalletConnectUri(wcUri); } @@ -625,7 +618,8 @@ class _MainAppContainerState extends State Token? token; if (uri.hasQuery) { - uri.queryParametersAll.forEach((String key, List value) async { + uri.queryParametersAll + .forEach((String key, List value) async { if (key == 'amount') { queryAmount = value.first; } else if (key == 'zts') { @@ -651,9 +645,10 @@ class _MainAppContainerState extends State } } - final SendPaymentBloc sendPaymentBloc = SendPaymentBloc(); - final StakingOptionsBloc stakingOptionsBloc = StakingOptionsBloc(); - final DelegateButtonBloc delegateButtonBloc = DelegateButtonBloc(); + final StakingOptionsBloc stakingOptionsBloc = + StakingOptionsBloc(); + final DelegateButtonBloc delegateButtonBloc = + DelegateButtonBloc(); final PlasmaOptionsBloc plasmaOptionsBloc = PlasmaOptionsBloc(); if (context.mounted) { @@ -672,7 +667,8 @@ class _MainAppContainerState extends State _navigateTo(Tabs.transfer); if (token != null) { - showDialogWithNoAndYesOptions( + final bool? actionAccepted = + await showDialogWithNoAndYesOptions( context: context, title: 'Transfer action', isBarrierDismissible: true, @@ -684,20 +680,21 @@ class _MainAppContainerState extends State ), ], ), - onYesButtonPressed: () { - sendPaymentBloc.sendTransfer( - fromAddress: kSelectedAddress, - toAddress: queryAddress, - amount: - queryAmount.extractDecimals(token!.decimals), - token: token, - ); - }, - onNoButtonPressed: () {}, ); + + if (actionAccepted ?? false) { + context.read().add( + SendTransactionInitiate( + fromAddress: kSelectedAddress!, + toAddress: queryAddress, + amount: queryAmount + .extractDecimals(token.decimals), + token: token, + ), + ); + } } } - case 'stake': await sl().addNotification( WalletNotification( @@ -711,7 +708,7 @@ class _MainAppContainerState extends State if (kCurrentPage != Tabs.lock) { _navigateTo(Tabs.staking); - showDialogWithNoAndYesOptions( + final bool? actionAccepted = await showDialogWithNoAndYesOptions( context: context, title: 'Stake ${kZnnCoin.symbol} action', isBarrierDismissible: true, @@ -723,14 +720,14 @@ class _MainAppContainerState extends State ), ], ), - onYesButtonPressed: () { - stakingOptionsBloc.stakeForQsr( - Duration(seconds: queryDuration * stakeTimeUnitSec), - queryAmount.extractDecimals(kZnnCoin.decimals), - ); - }, - onNoButtonPressed: () {}, ); + + if (actionAccepted ?? false) { + stakingOptionsBloc.stakeForQsr( + Duration(seconds: queryDuration * stakeTimeUnitSec), + queryAmount.extractDecimals(kZnnCoin.decimals), + ); + } } case 'delegate': @@ -746,7 +743,7 @@ class _MainAppContainerState extends State if (kCurrentPage != Tabs.lock) { _navigateTo(Tabs.pillars); - showDialogWithNoAndYesOptions( + final bool? actionAccepted = await showDialogWithNoAndYesOptions( context: context, title: 'Delegate ${kZnnCoin.symbol} action', isBarrierDismissible: true, @@ -758,11 +755,11 @@ class _MainAppContainerState extends State ), ], ), - onYesButtonPressed: () { - delegateButtonBloc.delegateToPillar(queryPillarName); - }, - onNoButtonPressed: () {}, ); + + if (actionAccepted ?? false) { + delegateButtonBloc.delegateToPillar(queryPillarName); + } } case 'fuse': @@ -778,7 +775,7 @@ class _MainAppContainerState extends State if (kCurrentPage != Tabs.lock) { _navigateTo(Tabs.plasma); - showDialogWithNoAndYesOptions( + final bool? actionAccepted = await showDialogWithNoAndYesOptions( context: context, title: 'Fuse ${kQsrCoin.symbol} action', isBarrierDismissible: true, @@ -790,14 +787,14 @@ class _MainAppContainerState extends State ), ], ), - onYesButtonPressed: () { - plasmaOptionsBloc.generatePlasma( - queryAddress, - queryAmount.extractDecimals(kZnnCoin.decimals), - ); - }, - onNoButtonPressed: () {}, ); + + if (actionAccepted ?? false) { + plasmaOptionsBloc.generatePlasma( + queryAddress, + queryAmount.extractDecimals(kZnnCoin.decimals), + ); + } } case 'sentinel': @@ -891,7 +888,8 @@ class _MainAppContainerState extends State @override Future onClipboardChanged() async { - final ClipboardData? newClipboardData = await Clipboard.getData(Clipboard.kTextPlain); + final ClipboardData? newClipboardData = + await Clipboard.getData(Clipboard.kTextPlain); final String text = newClipboardData?.text ?? ''; if (text.isNotEmpty && WalletConnectUri.tryParse(text) != null) { // This check is needed because onClipboardChanged is called twice sometimes diff --git a/lib/widgets/modular_widgets/p2p_swap_widgets/detail_row.dart b/lib/widgets/modular_widgets/p2p_swap_widgets/detail_row.dart index 3765cebd..6cdc1533 100644 --- a/lib/widgets/modular_widgets/p2p_swap_widgets/detail_row.dart +++ b/lib/widgets/modular_widgets/p2p_swap_widgets/detail_row.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:zenon_syrius_wallet_flutter/utils/app_colors.dart'; -import 'package:zenon_syrius_wallet_flutter/widgets/reusable_widgets/icons/copy_to_clipboard_icon.dart'; +import 'package:zenon_syrius_wallet_flutter/widgets/reusable_widgets/buttons/copy_to_clipboard_button.dart'; class DetailRow extends StatelessWidget { @@ -38,11 +38,8 @@ class DetailRow extends StatelessWidget { fontSize: 12, color: AppColors.subtitleColor,),), Visibility( visible: canBeCopied, - child: CopyToClipboardIcon( + child: CopyToClipboardButton( value, - iconColor: AppColors.subtitleColor, - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - padding: const EdgeInsets.only(left: 8), ), ), ], diff --git a/lib/widgets/modular_widgets/p2p_swap_widgets/p2p_swaps_card.dart b/lib/widgets/modular_widgets/p2p_swap_widgets/p2p_swaps_card.dart index 49d99b3e..0aeab282 100644 --- a/lib/widgets/modular_widgets/p2p_swap_widgets/p2p_swaps_card.dart +++ b/lib/widgets/modular_widgets/p2p_swap_widgets/p2p_swaps_card.dart @@ -8,11 +8,11 @@ import 'package:zenon_syrius_wallet_flutter/widgets/modular_widgets/p2p_swap_wid import 'package:zenon_syrius_wallet_flutter/widgets/widgets.dart'; class P2pSwapsCard extends StatefulWidget { - const P2pSwapsCard({ required this.onStepperNotificationSeeMorePressed, super.key, }); + final VoidCallback onStepperNotificationSeeMorePressed; @override @@ -99,31 +99,35 @@ class _P2pSwapsCardState extends State { } Future _onDeleteSwapTapped(P2pSwap swap) async { - showDialogWithNoAndYesOptions( - context: context, - isBarrierDismissible: true, - title: 'Delete swap', - description: - 'Are you sure you want to delete this swap? This action cannot be undone.', - onYesButtonPressed: () async { - if (swap.mode == P2pSwapMode.htlc) { - await htlcSwapsService!.deleteSwap(swap.id); - } - _p2pSwapsListBloc.getData(); - },); + final bool? deleteConfirmed = await showDialogWithNoAndYesOptions( + context: context, + isBarrierDismissible: true, + title: 'Delete swap', + description: + 'Are you sure you want to delete this swap? This action cannot be undone.', + ); + + if (deleteConfirmed ?? false) { + if (swap.mode == P2pSwapMode.htlc) { + await htlcSwapsService!.deleteSwap(swap.id); + } + _p2pSwapsListBloc.getData(); + } } Future _onDeleteSwapHistoryTapped() async { - showDialogWithNoAndYesOptions( - context: context, - isBarrierDismissible: true, - title: 'Delete swap history', - description: - 'Are you sure you want to delete your swap history? Active swaps cannot be deleted.', - onYesButtonPressed: () async { - await htlcSwapsService!.deleteInactiveSwaps(); - _p2pSwapsListBloc.getData(); - },); + final bool? deleteHistoryConfirmed = await showDialogWithNoAndYesOptions( + context: context, + isBarrierDismissible: true, + title: 'Delete swap history', + description: + 'Are you sure you want to delete your swap history? Active swaps cannot be deleted.', + ); + + if (deleteHistoryConfirmed ?? false) { + await htlcSwapsService!.deleteInactiveSwaps(); + _p2pSwapsListBloc.getData(); + } } Widget _getTable(List swaps) { @@ -143,22 +147,23 @@ class _P2pSwapsCardState extends State { child: Scrollbar( controller: _scrollController, child: ListView.separated( - controller: _scrollController, - cacheExtent: 1000, - itemCount: swaps.length, - separatorBuilder: (_, __) { - return const SizedBox( - height: 15, - ); - }, - itemBuilder: (_, int index) { - return P2pSwapsListItem( - key: ValueKey(swaps.elementAt(index).id), - swap: swaps.elementAt(index), - onTap: _onSwapTapped, - onDelete: _onDeleteSwapTapped, - ); - },), + controller: _scrollController, + cacheExtent: 1000, + itemCount: swaps.length, + separatorBuilder: (_, __) { + return const SizedBox( + height: 15, + ); + }, + itemBuilder: (_, int index) { + return P2pSwapsListItem( + key: ValueKey(swaps.elementAt(index).id), + swap: swaps.elementAt(index), + onTap: _onSwapTapped, + onDelete: _onDeleteSwapTapped, + ); + }, + ), ), ), ], diff --git a/lib/widgets/modular_widgets/pillar_widgets/pillar_collect.dart b/lib/widgets/modular_widgets/pillar_widgets/pillar_collect.dart index 46e7dae8..a27fabfc 100644 --- a/lib/widgets/modular_widgets/pillar_widgets/pillar_collect.dart +++ b/lib/widgets/modular_widgets/pillar_widgets/pillar_collect.dart @@ -97,7 +97,7 @@ class _PillarCollectState extends State { _onCollectPressed() async { try { _collectButtonKey.currentState?.animateForward(); - await AccountBlockUtils.createAccountBlock( + await AccountBlockUtils().createAccountBlock( zenon!.embedded.pillar.collectReward(), 'collect Pillar rewards', waitForRequiredPlasma: true, diff --git a/lib/widgets/modular_widgets/sentinel_widgets/sentinel_collect.dart b/lib/widgets/modular_widgets/sentinel_widgets/sentinel_collect.dart index 61ba7c18..55defba5 100644 --- a/lib/widgets/modular_widgets/sentinel_widgets/sentinel_collect.dart +++ b/lib/widgets/modular_widgets/sentinel_widgets/sentinel_collect.dart @@ -104,7 +104,7 @@ class _SentinelCollectState extends State { Future _onCollectPressed() async { try { _collectButtonKey.currentState?.animateForward(); - await AccountBlockUtils.createAccountBlock( + await AccountBlockUtils().createAccountBlock( zenon!.embedded.sentinel.collectReward(), 'collect Sentinel rewards', waitForRequiredPlasma: true, diff --git a/lib/widgets/modular_widgets/settings_widgets/addresses.dart b/lib/widgets/modular_widgets/settings_widgets/addresses.dart index f1a54d95..8067545d 100644 --- a/lib/widgets/modular_widgets/settings_widgets/addresses.dart +++ b/lib/widgets/modular_widgets/settings_widgets/addresses.dart @@ -5,6 +5,9 @@ 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/rearchitecture/features/features.dart'; +import 'package:zenon_syrius_wallet_flutter/rearchitecture/features/latest_transactions/bloc/latest_transactions_bloc.dart'; +import 'package:zenon_syrius_wallet_flutter/rearchitecture/utils/blocs/blocs.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'; @@ -12,6 +15,7 @@ import 'package:zenon_syrius_wallet_flutter/utils/global.dart'; import 'package:zenon_syrius_wallet_flutter/utils/notifiers/default_address_notifier.dart'; import 'package:zenon_syrius_wallet_flutter/utils/notifiers/plasma_beneficiary_address_notifier.dart'; import 'package:zenon_syrius_wallet_flutter/widgets/widgets.dart'; +import 'package:znn_sdk_dart/znn_sdk_dart.dart'; class Addresses extends StatefulWidget { @@ -19,6 +23,7 @@ class Addresses extends StatefulWidget { required this.accountChainStatsBloc, super.key, }); + final AccountChainStatsBloc accountChainStatsBloc; @override @@ -70,6 +75,16 @@ class AddressesState extends State { ); widget.accountChainStatsBloc.updateStream(); _selectedAddress = newDefaultAddress; + context.read().add( + InfiniteListRefreshRequested( + address: Address.parse(_selectedAddress!), + ), + ); + context.read().add( + InfiniteListRefreshRequested( + address: Address.parse(_selectedAddress!), + ), + ); } catch (e) { rethrow; } @@ -102,34 +117,35 @@ class AddressesState extends State { setState(() { _futureGenerateNewAddress = ZenonAddressUtils.generateNewAddress( - numAddr: numberOfAddressesToAdd, - callback: () { - setState(() { - _shouldScrollToTheEnd = true; - }); - },); + numAddr: numberOfAddressesToAdd, + callback: () { + setState(() { + _shouldScrollToTheEnd = true; + }); + }, + ); }); }, child: Container( - constraints: - const BoxConstraints(minWidth: 150, minHeight: 50), - alignment: Alignment.center, - child: Row( - children: [ - const Icon( - Icons.add_circle, - color: AppColors.znnColor, - size: 20, - ), - const SizedBox(width: 10), - Text( - (numberOfAddressesToAdd == 1) - ? 'Add $numberOfAddressesToAdd address ' - : 'Add $numberOfAddressesToAdd addresses', - style: Theme.of(context).textTheme.bodyLarge, - ), - ], - ),), + constraints: const BoxConstraints(minWidth: 150, minHeight: 50), + alignment: Alignment.center, + child: Row( + children: [ + const Icon( + Icons.add_circle, + color: AppColors.znnColor, + size: 20, + ), + const SizedBox(width: 10), + Text( + (numberOfAddressesToAdd == 1) + ? 'Add $numberOfAddressesToAdd address ' + : 'Add $numberOfAddressesToAdd addresses', + style: Theme.of(context).textTheme.bodyLarge, + ), + ], + ), + ), ), const SizedBox(width: 10), ], diff --git a/lib/widgets/modular_widgets/settings_widgets/security.dart b/lib/widgets/modular_widgets/settings_widgets/security.dart index 0b5e7b66..7ea47a0b 100644 --- a/lib/widgets/modular_widgets/settings_widgets/security.dart +++ b/lib/widgets/modular_widgets/settings_widgets/security.dart @@ -248,9 +248,8 @@ class _SecurityWidgetState extends State { onChanged: (String? value) { setState(() {}); }, - suffixIcon: CopyToClipboardIcon( + suffixIcon: CopyToClipboardButton( _textToBeSignedController.text, - hoverColor: Colors.transparent, ), ), kVerticalSpacing, @@ -281,7 +280,7 @@ class _SecurityWidgetState extends State { controller: _signedTextController, ), ), - CopyToClipboardIcon(_signedTextController.text), + CopyToClipboardButton(_signedTextController.text), ], ), const SizedBox( @@ -299,7 +298,7 @@ class _SecurityWidgetState extends State { controller: _publicKeyController, ), ), - CopyToClipboardIcon(_publicKeyController.text), + CopyToClipboardButton(_publicKeyController.text), ], ), ], @@ -521,7 +520,7 @@ class _SecurityWidgetState extends State { controller: _fileHashController, ), ), - CopyToClipboardIcon(_fileHashController.text), + CopyToClipboardButton(_fileHashController.text), ], ), kVerticalSpacing, @@ -535,7 +534,7 @@ class _SecurityWidgetState extends State { controller: _publicKeySignFileController, ), ), - CopyToClipboardIcon(_publicKeySignFileController.text), + CopyToClipboardButton(_publicKeySignFileController.text), ], ), ], diff --git a/lib/widgets/modular_widgets/staking_widgets/stake_collect.dart b/lib/widgets/modular_widgets/staking_widgets/stake_collect.dart index d5cebe5b..f582707b 100644 --- a/lib/widgets/modular_widgets/staking_widgets/stake_collect.dart +++ b/lib/widgets/modular_widgets/staking_widgets/stake_collect.dart @@ -90,7 +90,7 @@ class _StakeCollectState extends State { Future _onCollectPressed() async { try { _collectButtonKey.currentState?.animateForward(); - await AccountBlockUtils.createAccountBlock( + await AccountBlockUtils().createAccountBlock( zenon!.embedded.stake.collectReward(), 'collect staking rewards', waitForRequiredPlasma: true, diff --git a/lib/widgets/modular_widgets/token_widgets/token_card.dart b/lib/widgets/modular_widgets/token_widgets/token_card.dart index 1a9375c7..33848028 100644 --- a/lib/widgets/modular_widgets/token_widgets/token_card.dart +++ b/lib/widgets/modular_widgets/token_widgets/token_card.dart @@ -169,7 +169,7 @@ class _TokenCardState extends State { ), ), ), - CopyToClipboardIcon( + CopyToClipboardButton( widget.token.tokenStandard.toString(), ), ], @@ -267,7 +267,7 @@ class _TokenCardState extends State { : widget.token.owner.toShortString(), style: Theme.of(context).textTheme.titleSmall, ), - CopyToClipboardIcon(widget.token.owner.toString()), + CopyToClipboardButton(widget.token.owner.toString()), ], ), RawMaterialButton( diff --git a/lib/widgets/modular_widgets/token_widgets/token_stepper.dart b/lib/widgets/modular_widgets/token_widgets/token_stepper.dart index 860e55b4..32a83255 100644 --- a/lib/widgets/modular_widgets/token_widgets/token_stepper.dart +++ b/lib/widgets/modular_widgets/token_widgets/token_stepper.dart @@ -7,6 +7,7 @@ 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/rearchitecture/features/tokens/cubit/tokens_cubit.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'; @@ -708,6 +709,7 @@ class _TokenStepperState extends State { (AccountBlockTemplate response) { _createButtonKey.currentState?.animateReverse(); _saveProgressAndNavigateToNextStep(TokenStepperStep.issueToken); + sl.get().fetch(); }, onError: (error) async { _createButtonKey.currentState?.animateReverse(); 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 deleted file mode 100644 index b096f034..00000000 --- a/lib/widgets/modular_widgets/transfer_widgets/latest_transactions/latest_transactions.dart +++ /dev/null @@ -1,364 +0,0 @@ -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 { - - const LatestTransactions({ - super.key, - this.version = LatestTransactionsVersion.standard, - }); - final LatestTransactionsVersion version; - - @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, - ) { - final AccountBlock infoBlock = - BlockUtils.isReceiveBlock(transactionBlock.blockType) - ? transactionBlock.pairedAccountBlock! - : transactionBlock; - return [ - if (isSelected) WidgetUtils.getMarqueeAddressTableCell(infoBlock.address, context) else WidgetUtils.getTextAddressTableCell(infoBlock.address, context), - if (isSelected) WidgetUtils.getMarqueeAddressTableCell(infoBlock.toAddress, context) else WidgetUtils.getTextAddressTableCell(infoBlock.toAddress, context), - if (isSelected) InfiniteScrollTableCell.withMarquee(infoBlock.hash.toString(), - flex: 2,) else 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: (String formattedAmount, String 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, - ); - } - if (BlockUtils.isReceiveBlock(block.blockType)) { - return const Icon( - MaterialCommunityIcons.arrow_down, - color: AppColors.lightHintTextColor, - size: 20, - ); - } - 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( - (AccountBlock a, AccountBlock b) => a.address.toString().compareTo( - b.address.toString(), - ), - ) - : _transactions!.sort( - (AccountBlock a, AccountBlock b) => b.address.toString().compareTo( - a.address.toString(), - ), - ); - case 'Receiver': - _sortAscending - ? _transactions!.sort( - (AccountBlock a, AccountBlock b) => a.toAddress.toString().compareTo( - b.toAddress.toString(), - ), - ) - : _transactions!.sort((AccountBlock a, AccountBlock b) => - b.toAddress.toString().compareTo(a.toAddress.toString()),); - case 'Hash': - _sortAscending - ? _transactions!.sort( - (AccountBlock a, AccountBlock b) => a.hash.toString().compareTo( - b.hash.toString(), - ), - ) - : _transactions!.sort( - (AccountBlock a, AccountBlock b) => b.hash.toString().compareTo( - a.hash.toString(), - ), - ); - case 'Amount': - _sortAscending - ? _transactions!.sort((AccountBlock a, AccountBlock b) => a.amount.compareTo(b.amount)) - : _transactions!.sort((AccountBlock a, AccountBlock b) => b.amount.compareTo(a.amount)); - case 'Date': - _sortAscending - ? _transactions!.sort( - (AccountBlock a, AccountBlock b) => a.confirmationDetail!.momentumTimestamp.compareTo( - b.confirmationDetail!.momentumTimestamp, - ),) - : _transactions!.sort( - (AccountBlock a, AccountBlock b) => b.confirmationDetail!.momentumTimestamp.compareTo( - a.confirmationDetail!.momentumTimestamp, - ),); - case 'Type': - _sortAscending - ? _transactions!.sort((AccountBlock a, AccountBlock b) => a.blockType.compareTo(b.blockType)) - : _transactions!.sort((AccountBlock a, AccountBlock b) => b.blockType.compareTo(a.blockType)); - case 'Assets': - _sortAscending - ? _transactions!.sort( - (AccountBlock a, AccountBlock b) => a.token!.symbol.compareTo(b.token!.symbol), - ) - : _transactions!.sort( - (AccountBlock a, AccountBlock b) => b.token!.symbol.compareTo(a.token!.symbol), - ); - default: - _sortAscending - ? _transactions!.sort( - (AccountBlock a, AccountBlock b) => a.tokenStandard.toString().compareTo( - b.tokenStandard.toString(), - ), - ) - : _transactions!.sort( - (AccountBlock a, AccountBlock 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, - ) { - final AccountBlock infoBlock = - BlockUtils.isReceiveBlock(transactionBlock.blockType) - ? transactionBlock.pairedAccountBlock! - : transactionBlock; - - return [ - if (isSelected) WidgetUtils.getMarqueeAddressTableCell(infoBlock.address, context) else 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: (String formattedAmount, String 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( - 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/latest_transactions/latest_transactions_transfer_widget.dart b/lib/widgets/modular_widgets/transfer_widgets/latest_transactions/latest_transactions_transfer_widget.dart deleted file mode 100644 index b60fec95..00000000 --- a/lib/widgets/modular_widgets/transfer_widgets/latest_transactions/latest_transactions_transfer_widget.dart +++ /dev/null @@ -1,373 +0,0 @@ -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 { - - const LatestTransactions({ - super.key, - this.version = LatestTransactionsVersion.standard, - }); - final LatestTransactionsVersion version; - - @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) involving your wallet addresses', - 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, { - SentinelsListBloc? model, - }) => - widget.version == LatestTransactionsVersion.dashboard - ? _getCellsForDashboardWidget(isSelected, transaction) - : _getCellsForTransferWidget(isSelected, transaction); - - List _getCellsForTransferWidget( - bool isSelected, - AccountBlock transactionBlock, - ) { - final AccountBlock infoBlock = - BlockUtils.isReceiveBlock(transactionBlock.blockType) - ? transactionBlock.pairedAccountBlock! - : transactionBlock; - return [ - if (isSelected) WidgetUtils.getMarqueeAddressTableCell(infoBlock.address, context) else WidgetUtils.getTextAddressTableCell(infoBlock.address, context), - if (isSelected) WidgetUtils.getMarqueeAddressTableCell(infoBlock.toAddress, context) else WidgetUtils.getTextAddressTableCell(infoBlock.toAddress, context), - if (isSelected) InfiniteScrollTableCell.withMarquee(infoBlock.hash.toString(), - flex: 2,) else 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: (String formattedAmount, String tokenSymbol) => Text( - formattedAmount, - style: Theme.of(context).textTheme.titleMedium!.copyWith( - color: AppColors.subtitleColor, - ), - ), - ), - ), - ),), - InfiniteScrollTableCell.withText( - context, - infoBlock.confirmationDetail?.momentumTimestamp == null - ? 'Pending' - : _formatData( - infoBlock.confirmationDetail!.momentumTimestamp * 1000,), - ), - InfiniteScrollTableCell(_getTransactionTypeIcon(transactionBlock)), - InfiniteScrollTableCell( - 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, - contentAlign: MainAxisAlignment.center, - ), - InfiniteScrollTableHeaderColumn( - columnName: 'Assets', - onSortArrowsPressed: _onSortArrowsPressed, - ), - ]; - } - - String _formatData(int transactionMillis) { - final 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'); - } - - String _formatDataShort(int i) { - final 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'; - } - - Widget _getTransactionTypeIcon(AccountBlock block) { - if (BlockUtils.isSendBlock(block.blockType)) { - return const Icon( - MaterialCommunityIcons.arrow_up, - color: AppColors.darkHintTextColor, - size: 20, - ); - } - if (BlockUtils.isReceiveBlock(block.blockType)) { - return const Icon( - MaterialCommunityIcons.arrow_down, - color: AppColors.lightHintTextColor, - size: 20, - ); - } - return Text( - FormatUtils.extractNameFromEnum( - BlockTypeEnum.values[block.blockType], - ), - textAlign: TextAlign.center, - 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( - (AccountBlock a, AccountBlock b) => a.address.toString().compareTo( - b.address.toString(), - ), - ) - : _transactions!.sort( - (AccountBlock a, AccountBlock b) => b.address.toString().compareTo( - a.address.toString(), - ), - ); - case 'Receiver': - _sortAscending - ? _transactions!.sort( - (AccountBlock a, AccountBlock b) => a.toAddress.toString().compareTo( - b.toAddress.toString(), - ), - ) - : _transactions!.sort((AccountBlock a, AccountBlock b) => - b.toAddress.toString().compareTo(a.toAddress.toString()),); - case 'Hash': - _sortAscending - ? _transactions!.sort( - (AccountBlock a, AccountBlock b) => a.hash.toString().compareTo( - b.hash.toString(), - ), - ) - : _transactions!.sort( - (AccountBlock a, AccountBlock b) => b.hash.toString().compareTo( - a.hash.toString(), - ), - ); - case 'Amount': - _sortAscending - ? _transactions!.sort((AccountBlock a, AccountBlock b) => a.amount.compareTo(b.amount)) - : _transactions!.sort((AccountBlock a, AccountBlock b) => b.amount.compareTo(a.amount)); - case 'Date': - _sortAscending - ? _transactions!.sort( - (AccountBlock a, AccountBlock b) => a.confirmationDetail!.momentumTimestamp.compareTo( - b.confirmationDetail!.momentumTimestamp, - ),) - : _transactions!.sort( - (AccountBlock a, AccountBlock b) => b.confirmationDetail!.momentumTimestamp.compareTo( - a.confirmationDetail!.momentumTimestamp, - ),); - case 'Type': - _sortAscending - ? _transactions!.sort((AccountBlock a, AccountBlock b) => a.blockType.compareTo(b.blockType)) - : _transactions!.sort((AccountBlock a, AccountBlock b) => b.blockType.compareTo(a.blockType)); - case 'Assets': - _sortAscending - ? _transactions!.sort( - (AccountBlock a, AccountBlock b) => a.token!.symbol.compareTo(b.token!.symbol), - ) - : _transactions!.sort( - (AccountBlock a, AccountBlock b) => b.token!.symbol.compareTo(a.token!.symbol), - ); - default: - _sortAscending - ? _transactions!.sort( - (AccountBlock a, AccountBlock b) => a.tokenStandard.toString().compareTo( - b.tokenStandard.toString(), - ), - ) - : _transactions!.sort( - (AccountBlock a, AccountBlock 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, - contentAlign: MainAxisAlignment.center, - ), - InfiniteScrollTableHeaderColumn( - columnName: 'Assets', - onSortArrowsPressed: _onSortArrowsPressed, - ), - ]; - } - - List _getCellsForDashboardWidget( - bool isSelected, - AccountBlock transactionBlock, - ) { - final AccountBlock infoBlock = - BlockUtils.isReceiveBlock(transactionBlock.blockType) - ? transactionBlock.pairedAccountBlock! - : transactionBlock; - - return [ - if (isSelected) WidgetUtils.getMarqueeAddressTableCell(infoBlock.address, context) else 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: (String formattedAmount, String tokenSymbol) => Text( - formattedAmount, - style: Theme.of(context).textTheme.titleMedium!.copyWith( - color: AppColors.subtitleColor, - ), - ), - ), - ), - ), - ), - InfiniteScrollTableCell.withText( - context, - infoBlock.confirmationDetail?.momentumTimestamp == null - ? 'Pending' - : _formatData( - infoBlock.confirmationDetail!.momentumTimestamp * 1000, - ), - ), - InfiniteScrollTableCell(_getTransactionTypeIcon(transactionBlock)), - InfiniteScrollTableCell( - 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 deleted file mode 100644 index 2bcd6102..00000000 --- a/lib/widgets/modular_widgets/transfer_widgets/pending_transactions/pending_transactions.dart +++ /dev/null @@ -1,292 +0,0 @@ -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({ - super.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,) { - final AccountBlock infoBlock = BlockUtils.isReceiveBlock(transaction.blockType) - ? transaction.pairedAccountBlock! - : transaction; - return [ - if (isSelected) WidgetUtils.getMarqueeAddressTableCell(infoBlock.address, context) else WidgetUtils.getTextAddressTableCell(infoBlock.address, context), - if (isSelected) WidgetUtils.getMarqueeAddressTableCell(infoBlock.toAddress, context) else WidgetUtils.getTextAddressTableCell(infoBlock.toAddress, context), - if (isSelected) InfiniteScrollTableCell.withMarquee(infoBlock.hash.toString(), - flex: 2,) else 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: (String formattedAmount, String 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( - (AccountBlock a, AccountBlock b) => a.address.toString().compareTo( - b.address.toString(), - ), - ) - : _transactions!.sort( - (AccountBlock a, AccountBlock b) => b.address.toString().compareTo( - a.address.toString(), - ), - ); - case 'Receiver': - _sortAscending - ? _transactions!.sort( - (AccountBlock a, AccountBlock b) => a.toAddress.toString().compareTo( - b.toAddress.toString(), - ), - ) - : _transactions!.sort((AccountBlock a, AccountBlock b) => - b.toAddress.toString().compareTo(a.toAddress.toString()),); - case 'Hash': - _sortAscending - ? _transactions!.sort( - (AccountBlock a, AccountBlock b) => a.hash.toString().compareTo( - b.hash.toString(), - ), - ) - : _transactions!.sort( - (AccountBlock a, AccountBlock b) => b.hash.toString().compareTo( - a.hash.toString(), - ), - ); - case 'Amount': - _sortAscending - ? _transactions!.sort((AccountBlock a, AccountBlock b) => a.amount.compareTo(b.amount)) - : _transactions!.sort((AccountBlock a, AccountBlock b) => b.amount.compareTo(a.amount)); - case 'Date': - _sortAscending - ? _transactions!.sort( - (AccountBlock a, AccountBlock b) => a.confirmationDetail!.momentumTimestamp.compareTo( - b.confirmationDetail!.momentumTimestamp, - ),) - : _transactions!.sort( - (AccountBlock a, AccountBlock b) => b.confirmationDetail!.momentumTimestamp.compareTo( - a.confirmationDetail!.momentumTimestamp, - ),); - case 'Assets': - _sortAscending - ? _transactions!.sort( - (AccountBlock a, AccountBlock b) => a.token!.symbol.compareTo(b.token!.symbol), - ) - : _transactions!.sort( - (AccountBlock a, AccountBlock b) => b.token!.symbol.compareTo(a.token!.symbol), - ); - default: - _sortAscending - ? _transactions!.sort( - (AccountBlock a, AccountBlock b) => a.tokenStandard.toString().compareTo( - b.tokenStandard.toString(), - ), - ) - : _transactions!.sort( - (AccountBlock a, AccountBlock 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: (ReceiveTransactionBloc model) { - model.stream.listen( - (AccountBlockTemplate? event) { - if (event != null) { - transactionModel.refreshResults(); - } - }, - onError: (error) async { - await NotificationUtils.sendNotificationError( - error, 'Error while receiving transaction',); - }, - ); - }, - builder: (_, ReceiveTransactionBloc model, __) => _getReceiveButton( - model, - transactionItem.hash.toString(), - ), - viewModelBuilder: ReceiveTransactionBloc.new, - ); - } - - Widget _getReceiveButton( - ReceiveTransactionBloc model, - String transactionHash, - ) { - return MaterialIconButton( - size: 25, - 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 deleted file mode 100644 index 817f4401..00000000 --- a/lib/widgets/modular_widgets/transfer_widgets/receive/receive_large.dart +++ /dev/null @@ -1,241 +0,0 @@ -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/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/format_utils.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/zts_utils.dart'; -import 'package:zenon_syrius_wallet_flutter/widgets/widgets.dart'; -import 'package:znn_sdk_dart/znn_sdk_dart.dart'; - -class ReceiveLargeCard extends StatefulWidget { - - const ReceiveLargeCard({ - required this.onCollapseClicked, super.key, - this.extendIcon, - }); - final bool? extendIcon; - final VoidCallback onCollapseClicked; - - @override - State createState() => _ReceiveLargeCardState(); -} - -class _ReceiveLargeCardState extends State { - final TextEditingController _transferAddressController = - TextEditingController(); - final TextEditingController _amountController = TextEditingController(); - - final GlobalKey _formKey = GlobalKey(); - final GlobalKey _amountKey = GlobalKey(); - - String? _selectedSelfAddress = kSelectedAddress; - - Token _selectedToken = kDualCoin.first; - final List _tokens = []; - - final Box _recipientAddressBox = Hive.box(kRecipientAddressBox); - - final TokensBloc _tokensBloc = TokensBloc(); - - @override - void initState() { - super.initState(); - _initAddressController(); - _tokensBloc.getDataAsync(); - } - - @override - Widget build(BuildContext context) { - return CardScaffold( - title: 'Receive', - titleFontSize: Theme.of(context).textTheme.headlineSmall!.fontSize, - description: 'Manage receiving funds', - childBuilder: _getTokensStreamBuilder, - ); - } - - Widget _getTokensStreamBuilder() { - return StreamBuilder?>( - stream: _tokensBloc.stream, - builder: (_, AsyncSnapshot?> snapshot) { - if (snapshot.hasError) { - return SyriusErrorWidget(snapshot.error!); - } - if (snapshot.connectionState == ConnectionState.active) { - if (snapshot.hasData) { - return _getWidgetBody(context, snapshot.data!); - } - return const SyriusLoadingWidget(); - } - return const SyriusLoadingWidget(); - }, - ); - } - - Widget _getWidgetBody(BuildContext context, List tokens) { - _initTokens(tokens); - - return Container( - margin: const EdgeInsets.only( - right: 20, - top: 20, - ), - child: Form( - key: _formKey, - autovalidateMode: AutovalidateMode.onUserInteraction, - child: ListView( - shrinkWrap: true, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox( - width: 20, - ), - ReceiveQrImage( - data: _getQrString(), - size: 150, - tokenStandard: _selectedToken.tokenStandard, - context: context, - ), - const SizedBox( - width: 20, - ), - Expanded( - child: Column( - children: [ - Row( - children: [ - Expanded( - flex: 7, - child: _getDefaultAddressDropdown(), - ), - CopyToClipboardIcon( - _selectedSelfAddress, - iconColor: AppColors.darkHintTextColor, - ), - ], - ), - kVerticalSpacing, - Form( - key: _amountKey, - autovalidateMode: AutovalidateMode.onUserInteraction, - child: InputField( - validator: (String? value) => InputValidators.correctValue( - value, - kBigP255m1, - _selectedToken.decimals, - BigInt.zero,), - onChanged: (String value) => setState(() {}), - inputFormatters: - FormatUtils.getAmountTextInputFormatters( - _amountController.text, - ), - controller: _amountController, - suffixIcon: Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.end, - children: [ - _getCoinDropdown(), - const SizedBox( - width: 15, - ), - ], - ), - hintText: 'Amount', - ), - ), - ], - ), - ), - ], - ), - Row( - children: [ - Visibility( - visible: widget.extendIcon!, - child: TransferToggleCardSizeButton( - onPressed: widget.onCollapseClicked, - iconData: Icons.navigate_next, - ), - ), - ], - ), - ], - ), - ), - ); - } - - String _getQrString() { - return '${_selectedToken.symbol.toLowerCase()}:' - '$_selectedSelfAddress?zts=${_selectedToken.tokenStandard}' - '&amount=${_getAmount()}'; - } - - BigInt _getAmount() { - try { - return _amountController.text.extractDecimals(_selectedToken.decimals); - } catch (e) { - return BigInt.zero; - } - } - - void _initAddressController() { - if (_recipientAddressBox.isNotEmpty) { - _transferAddressController.text = _recipientAddressBox.getAt(0); - } - } - - Widget _getDefaultAddressDropdown() { - return AddressesDropdown( - _selectedSelfAddress, - (String? value) => setState( - () { - _selectedToken = kDualCoin.first; - _selectedSelfAddress = value; - _tokensBloc.getDataAsync(); - }, - ), - ); - } - - Widget _getCoinDropdown() => CoinDropdown( - _tokens, - _selectedToken, - (Token? value) { - if (_selectedToken.tokenStandard != value!.tokenStandard) { - setState( - () { - _selectedToken = value; - }, - ); - } - }, - ); - - void _initTokens(List tokens) { - if (_tokens.isNotEmpty) { - _tokens.clear(); - } - _tokens.addAll(kDualCoin); - for (final Token element in tokens) { - if (!_tokens.contains(element)) { - _tokens.add(element); - } - } - } - - @override - void dispose() { - _transferAddressController.dispose(); - _amountController.dispose(); - _tokensBloc.dispose(); - super.dispose(); - } -} diff --git a/lib/widgets/modular_widgets/transfer_widgets/receive/receive_medium.dart b/lib/widgets/modular_widgets/transfer_widgets/receive/receive_medium.dart deleted file mode 100644 index ef6815c1..00000000 --- a/lib/widgets/modular_widgets/transfer_widgets/receive/receive_medium.dart +++ /dev/null @@ -1,233 +0,0 @@ -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/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/format_utils.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/zts_utils.dart'; -import 'package:zenon_syrius_wallet_flutter/widgets/widgets.dart'; -import 'package:znn_sdk_dart/znn_sdk_dart.dart'; - -class ReceiveMediumCard extends StatefulWidget { - - const ReceiveMediumCard({required this.onExpandClicked, super.key}); - final VoidCallback onExpandClicked; - - @override - State createState() => _ReceiveMediumCardState(); -} - -class _ReceiveMediumCardState extends State { - final TextEditingController _transferAddrController = TextEditingController(); - final TextEditingController _amountController = TextEditingController(); - - final GlobalKey _formKey = GlobalKey(); - - String? _selectedSelfAddress = kSelectedAddress; - - Token _selectedToken = kDualCoin.first; - - final List _tokens = []; - - final GlobalKey _amountKey = GlobalKey(); - - final Box _recipientAddressBox = Hive.box(kRecipientAddressBox); - - final TokensBloc _tokensBloc = TokensBloc(); - - @override - void initState() { - _initAddressController(); - _tokensBloc.getDataAsync(); - super.initState(); - } - - @override - Widget build(BuildContext context) { - return CardScaffold( - title: 'Receive', - titleFontSize: Theme.of(context).textTheme.headlineSmall!.fontSize, - description: 'Manage receiving funds', - childBuilder: _getTokensStreamBuilder, - ); - } - - Widget _getTokensStreamBuilder() { - return StreamBuilder?>( - stream: _tokensBloc.stream, - builder: (_, AsyncSnapshot?> snapshot) { - if (snapshot.hasError) { - return SyriusErrorWidget(snapshot.error!); - } - if (snapshot.connectionState == ConnectionState.active) { - if (snapshot.hasData) { - return _getWidgetBody(context, snapshot.data!); - } - return const SyriusLoadingWidget(); - } - return const SyriusLoadingWidget(); - },); - } - - Widget _getWidgetBody(BuildContext context, List tokens) { - _initTokens(tokens); - - return Container( - margin: const EdgeInsets.only( - right: 20, - top: 20, - ), - child: Form( - key: _formKey, - autovalidateMode: AutovalidateMode.onUserInteraction, - child: ListView( - shrinkWrap: true, - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const SizedBox( - width: 20, - ), - ReceiveQrImage( - data: _getQrString(), - size: 110, - tokenStandard: _selectedToken.tokenStandard, - context: context, - ), - const SizedBox( - width: 20, - ), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Expanded( - child: _getDefaultAddressDropdown(), - ), - CopyToClipboardIcon( - _selectedSelfAddress, - iconColor: AppColors.darkHintTextColor, - ), - ], - ), - kVerticalSpacing, - Form( - key: _amountKey, - autovalidateMode: AutovalidateMode.onUserInteraction, - child: InputField( - validator: (String? value) => InputValidators.correctValue( - value, - kBigP255m1, - _selectedToken.decimals, - BigInt.zero,), - onChanged: (String value) => setState(() {}), - inputFormatters: - FormatUtils.getAmountTextInputFormatters( - _amountController.text, - ), - controller: _amountController, - suffixIcon: Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.end, - children: [ - _getCoinDropdown(), - const SizedBox( - width: 15, - ), - ], - ), - hintText: 'Amount', - ), - ), - ], - ), - ), - ], - ), - const SizedBox( - height: 35, - ), - Row( - children: [ - TransferToggleCardSizeButton( - onPressed: widget.onExpandClicked, - iconData: Icons.navigate_before, - ), - ], - ), - ], - ), - ), - ); - } - - String _getQrString() { - return '${_selectedToken.symbol.toLowerCase()}:' - '$_selectedSelfAddress?zts=${_selectedToken.tokenStandard}' - '&amount=${_getAmount()}'; - } - - BigInt _getAmount() { - try { - return _amountController.text.extractDecimals(_selectedToken.decimals); - } catch (e) { - return BigInt.zero; - } - } - - void _initAddressController() { - if (_recipientAddressBox.isNotEmpty) { - _transferAddrController.text = _recipientAddressBox.getAt(0); - } - } - - Widget _getDefaultAddressDropdown() { - return AddressesDropdown( - _selectedSelfAddress, - (String? value) => setState(() { - _selectedSelfAddress = value; - }), - ); - } - - Widget _getCoinDropdown() => CoinDropdown( - _tokens.toList(), - _selectedToken, - (Token? value) { - if (_selectedToken != value) { - setState( - () { - _selectedToken = value!; - }, - ); - } - }, - ); - - void _initTokens(List tokens) { - if (_tokens.isNotEmpty) { - _tokens.clear(); - } - _tokens.addAll(kDualCoin); - for (final Token element in tokens) { - if (!_tokens.contains(element)) { - _tokens.add(element); - } - } - } - - @override - void dispose() { - _amountController.dispose(); - _transferAddrController.dispose(); - _tokensBloc.dispose(); - super.dispose(); - } -} diff --git a/lib/widgets/modular_widgets/transfer_widgets/receive/receive_small.dart b/lib/widgets/modular_widgets/transfer_widgets/receive/receive_small.dart deleted file mode 100644 index 5cc279d6..00000000 --- a/lib/widgets/modular_widgets/transfer_widgets/receive/receive_small.dart +++ /dev/null @@ -1,49 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_vector_icons/flutter_vector_icons.dart'; -import 'package:zenon_syrius_wallet_flutter/utils/app_colors.dart'; -import 'package:zenon_syrius_wallet_flutter/widgets/widgets.dart'; - -class ReceiveSmallCard extends StatefulWidget { - - const ReceiveSmallCard( - this.onPressed, { - super.key, - }); - final VoidCallback onPressed; - - @override - State createState() => _ReceiveSmallCardState(); -} - -class _ReceiveSmallCardState extends State { - @override - Widget build(BuildContext context) { - return GestureDetector( - onTap: widget.onPressed, - child: Container( - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.primary, - borderRadius: BorderRadius.circular( - 15, - ), - ), - child: const Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon( - SimpleLineIcons.arrow_down_circle, - size: 60, - color: AppColors.lightHintTextColor, - ), - SizedBox( - height: 20, - ), - TransferIconLegend( - legendText: '● Receive', - ), - ], - ), - ), - ); - } -} diff --git a/lib/widgets/modular_widgets/transfer_widgets/send/send_large.dart b/lib/widgets/modular_widgets/transfer_widgets/send/send_large.dart deleted file mode 100644 index 1fce39f7..00000000 --- a/lib/widgets/modular_widgets/transfer_widgets/send/send_large.dart +++ /dev/null @@ -1,401 +0,0 @@ -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/main.dart'; -import 'package:zenon_syrius_wallet_flutter/model/model.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/clipboard_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/format_utils.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/notification_utils.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'; - -class SendLargeCard extends StatefulWidget { - - const SendLargeCard({ - super.key, - this.cardWidth, - this.extendIcon, - this.onCollapsePressed, - }); - final double? cardWidth; - final bool? extendIcon; - final VoidCallback? onCollapsePressed; - - @override - State createState() => _SendLargeCardState(); -} - -class _SendLargeCardState extends State { - TextEditingController _recipientController = TextEditingController(); - TextEditingController _amountController = TextEditingController(); - - GlobalKey _recipientKey = GlobalKey(); - GlobalKey _amountKey = GlobalKey(); - - final GlobalKey _sendPaymentButtonKey = GlobalKey(); - - final List _tokensWithBalance = []; - - Token _selectedToken = kDualCoin.first; - - String? _selectedSelfAddress = kSelectedAddress; - - @override - void initState() { - super.initState(); - sl.get().getBalanceForAllAddresses(); - _tokensWithBalance.addAll(kDualCoin); - } - - @override - Widget build(BuildContext context) { - return CardScaffold( - title: 'Send', - titleFontSize: Theme.of(context).textTheme.headlineSmall!.fontSize, - description: 'Manage sending funds', - childBuilder: _getBalanceStreamBuilder, - ); - } - - Widget _getBalanceStreamBuilder() { - return StreamBuilder?>( - stream: sl.get().stream, - builder: (_, AsyncSnapshot?> snapshot) { - if (snapshot.hasError) { - return SyriusErrorWidget(snapshot.error!); - } - if (snapshot.connectionState == ConnectionState.active) { - if (snapshot.hasData) { - if (_tokensWithBalance.length == kDualCoin.length) { - _addTokensWithBalance(snapshot.data![_selectedSelfAddress!]!); - } - return _getBody( - context, - snapshot.data![_selectedSelfAddress!]!, - ); - } - return const SyriusLoadingWidget(); - } - return const SyriusLoadingWidget(); - }, - ); - } - - Widget _getBody(BuildContext context, AccountInfo accountInfo) { - return Container( - margin: const EdgeInsets.only( - left: 20, - top: 20, - ), - child: ListView( - shrinkWrap: true, - children: [ - Container( - margin: const EdgeInsets.only(right: 20), - child: Form( - key: _recipientKey, - autovalidateMode: AutovalidateMode.onUserInteraction, - child: InputField( - onChanged: (String value) { - setState(() {}); - }, - controller: _recipientController, - validator: InputValidators.checkAddress, - suffixIcon: RawMaterialButton( - shape: const CircleBorder(), - onPressed: () { - ClipboardUtils.pasteToClipboard(context, (String value) { - _recipientController.text = value; - setState(() {}); - }); - }, - child: const Icon( - Icons.content_paste, - color: AppColors.darkHintTextColor, - size: 15, - ), - ), - suffixIconConstraints: const BoxConstraints( - maxWidth: 45, - maxHeight: 20, - ), - hintText: 'Recipient Address', - ), - ), - ), - kVerticalSpacing, - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - flex: 8, - child: Container( - margin: const EdgeInsets.only(right: 20), - child: Form( - key: _amountKey, - autovalidateMode: AutovalidateMode.onUserInteraction, - child: InputField( - onChanged: (String value) { - setState(() {}); - }, - inputFormatters: FormatUtils.getAmountTextInputFormatters( - _amountController.text, - ), - controller: _amountController, - validator: (String? value) => InputValidators.correctValue( - value, - accountInfo.getBalance( - _selectedToken.tokenStandard, - ), - _selectedToken.decimals, - BigInt.zero,), - suffixIcon: _getAmountSuffix(accountInfo), - hintText: 'Amount', - ), - ), - ), - ), - ], - ), - const SizedBox( - height: 5, - ), - Padding( - padding: const EdgeInsets.only(left: 10), - child: Text( - 'Send from', - style: Theme.of(context).inputDecorationTheme.hintStyle, - ), - ), - const SizedBox( - height: 5, - ), - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - child: _getDefaultAddressDropdown(), - ), - Container( - width: 10, - ), - _getSendPaymentViewModel(accountInfo), - const SizedBox( - width: 20, - ), - ], - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.only( - left: 10, - top: 10, - ), - child: AvailableBalance( - _selectedToken, - accountInfo, - ), - ), - Visibility( - visible: widget.extendIcon!, - child: TransferToggleCardSizeButton( - onPressed: widget.onCollapsePressed, - iconData: Icons.navigate_before, - ), - ), - ], - ), - ], - ), - ); - } - - void _onSendPaymentPressed(SendPaymentBloc model) { - if (_recipientKey.currentState!.validate() && - _amountKey.currentState!.validate()) { - showDialogWithNoAndYesOptions( - isBarrierDismissible: false, - context: context, - title: 'Send', - description: 'Are you sure you want to transfer ' - '${_amountController.text} ${_selectedToken.symbol} to ' - '${ZenonAddressUtils.getLabel(_recipientController.text)} ?', - onYesButtonPressed: () => _sendPayment(model), - ); - } - } - - Widget _getAmountSuffix(AccountInfo accountInfo) { - return Row( - mainAxisAlignment: MainAxisAlignment.end, - mainAxisSize: MainAxisSize.min, - children: [ - _getCoinDropdown(), - const SizedBox( - width: 5, - ), - AmountSuffixMaxWidget( - onPressed: () => _onMaxPressed(accountInfo), - context: context, - ), - const SizedBox( - width: 15, - ), - ], - ); - } - - void _sendPayment(SendPaymentBloc model) { - _sendPaymentButtonKey.currentState?.animateForward(); - model.sendTransfer( - fromAddress: _selectedSelfAddress, - toAddress: _recipientController.text, - amount: _amountController.text.extractDecimals(_selectedToken.decimals), - token: _selectedToken, - ); - } - - Widget _getDefaultAddressDropdown() { - return AddressesDropdown( - _selectedSelfAddress, - (String? value) => setState( - () { - _selectedSelfAddress = value; - _selectedToken = kDualCoin.first; - _tokensWithBalance.clear(); - _tokensWithBalance.addAll(kDualCoin); - sl.get().getBalanceForAllAddresses(); - }, - ), - ); - } - - Widget _getCoinDropdown() => CoinDropdown( - _tokensWithBalance, - _selectedToken, - (Token? value) { - if (_selectedToken != value) { - setState( - () { - _selectedToken = value!; - }, - ); - } - }, - ); - - void _onMaxPressed(AccountInfo accountInfo) { - final BigInt maxBalance = accountInfo.getBalance( - _selectedToken.tokenStandard, - ); - - if (_amountController.text.isEmpty || - _amountController.text.extractDecimals(_selectedToken.decimals) < - maxBalance) { - setState(() { - _amountController.text = - maxBalance.addDecimals(_selectedToken.decimals); - }); - } - } - - Widget _getSendPaymentViewModel(AccountInfo? accountInfo) { - return ViewModelBuilder.reactive( - onViewModelReady: (SendPaymentBloc model) { - model.stream.listen( - (AccountBlockTemplate? event) async { - if (event is AccountBlockTemplate) { - await _sendConfirmationNotification(); - setState(() { - _sendPaymentButtonKey.currentState?.animateReverse(); - _amountController = TextEditingController(); - _recipientController = TextEditingController(); - _amountKey = GlobalKey(); - _recipientKey = GlobalKey(); - }); - } - }, - onError: (error) async { - _sendPaymentButtonKey.currentState?.animateReverse(); - await _sendErrorNotification(error); - }, - ); - }, - builder: (_, SendPaymentBloc model, __) => SendPaymentButton( - onPressed: _hasBalance(accountInfo!) && _isInputValid(accountInfo) - ? () => _onSendPaymentPressed(model) - : null, - minimumSize: const Size(50, 48), - key: _sendPaymentButtonKey, - ), - viewModelBuilder: SendPaymentBloc.new, - ); - } - - Future _sendErrorNotification(error) async { - await NotificationUtils.sendNotificationError( - error, - "Couldn't send ${_amountController.text} " - '${_selectedToken.symbol} ' - 'to ${_recipientController.text}', - ); - } - - Future _sendConfirmationNotification() async { - await sl.get().addNotification( - WalletNotification( - title: 'Sent ${_amountController.text} ${_selectedToken.symbol} ' - 'to ${ZenonAddressUtils.getLabel(_recipientController.text)}', - timestamp: DateTime.now().millisecondsSinceEpoch, - details: 'Sent ${_amountController.text} ${_selectedToken.symbol} ' - 'from ${ZenonAddressUtils.getLabel(_selectedSelfAddress!)} to ${ZenonAddressUtils.getLabel(_recipientController.text)}', - type: NotificationType.paymentSent, - ), - ); - } - - bool _hasBalance(AccountInfo accountInfo) => - accountInfo.getBalance( - _selectedToken.tokenStandard, - ) > - BigInt.zero; - - void _addTokensWithBalance(AccountInfo accountInfo) { - for (final BalanceInfoListItem balanceInfo in accountInfo.balanceInfoList!) { - if (balanceInfo.balance! > BigInt.zero && - !_tokensWithBalance.contains(balanceInfo.token)) { - _tokensWithBalance.add(balanceInfo.token); - } - } - } - - bool _isInputValid(AccountInfo accountInfo) => - InputValidators.checkAddress(_recipientController.text) == null && - InputValidators.correctValue( - _amountController.text, - accountInfo.getBalance( - _selectedToken.tokenStandard, - ), - _selectedToken.decimals, - BigInt.one, - canBeEqualToMin: true, - ) == - null; - - @override - void dispose() { - _recipientController.dispose(); - _amountController.dispose(); - super.dispose(); - } -} diff --git a/lib/widgets/modular_widgets/transfer_widgets/send/send_medium.dart b/lib/widgets/modular_widgets/transfer_widgets/send/send_medium.dart deleted file mode 100644 index a6444467..00000000 --- a/lib/widgets/modular_widgets/transfer_widgets/send/send_medium.dart +++ /dev/null @@ -1,339 +0,0 @@ -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/main.dart'; -import 'package:zenon_syrius_wallet_flutter/model/model.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/clipboard_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/format_utils.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/notification_utils.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'; - -class SendMediumCard extends StatefulWidget { - - const SendMediumCard({ - required this.onExpandClicked, - super.key, - }); - final VoidCallback onExpandClicked; - - @override - State createState() => _SendMediumCardState(); -} - -class _SendMediumCardState extends State { - TextEditingController _recipientController = TextEditingController(); - TextEditingController _amountController = TextEditingController(); - - GlobalKey _recipientKey = GlobalKey(); - GlobalKey _amountKey = GlobalKey(); - - Token _selectedToken = kDualCoin.first; - - final List _tokensWithBalance = []; - - final FocusNode _recipientFocusNode = FocusNode(); - - final GlobalKey _sendPaymentButtonKey = GlobalKey(); - - @override - void initState() { - super.initState(); - sl.get().getBalanceForAllAddresses(); - _tokensWithBalance.addAll(kDualCoin); - } - - @override - Widget build(BuildContext context) { - return CardScaffold( - title: 'Send', - titleFontSize: Theme.of(context).textTheme.headlineSmall!.fontSize, - description: 'Manage sending funds', - childBuilder: _getBalanceStreamBuilder, - ); - } - - Widget _getBalanceStreamBuilder() { - return StreamBuilder?>( - stream: sl.get().stream, - builder: (_, AsyncSnapshot?> snapshot) { - if (snapshot.hasError) { - return SyriusErrorWidget(snapshot.error!); - } - if (snapshot.connectionState == ConnectionState.active) { - if (snapshot.hasData) { - if (_tokensWithBalance.length == kDualCoin.length) { - _addTokensWithBalance(snapshot.data![kSelectedAddress!]!); - } - return _getBody( - context, - snapshot.data![kSelectedAddress!]!, - ); - } - return const SyriusLoadingWidget(); - } - return const SyriusLoadingWidget(); - }, - ); - } - - Widget _getBody(BuildContext context, AccountInfo accountInfo) { - return Container( - margin: const EdgeInsets.only( - left: 20, - top: 20, - ), - child: ListView( - shrinkWrap: true, - children: [ - Container( - margin: const EdgeInsets.only(right: 20), - child: Form( - key: _recipientKey, - autovalidateMode: AutovalidateMode.onUserInteraction, - child: InputField( - onChanged: (String value) { - setState(() {}); - }, - thisNode: _recipientFocusNode, - validator: InputValidators.checkAddress, - controller: _recipientController, - suffixIcon: RawMaterialButton( - shape: const CircleBorder(), - onPressed: () { - ClipboardUtils.pasteToClipboard(context, (String value) { - _recipientController.text = value; - setState(() {}); - }); - }, - child: const Icon( - Icons.content_paste, - color: AppColors.darkHintTextColor, - size: 15, - ), - ), - suffixIconConstraints: const BoxConstraints( - maxWidth: 45, - maxHeight: 20, - ), - hintText: 'Recipient Address', - ), - ), - ), - kVerticalSpacing, - Container( - margin: const EdgeInsets.only(right: 20), - child: Form( - key: _amountKey, - autovalidateMode: AutovalidateMode.onUserInteraction, - child: InputField( - onChanged: (String value) { - setState(() {}); - }, - inputFormatters: FormatUtils.getAmountTextInputFormatters( - _amountController.text, - ), - validator: (String? value) => InputValidators.correctValue( - value, - accountInfo.getBalance( - _selectedToken.tokenStandard, - ), - _selectedToken.decimals, - BigInt.zero, - ), - controller: _amountController, - suffixIcon: _getAmountSuffix(accountInfo), - hintText: 'Amount', - ), - ), - ), - kVerticalSpacing, - Center( - child: Container( - child: _getSendPaymentViewModel(accountInfo), - ), - ), - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - TransferToggleCardSizeButton( - onPressed: widget.onExpandClicked, - iconData: Icons.navigate_next, - ), - ], - ), - ], - ), - ); - } - - void _onSendPaymentPressed(SendPaymentBloc model) { - if (_recipientKey.currentState!.validate() && - _amountKey.currentState!.validate()) { - showDialogWithNoAndYesOptions( - context: context, - isBarrierDismissible: true, - title: 'Send', - description: 'Are you sure you want to transfer ' - '${_amountController.text} ${_selectedToken.symbol} to ' - '${ZenonAddressUtils.getLabel(_recipientController.text)} ?', - onYesButtonPressed: () => _sendPayment(model), - ); - } - } - - void _sendPayment(SendPaymentBloc model) { - _sendPaymentButtonKey.currentState?.animateForward(); - model.sendTransfer( - fromAddress: kSelectedAddress, - toAddress: _recipientController.text, - amount: _amountController.text.extractDecimals(_selectedToken.decimals), - token: _selectedToken, - ); - } - - Widget _getAmountSuffix(AccountInfo accountInfo) { - return Row( - mainAxisAlignment: MainAxisAlignment.end, - mainAxisSize: MainAxisSize.min, - children: [ - _getCoinDropdown(), - const SizedBox( - width: 5, - ), - AmountSuffixMaxWidget( - onPressed: () => _onMaxPressed(accountInfo), - context: context, - ), - const SizedBox( - width: 15, - ), - ], - ); - } - - Widget _getCoinDropdown() => CoinDropdown( - _tokensWithBalance, - _selectedToken, - (Token? value) { - if (_selectedToken != value) { - setState( - () { - _selectedToken = value!; - }, - ); - } - }, - ); - - void _onMaxPressed(AccountInfo accountInfo) { - final BigInt maxBalance = accountInfo.getBalance( - _selectedToken.tokenStandard, - ); - - if (_amountController.text.isEmpty || - _amountController.text.extractDecimals(_selectedToken.decimals) < - maxBalance) { - setState(() { - _amountController.text = - maxBalance.addDecimals(_selectedToken.decimals); - }); - } - } - - Widget _getSendPaymentViewModel(AccountInfo? accountInfo) { - return ViewModelBuilder.reactive( - fireOnViewModelReadyOnce: true, - onViewModelReady: (SendPaymentBloc model) { - model.stream.listen( - (AccountBlockTemplate? event) async { - if (event is AccountBlockTemplate) { - await _sendConfirmationNotification(); - setState(() { - _sendPaymentButtonKey.currentState?.animateReverse(); - _amountController = TextEditingController(); - _recipientController = TextEditingController(); - _amountKey = GlobalKey(); - _recipientKey = GlobalKey(); - }); - } - }, - onError: (error) async { - _sendPaymentButtonKey.currentState?.animateReverse(); - await _sendErrorNotification(error); - }, - ); - }, - builder: (_, SendPaymentBloc model, __) => SendPaymentButton( - onPressed: _hasBalance(accountInfo!) && _isInputValid(accountInfo) - ? () => _onSendPaymentPressed(model) - : null, - key: _sendPaymentButtonKey, - ), - viewModelBuilder: SendPaymentBloc.new, - ); - } - - Future _sendErrorNotification(error) async { - await NotificationUtils.sendNotificationError( - error, - "Couldn't send ${_amountController.text} ${_selectedToken.symbol} " - 'to ${_recipientController.text}', - ); - } - - Future _sendConfirmationNotification() async { - await sl.get().addNotification( - WalletNotification( - title: 'Sent ${_amountController.text} ${_selectedToken.symbol} ' - 'to ${ZenonAddressUtils.getLabel(_recipientController.text)}', - timestamp: DateTime.now().millisecondsSinceEpoch, - details: 'Sent ${_amountController.text} ${_selectedToken.symbol} ' - 'from ${ZenonAddressUtils.getLabel(kSelectedAddress!)} to ${ZenonAddressUtils.getLabel(_recipientController.text)}', - type: NotificationType.paymentSent, - ), - ); - } - - bool _hasBalance(AccountInfo accountInfo) => - accountInfo.getBalance( - _selectedToken.tokenStandard, - ) > - BigInt.zero; - - void _addTokensWithBalance(AccountInfo accountInfo) { - for (final BalanceInfoListItem balanceInfo in accountInfo.balanceInfoList!) { - if (balanceInfo.balance! > BigInt.zero && - !_tokensWithBalance.contains(balanceInfo.token)) { - _tokensWithBalance.add(balanceInfo.token); - } - } - } - - bool _isInputValid(AccountInfo accountInfo) => - InputValidators.checkAddress(_recipientController.text) == null && - InputValidators.correctValue( - _amountController.text, - accountInfo.getBalance( - _selectedToken.tokenStandard, - ), - _selectedToken.decimals, - BigInt.one, - canBeEqualToMin: true, - ) == - null; - - @override - void dispose() { - _recipientController.dispose(); - _amountController.dispose(); - super.dispose(); - } -} diff --git a/lib/widgets/modular_widgets/transfer_widgets/send/send_small.dart b/lib/widgets/modular_widgets/transfer_widgets/send/send_small.dart deleted file mode 100644 index ccaedbb9..00000000 --- a/lib/widgets/modular_widgets/transfer_widgets/send/send_small.dart +++ /dev/null @@ -1,54 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_vector_icons/flutter_vector_icons.dart'; -import 'package:zenon_syrius_wallet_flutter/utils/app_colors.dart'; -import 'package:zenon_syrius_wallet_flutter/widgets/widgets.dart'; - -class SendSmallCard extends StatefulWidget { - - const SendSmallCard( - this.onClicked, { - super.key, - }); - final VoidCallback onClicked; - - @override - State createState() => _SendSmallCardState(); -} - -class _SendSmallCardState extends State { - @override - Widget build(BuildContext context) { - return GestureDetector( - onTap: widget.onClicked, - child: Container( - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.primary, - borderRadius: BorderRadius.circular( - 15, - ), - ), - child: Container( - padding: const EdgeInsets.all( - 20, - ), - child: const Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon( - SimpleLineIcons.arrow_up_circle, - size: 60, - color: AppColors.darkHintTextColor, - ), - SizedBox( - height: 20, - ), - TransferIconLegend( - legendText: '● Send', - ), - ], - ), - ), - ), - ); - } -} diff --git a/lib/widgets/modular_widgets/transfer_widgets/transfer_widgets.dart b/lib/widgets/modular_widgets/transfer_widgets/transfer_widgets.dart index d9b1dcb2..c0443838 100644 --- a/lib/widgets/modular_widgets/transfer_widgets/transfer_widgets.dart +++ b/lib/widgets/modular_widgets/transfer_widgets/transfer_widgets.dart @@ -1,8 +1,2 @@ -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'; -export 'send/send_large.dart'; -export 'send/send_medium.dart'; -export 'send/send_small.dart'; +export '../../../rearchitecture/features/pending_transactions/view/pending_transactions_card.dart'; + 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 d41ffd09..fba53788 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 @@ -75,7 +75,7 @@ class _WalletConnectQrCardState extends State { scale: 0.3, padding: EdgeInsets.only(top: 10, bottom: 10), image: AssetImage( - 'assets/images/qr_code_child_image_znn.png',),),), + 'assets/images/qr_code_child_image_znn_cut.png',),),), errorCorrectLevel: QrErrorCorrectLevel.H, ), ), diff --git a/lib/widgets/reusable_widgets/buttons/buttons.dart b/lib/widgets/reusable_widgets/buttons/buttons.dart index e6b268e6..ebd7618d 100644 --- a/lib/widgets/reusable_widgets/buttons/buttons.dart +++ b/lib/widgets/reusable_widgets/buttons/buttons.dart @@ -1,9 +1,9 @@ +export 'copy_to_clipboard_button.dart'; export 'elevated_button.dart'; export 'loading_button.dart'; export 'material_icon_button.dart'; export 'onboarding_button.dart'; export 'outlined_button.dart'; -export 'send_payment_button.dart'; export 'settings_button.dart'; export 'stepper_button.dart'; export 'transfer_toggle_card_size_button.dart'; diff --git a/lib/widgets/reusable_widgets/buttons/copy_to_clipboard_button.dart b/lib/widgets/reusable_widgets/buttons/copy_to_clipboard_button.dart new file mode 100644 index 00000000..3fb15599 --- /dev/null +++ b/lib/widgets/reusable_widgets/buttons/copy_to_clipboard_button.dart @@ -0,0 +1,65 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:zenon_syrius_wallet_flutter/utils/app_colors.dart'; +import 'package:zenon_syrius_wallet_flutter/utils/clipboard_utils.dart'; + +class CopyToClipboardButton extends StatefulWidget { + const CopyToClipboardButton( + this._textToBeCopied, { + this.iconSize, + super.key, + }); + + final String _textToBeCopied; + final double? iconSize; + + @override + State createState() => _CopyToClipboardIcon(); +} + +class _CopyToClipboardIcon extends State { + bool _isCopied = false; + + @override + Widget build(BuildContext context) { + final double iconSize = widget.iconSize ?? 24; + + final Widget firstChild = Icon( + Icons.content_copy, + color: AppColors.znnColor, + size: iconSize, + ); + + final Widget secondChild = Icon( + Icons.check, + color: AppColors.znnColor, + size: iconSize, + ); + + return IconButton( + onPressed: () { + if (!_isCopied) { + Timer(const Duration(seconds: 3), () { + if (mounted) { + setState(() { + _isCopied = false; + }); + } + }); + setState(() { + _isCopied = true; + }); + } + ClipboardUtils.copyToClipboard(widget._textToBeCopied, context); + }, + icon: AnimatedCrossFade( + duration: const Duration(milliseconds: 100), + firstChild: firstChild, + secondChild: secondChild, + crossFadeState: + _isCopied ? CrossFadeState.showSecond : CrossFadeState.showFirst, + ), + ); + } +} diff --git a/lib/widgets/reusable_widgets/buttons/send_payment_button.dart b/lib/widgets/reusable_widgets/buttons/send_payment_button.dart deleted file mode 100644 index 470d25c9..00000000 --- a/lib/widgets/reusable_widgets/buttons/send_payment_button.dart +++ /dev/null @@ -1,30 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:zenon_syrius_wallet_flutter/utils/app_colors.dart'; -import 'package:zenon_syrius_wallet_flutter/widgets/widgets.dart'; - -class SendPaymentButton extends LoadingButton { - const SendPaymentButton({ - required super.onPressed, - required super.key, - String super.text = 'Send', - super.outlineColor, - super.minimumSize = const Size(100, 40), - }) : super( - paddingAroundChild: const EdgeInsets.symmetric( - horizontal: 10, - ), - ); - - factory SendPaymentButton.error({ - required VoidCallback? onPressed, - required Key key, - Size minimumSize = const Size(150, 40), - }) => - SendPaymentButton( - onPressed: onPressed, - text: 'Retry', - outlineColor: AppColors.errorColor, - minimumSize: minimumSize, - key: key, - ); -} diff --git a/lib/widgets/reusable_widgets/buttons/transfer_toggle_card_size_button.dart b/lib/widgets/reusable_widgets/buttons/transfer_toggle_card_size_button.dart index 4679da9b..c8c1fe8d 100644 --- a/lib/widgets/reusable_widgets/buttons/transfer_toggle_card_size_button.dart +++ b/lib/widgets/reusable_widgets/buttons/transfer_toggle_card_size_button.dart @@ -35,18 +35,13 @@ class TransferToggleCardSizeButton extends StatelessWidget { ); } - RawMaterialButton _getButton( + Widget _getButton( BuildContext context, VoidCallback? onButtonPressed, ) { - return RawMaterialButton( - constraints: const BoxConstraints.tightForFinite(), - padding: const EdgeInsets.all( - 20, - ), - shape: const CircleBorder(), + return IconButton( onPressed: onButtonPressed, - child: Icon( + icon: Icon( iconData, color: onPressed == null ? Colors.grey diff --git a/lib/widgets/reusable_widgets/custom_table.dart b/lib/widgets/reusable_widgets/custom_table.dart index 169ad303..0bc75e26 100644 --- a/lib/widgets/reusable_widgets/custom_table.dart +++ b/lib/widgets/reusable_widgets/custom_table.dart @@ -241,9 +241,8 @@ class CustomTableCell extends StatelessWidget { ), ), ), - CopyToClipboardIcon( + CopyToClipboardButton( address.toString(), - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, ), const SizedBox( width: 10, @@ -281,9 +280,8 @@ class CustomTableCell extends StatelessWidget { visible: showCopyToClipboardIcon, child: Row( children: [ - CopyToClipboardIcon( + CopyToClipboardButton( text, - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, ), const SizedBox( width: 10, @@ -323,9 +321,8 @@ class CustomTableCell extends StatelessWidget { visible: showCopyToClipboardIcon, child: Row( children: [ - CopyToClipboardIcon( + CopyToClipboardButton( address.toString(), - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, ), const SizedBox( width: 10, @@ -361,9 +358,8 @@ class CustomTableCell extends StatelessWidget { visible: showCopyToClipboardIcon, child: Row( children: [ - CopyToClipboardIcon( + CopyToClipboardButton( text, - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, ), const SizedBox( width: 10, diff --git a/lib/widgets/reusable_widgets/dialogs.dart b/lib/widgets/reusable_widgets/dialogs.dart index 445125e6..7049fcf5 100644 --- a/lib/widgets/reusable_widgets/dialogs.dart +++ b/lib/widgets/reusable_widgets/dialogs.dart @@ -54,16 +54,14 @@ Future showWarningDialog({ return isPressed; } -Future showDialogWithNoAndYesOptions({ +Future showDialogWithNoAndYesOptions({ required BuildContext context, required String title, - required VoidCallback onYesButtonPressed, - required isBarrierDismissible, - VoidCallback? onNoButtonPressed, + required bool isBarrierDismissible, Widget? content, String? description, }) => - showDialog( + showDialog( barrierDismissible: isBarrierDismissible, context: context, builder: (BuildContext context) => AlertDialog( @@ -71,8 +69,10 @@ Future showDialogWithNoAndYesOptions({ content: content ?? Text(description!), actions: [ TextButton( + style: TextButton.styleFrom( + backgroundColor: AppColors.znnColor, + ), onPressed: () { - onNoButtonPressed?.call(); Navigator.pop(context, false); }, child: Text( @@ -81,12 +81,10 @@ Future showDialogWithNoAndYesOptions({ ), ), TextButton( - style: Theme.of(context).textButtonTheme.style!.copyWith( - backgroundColor: WidgetStateColor.resolveWith( - (Set states) => AppColors.errorColor,), - ), + style: TextButton.styleFrom( + backgroundColor: AppColors.errorColor, + ), onPressed: () { - onYesButtonPressed.call(); Navigator.pop(context, true); }, child: Text( diff --git a/lib/widgets/reusable_widgets/dropdown/coin_dropdown.dart b/lib/widgets/reusable_widgets/dropdown/coin_dropdown.dart index db6fa1bc..03287884 100644 --- a/lib/widgets/reusable_widgets/dropdown/coin_dropdown.dart +++ b/lib/widgets/reusable_widgets/dropdown/coin_dropdown.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:flutter_vector_icons/flutter_vector_icons.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/constants.dart'; @@ -13,78 +12,60 @@ class CoinDropdown extends StatelessWidget { this._onChangeCallback, { super.key, }); + final Function(Token?) _onChangeCallback; final Token _selectedToken; final List _availableTokens; @override Widget build(BuildContext context) { - return Tooltip( - message: '${_selectedToken.tokenStandard}', - child: FocusableActionDetector( - mouseCursor: SystemMouseCursors.click, - child: DropdownButtonHideUnderline( - child: DropdownButton( - value: _selectedToken, - isDense: true, - selectedItemBuilder: (BuildContext context) { - return _availableTokens - .map( - (Token? e) => Container( - height: kAmountSuffixHeight, - alignment: Alignment.center, - decoration: BoxDecoration( - borderRadius: - BorderRadius.circular(kAmountSuffixRadius), - color: ColorUtils.getTokenColor(e!.tokenStandard), - ), - child: Padding( + return Container( + height: kAmountSuffixHeight, + alignment: Alignment.center, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(kAmountSuffixRadius), + color: ColorUtils.getTokenColor(_selectedToken.tokenStandard), + ), + child: Tooltip( + message: '${_selectedToken.tokenStandard}', + child: FocusableActionDetector( + mouseCursor: SystemMouseCursors.click, + child: DropdownButtonHideUnderline( + child: DropdownButton( + value: _selectedToken, + isDense: true, + selectedItemBuilder: (BuildContext context) { + return _availableTokens + .map( + (Token? e) => Padding( padding: const EdgeInsets.only(left: 8), child: Row( children: [ Text( - e.symbol, - style: Theme.of(context) - .textTheme - .titleMedium! - .copyWith( - color: Colors.white, - ), - ), - const Padding( - padding: EdgeInsets.symmetric( - horizontal: 5, - ), - child: Icon( - SimpleLineIcons.arrow_down, - size: 8, - color: Colors.white, - ), + e!.symbol, ), ], ), ), - ), - ) - .toList(); - }, - icon: Container(), - items: _availableTokens.map( - (Token? token) { - return DropdownMenuItem( - value: token, - child: Text( - token!.symbol, - style: Theme.of(context).textTheme.bodyMedium!.copyWith( - color: _selectedToken == token - ? AppColors.znnColor - : null, - ), - ), - ); + ) + .toList(); }, - ).toList(), - onChanged: _onChangeCallback, + items: _availableTokens.map( + (Token? token) { + return DropdownMenuItem( + value: token, + child: Text( + token!.symbol, + style: TextStyle( + color: + _selectedToken == token ? AppColors.znnColor : null, + ), + ), + ); + }, + ).toList(), + onChanged: _onChangeCallback, + ), ), ), ), diff --git a/lib/widgets/reusable_widgets/icons/copy_to_clipboard_icon.dart b/lib/widgets/reusable_widgets/icons/copy_to_clipboard_icon.dart deleted file mode 100644 index 58b0f7b5..00000000 --- a/lib/widgets/reusable_widgets/icons/copy_to_clipboard_icon.dart +++ /dev/null @@ -1,66 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; -import 'package:zenon_syrius_wallet_flutter/utils/app_colors.dart'; -import 'package:zenon_syrius_wallet_flutter/utils/clipboard_utils.dart'; - -class CopyToClipboardIcon extends StatefulWidget { - - const CopyToClipboardIcon( - this.textToBeCopied, { - this.iconColor = AppColors.znnColor, - this.hoverColor, - this.materialTapTargetSize = MaterialTapTargetSize.padded, - this.icon = Icons.content_copy, - this.padding = const EdgeInsets.all(8), - super.key, - }); - final String? textToBeCopied; - final Color iconColor; - final Color? hoverColor; - final MaterialTapTargetSize materialTapTargetSize; - final IconData icon; - final EdgeInsets padding; - - @override - State createState() => _CopyToClipboardIcon(); -} - -class _CopyToClipboardIcon extends State { - final double _iconSize = 15; - bool _isCopied = false; - - @override - Widget build(BuildContext context) { - return RawMaterialButton( - materialTapTargetSize: widget.materialTapTargetSize, - hoverColor: widget.hoverColor, - constraints: const BoxConstraints.tightForFinite(), - padding: widget.padding, - shape: const CircleBorder(), - onPressed: () { - if (!_isCopied) { - Timer(const Duration(seconds: 3), () { - if (mounted) { - setState(() { - _isCopied = false; - }); - } - }); - setState(() { - _isCopied = true; - }); - } - ClipboardUtils.copyToClipboard(widget.textToBeCopied!, context); - }, - child: AnimatedCrossFade( - duration: const Duration(milliseconds: 100), - firstChild: Icon(widget.icon, color: widget.iconColor, size: _iconSize), - secondChild: - Icon(Icons.check, color: AppColors.znnColor, size: _iconSize), - crossFadeState: - _isCopied ? CrossFadeState.showSecond : CrossFadeState.showFirst, - ), - ); - } -} diff --git a/lib/widgets/reusable_widgets/icons/icons.dart b/lib/widgets/reusable_widgets/icons/icons.dart index 2dc39289..069ecd53 100644 --- a/lib/widgets/reusable_widgets/icons/icons.dart +++ b/lib/widgets/reusable_widgets/icons/icons.dart @@ -1,3 +1,2 @@ -export 'copy_to_clipboard_icon.dart'; export 'link_icon.dart'; export 'standard_tooltip_icon.dart'; diff --git a/lib/widgets/reusable_widgets/infinite_scroll_table.dart b/lib/widgets/reusable_widgets/infinite_scroll_table.dart index 944f3753..3efb9f07 100644 --- a/lib/widgets/reusable_widgets/infinite_scroll_table.dart +++ b/lib/widgets/reusable_widgets/infinite_scroll_table.dart @@ -263,9 +263,8 @@ class InfiniteScrollTableCell extends StatelessWidget { ), ), ), - CopyToClipboardIcon( + CopyToClipboardButton( address.toString(), - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, ), const SizedBox( width: 10, @@ -308,9 +307,8 @@ class InfiniteScrollTableCell extends StatelessWidget { visible: showCopyToClipboardIcon, child: Row( children: [ - CopyToClipboardIcon( + CopyToClipboardButton( text, - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, ), const SizedBox( width: 10, @@ -355,9 +353,8 @@ class InfiniteScrollTableCell extends StatelessWidget { visible: showCopyToClipboardIcon, child: Row( children: [ - CopyToClipboardIcon( + CopyToClipboardButton( address.toString(), - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, ), const SizedBox( width: 10, @@ -398,9 +395,8 @@ class InfiniteScrollTableCell extends StatelessWidget { visible: showCopyToClipboardIcon, child: Row( children: [ - CopyToClipboardIcon( + CopyToClipboardButton( text, - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, ), const SizedBox( width: 10, diff --git a/lib/widgets/reusable_widgets/input_fields/amount_suffix_widgets.dart b/lib/widgets/reusable_widgets/input_fields/amount_suffix_widgets.dart index 53ad0a9c..00a07ac3 100644 --- a/lib/widgets/reusable_widgets/input_fields/amount_suffix_widgets.dart +++ b/lib/widgets/reusable_widgets/input_fields/amount_suffix_widgets.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:zenon_syrius_wallet_flutter/rearchitecture/utils/utils.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/constants.dart'; @@ -69,20 +70,18 @@ class AmountSuffixMaxWidget extends InkWell { onTap: onPressed, child: Container( height: kAmountSuffixHeight, - width: kAmountSuffixWidth, alignment: Alignment.center, - margin: const EdgeInsets.only( - right: kContentPadding, - ), decoration: BoxDecoration( borderRadius: BorderRadius.circular(kAmountSuffixRadius), border: Border.all( color: AppColors.maxAmountBorder, ), ), + padding: const EdgeInsetsDirectional.symmetric( + horizontal: 8, + ), child: Text( - 'MAX', - style: Theme.of(context).textTheme.titleMedium, + context.l10n.max.toUpperCase(), ), ), ); diff --git a/lib/widgets/reusable_widgets/layout_scaffold/standard_fluid_layout.dart b/lib/widgets/reusable_widgets/layout_scaffold/standard_fluid_layout.dart index 61ff3c27..68e37ea8 100644 --- a/lib/widgets/reusable_widgets/layout_scaffold/standard_fluid_layout.dart +++ b/lib/widgets/reusable_widgets/layout_scaffold/standard_fluid_layout.dart @@ -32,7 +32,8 @@ class StandardFluidLayout extends StatelessWidget { final int durationPerTile = totalDurationMs ~/ children.length; - final List tiles = List.generate( + final List tiles = + List.generate( children.length, (int index) { final int widgetAnimatorOffset = durationPerTile * (index + 1); @@ -81,6 +82,45 @@ class FluidCell { this.width, this.height, }); + + FluidCell.large({ + required Widget child, + required BuildContext context, + }) : this( + width: context.layout.value( + xl: kStaggeredNumOfColumns ~/ 1.2, + lg: kStaggeredNumOfColumns ~/ 1.2, + md: kStaggeredNumOfColumns ~/ 1.2, + sm: kStaggeredNumOfColumns, + xs: kStaggeredNumOfColumns, + ), + child: child, + ); + + FluidCell.medium({required Widget child, required BuildContext context}) + : this( + width: context.layout.value( + xl: kStaggeredNumOfColumns ~/ 2, + lg: kStaggeredNumOfColumns ~/ 2, + md: kStaggeredNumOfColumns ~/ 2, + sm: kStaggeredNumOfColumns, + xs: kStaggeredNumOfColumns, + ), + child: child, + ); + + FluidCell.small({required Widget child, required BuildContext context}) + : this( + width: context.layout.value( + xl: kStaggeredNumOfColumns ~/ 6, + lg: kStaggeredNumOfColumns ~/ 6, + md: kStaggeredNumOfColumns ~/ 6, + sm: kStaggeredNumOfColumns, + xs: kStaggeredNumOfColumns, + ), + child: child, + ); + final int? width; final double? height; final Widget child; diff --git a/lib/widgets/reusable_widgets/receive_qr_image.dart b/lib/widgets/reusable_widgets/receive_qr_image.dart index fb0ec2ef..927810f8 100644 --- a/lib/widgets/reusable_widgets/receive_qr_image.dart +++ b/lib/widgets/reusable_widgets/receive_qr_image.dart @@ -2,192 +2,176 @@ import 'dart:io'; import 'dart:typed_data'; 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:share_plus/share_plus.dart'; -import 'package:zenon_syrius_wallet_flutter/utils/color_utils.dart'; +import 'package:zenon_syrius_wallet_flutter/rearchitecture/utils/utils.dart'; import 'package:zenon_syrius_wallet_flutter/utils/utils.dart'; -import 'package:zenon_syrius_wallet_flutter/widgets/reusable_widgets/context_menu_region.dart'; import 'package:znn_sdk_dart/znn_sdk_dart.dart'; class ReceiveQrImage extends StatelessWidget { - const ReceiveQrImage({ - required this.data, - required this.size, - required this.tokenStandard, - required this.context, + required String data, + required double size, + required TokenStandard tokenStandard, super.key, - }); - final String data; - final int size; - final TokenStandard tokenStandard; - final BuildContext context; + }) : _data = data, + _size = size, + _tokenStandard = tokenStandard; + + final String _data; + final double _size; + final TokenStandard _tokenStandard; - static const PrettyQrDecorationImage decorationImage = PrettyQrDecorationImage( - scale: 0.3, - padding: EdgeInsets.only(top: 10, bottom: 10), - image: AssetImage('assets/images/qr_code_child_image_znn.png'), - ); + PrettyQrDecorationImage get _decorationImage => PrettyQrDecorationImage( + colorFilter: ColorFilter.mode( + ColorUtils.getTokenColor(_tokenStandard), + BlendMode.srcIn, + ), + image: const AssetImage( + 'assets/images/qr_code_child_image_znn_cut.png', + ), + fit: BoxFit.contain, + ); @override Widget build(BuildContext context) { - return ClipRRect( - borderRadius: BorderRadius.circular( - 15, - ), - child: Container( - height: size + 20, - width: size + 20, - padding: const EdgeInsets.all( - 10, + late Widget qrWidget; + PrettyQrDecoration? qrDecoration; + QrImage? qrImage; + + // onSurface is a color that ensures a strong contrast + final Color qrCodeColor = Theme.of(context).colorScheme.onSurface; + + try { + qrImage = QrImage( + QrCode.fromData( + data: _data, + errorCorrectLevel: QrErrorCorrectLevel.M, ), - color: Theme.of(context).colorScheme.surface, - child: ContextMenuRegion( - contextMenuBuilder: (BuildContext context, Offset offset) { - return AdaptiveTextSelectionToolbar( - anchors: TextSelectionToolbarAnchors( - primaryAnchor: offset, + ); + + qrDecoration = PrettyQrDecoration( + shape: PrettyQrSmoothSymbol( + color: qrCodeColor, + roundFactor: 0, + ), + image: _decorationImage, + ); + + qrWidget = PrettyQrView( + qrImage: qrImage, + decoration: qrDecoration, + ); + } on Exception catch (e) { + qrWidget = Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Lottie.asset( + 'assets/lottie/ic_anim_no_data.json', + width: 32, + height: 32, + ), + Tooltip( + message: e.toString(), + child: Text( + 'Failed to create QR code', + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.bodyMedium, + ), + ), + ], + ); + } + + return Column( + children: [ + Tooltip( + message: _data, + child: SizedBox.square( + dimension: _size, + child: qrWidget, + ), + ), + kVerticalGap16, + if (qrDecoration != null && qrImage != null) + Row( + children: [ + IconButton( + tooltip: context.l10n.shareQr, + onPressed: () => _shareQR( + qrDecoration: qrDecoration!, + qrImage: qrImage!, + ), + icon: const Icon( + Icons.share_rounded, + color: AppColors.znnColor, + ), + ), + kHorizontalGap16, + IconButton( + tooltip: context.l10n.saveQr, + onPressed: () => _saveQR( + qrDecoration: qrDecoration!, + qrImage: qrImage!, + ), + icon: const Icon( + Icons.save_alt_rounded, + color: AppColors.znnColor, ), - children: [ - Row( - children: [ - Expanded( - child: Directionality( - textDirection: TextDirection.rtl, - child: TextButton.icon( - icon: Icon( - MaterialCommunityIcons.share, - color: Theme.of(context).colorScheme.onSurface, - 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( - child: Directionality( - textDirection: TextDirection.rtl, - child: TextButton.icon( - icon: Icon( - Icons.save_alt, - color: Theme.of(context).colorScheme.onSurface, - 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: PrettyQrView.data( - data: data, - decoration: PrettyQrDecoration( - shape: PrettyQrSmoothSymbol( - roundFactor: 0, - color: ColorUtils.getTokenColor(tokenStandard), - ), - image: decorationImage,), - errorCorrectLevel: QrErrorCorrectLevel.M, - errorBuilder: (BuildContext context, Object error, StackTrace? stack) => Center( - child: Padding( - padding: const EdgeInsets.all(5), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Lottie.asset( - 'assets/lottie/ic_anim_no_data.json', - width: 32, - height: 32, - ), - Tooltip( - message: error.toString(), - child: Text( - 'Failed to create QR code', - textAlign: TextAlign.center, - style: Theme.of(context).textTheme.bodyMedium, - ),), - ], - ), - ), - ),),), - ), + ), + ], + ), + ], ); } - Future _getQRImageData() async { - final QrImage qr = QrImage(QrCode.fromData( - data: data, - errorCorrectLevel: QrErrorCorrectLevel.M, - ),); - - final ByteData? b = await qr.toImageAsBytes( - size: size, - decoration: PrettyQrDecoration( - shape: PrettyQrSmoothSymbol( - roundFactor: 0, - color: ColorUtils.getTokenColor(tokenStandard), - ), - image: decorationImage,),); + Future _getQRImageData({ + required PrettyQrDecoration qrDecoration, + required QrImage qrImage, + }) async { + final ByteData? b = await qrImage.toImageAsBytes( + size: _size.toInt(), + decoration: qrDecoration, + ); if (b != null) return b.buffer.asUint8List(); return null; } - Future _saveQR() async { - final Uint8List? imageData = await _getQRImageData(); + Future _saveQR({ + required PrettyQrDecoration qrDecoration, + required QrImage qrImage, + }) async { + final Uint8List? imageData = await _getQRImageData( + qrDecoration: qrDecoration, + qrImage: qrImage, + ); if (imageData != null) { final String fileName = DateTime.now().millisecondsSinceEpoch.toString(); final File imagePath = await File( - '${znnDefaultPaths.cache.path}${path.separator}$fileName.png',) - .create(); + '${znnDefaultPaths.cache.path}${path.separator}$fileName.png', + ).create(); await imagePath.writeAsBytes(imageData); await OpenFilex.open(imagePath.path); } } - Future _shareQR() async { - final Uint8List? imageData = await _getQRImageData(); + Future _shareQR({ + required PrettyQrDecoration qrDecoration, + required QrImage qrImage, + }) async { + final Uint8List? imageData = await _getQRImageData( + qrDecoration: qrDecoration, + qrImage: qrImage, + ); if (imageData != null) { final String fileName = DateTime.now().millisecondsSinceEpoch.toString(); final File imagePath = await File( - '${znnDefaultPaths.cache.path}${path.separator}$fileName.png',) - .create(); + '${znnDefaultPaths.cache.path}${path.separator}$fileName.png', + ).create(); 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 86961174..85fb4bed 100644 --- a/lib/widgets/reusable_widgets/settings_address.dart +++ b/lib/widgets/reusable_widgets/settings_address.dart @@ -95,9 +95,8 @@ class _SettingsAddressState extends State { const SizedBox( width: 5, ), - CopyToClipboardIcon( - widget.address, - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + CopyToClipboardButton( + widget.address!, ), const SizedBox( width: 5, diff --git a/lib/widgets/reusable_widgets/settings_node.dart b/lib/widgets/reusable_widgets/settings_node.dart index 96193710..408df9d9 100644 --- a/lib/widgets/reusable_widgets/settings_node.dart +++ b/lib/widgets/reusable_widgets/settings_node.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; import 'package:flutter_vector_icons/flutter_vector_icons.dart'; import 'package:hive/hive.dart'; @@ -11,7 +13,6 @@ import 'package:zenon_syrius_wallet_flutter/widgets/widgets.dart'; import 'package:znn_sdk_dart/znn_sdk_dart.dart'; class SettingsNode extends StatefulWidget { - const SettingsNode({ required this.node, required this.onNodePressed, @@ -19,6 +20,7 @@ class SettingsNode extends StatefulWidget { required this.currentNode, super.key, }); + final String node; final void Function(String?) onNodePressed; final VoidCallback onChangedOrDeletedNode; @@ -70,8 +72,7 @@ class _SettingsNodeState extends State { ), onTap: () => widget.onNodePressed(widget.node), child: Padding( - padding: - const EdgeInsets.symmetric(horizontal: 5, vertical: 5), + padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 5), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -82,7 +83,7 @@ class _SettingsNodeState extends State { .textTheme .bodyLarge! .color! - .withOpacity(0.7), + .withValues(alpha: 0.7), ), ), ], @@ -91,19 +92,20 @@ class _SettingsNodeState extends State { ), ), Visibility( - visible: widget.currentNode.contains(widget.node), - child: StandardTooltipIcon( - (connectedNodeChainIdentifier == getChainIdentifier()) - ? 'Client chain identifier: ${getChainIdentifier()}\n' - 'Node chain identifier: $connectedNodeChainIdentifier' - : 'Chain identifier mismatch\n' - 'Client chain identifier: ${getChainIdentifier()}\n' - 'Node chain identifier: $connectedNodeChainIdentifier', - MaterialCommunityIcons.identifier, - iconColor: - (getChainIdentifier() == connectedNodeChainIdentifier) - ? AppColors.znnColor - : AppColors.errorColor,),), + visible: widget.currentNode.contains(widget.node), + child: StandardTooltipIcon( + (connectedNodeChainIdentifier == getChainIdentifier()) + ? 'Client chain identifier: ${getChainIdentifier()}\n' + 'Node chain identifier: $connectedNodeChainIdentifier' + : 'Chain identifier mismatch\n' + 'Client chain identifier: ${getChainIdentifier()}\n' + 'Node chain identifier: $connectedNodeChainIdentifier', + MaterialCommunityIcons.identifier, + iconColor: (getChainIdentifier() == connectedNodeChainIdentifier) + ? AppColors.znnColor + : AppColors.errorColor, + ), + ), Visibility( visible: widget.node.contains('wss://'), child: const StandardTooltipIcon('Encrypted connection', Icons.lock), @@ -141,49 +143,54 @@ class _SettingsNodeState extends State { ), ), Visibility( - visible: !kDefaultNodes.contains(widget.node) && - !kDefaultCommunityNodes.contains(widget.node), - child: IconButton( - hoverColor: Colors.transparent, - padding: const EdgeInsets.all(4), - constraints: const BoxConstraints(), - iconSize: 15, - icon: const Icon( - Icons.edit, - color: AppColors.znnColor, - ), - onPressed: () { - setState(() { - _editable = true; - }); - }, - tooltip: 'Edit node', - ),), + visible: !kDefaultNodes.contains(widget.node) && + !kDefaultCommunityNodes.contains(widget.node), + child: IconButton( + hoverColor: Colors.transparent, + padding: const EdgeInsets.all(4), + constraints: const BoxConstraints(), + iconSize: 15, + 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), - constraints: const BoxConstraints(), - iconSize: 15, - icon: const Icon( - Icons.delete_forever, - color: AppColors.znnColor, - ), - onPressed: () => showDialogWithNoAndYesOptions( + visible: !kDefaultNodes.contains(widget.node) && + !kDefaultCommunityNodes.contains(widget.node), + child: IconButton( + hoverColor: Colors.transparent, + padding: const EdgeInsets.all(4), + constraints: const BoxConstraints(), + iconSize: 15, + icon: const Icon( + Icons.delete_forever, + color: AppColors.znnColor, + ), + onPressed: () async { + final bool? deleteConfirmed = await showDialogWithNoAndYesOptions( isBarrierDismissible: true, context: context, title: 'Node Management', description: 'Are you sure you want to delete ' '${widget.node} from the list of nodes? This action ' "can't be undone.", - onYesButtonPressed: () { - _deleteNodeFromDb(widget.node); - }, - ), - tooltip: 'Delete node', - ),), + ); + + if (deleteConfirmed ?? false) { + unawaited(_deleteNodeFromDb(widget.node)); + } + }, + tooltip: 'Delete node', + ), + ), ], ); } @@ -200,23 +207,24 @@ class _SettingsNodeState extends State { key: widget.key, autovalidateMode: AutovalidateMode.onUserInteraction, child: InputField( - controller: _nodeController, - hintText: 'Node address with port', - onSubmitted: (String value) { - if (_nodeController.text != widget.node && - _ifUserInputValid()) { - _onChangeButtonPressed(); - } - }, - onChanged: (String value) { - if (value.isNotEmpty) { - setState(() { - _nodeError = null; - }); - } - }, - validator: (String? value) => - InputValidators.node(value) ?? _nodeError,), + controller: _nodeController, + hintText: 'Node address with port', + onSubmitted: (String value) { + if (_nodeController.text != widget.node && + _ifUserInputValid()) { + _onChangeButtonPressed(); + } + }, + onChanged: (String value) { + if (value.isNotEmpty) { + setState(() { + _nodeError = null; + }); + } + }, + validator: (String? value) => + InputValidators.node(value) ?? _nodeError, + ), ), ), ), diff --git a/lib/widgets/tab_children_widgets/dashboard_tab_child.dart b/lib/widgets/tab_children_widgets/dashboard_tab_child.dart index a55fda75..a949b48e 100644 --- a/lib/widgets/tab_children_widgets/dashboard_tab_child.dart +++ b/lib/widgets/tab_children_widgets/dashboard_tab_child.dart @@ -3,16 +3,13 @@ import 'package:layout/layout.dart'; import 'package:zenon_syrius_wallet_flutter/blocs/blocs.dart'; import 'package:zenon_syrius_wallet_flutter/main.dart'; import 'package:zenon_syrius_wallet_flutter/rearchitecture/features/features.dart'; +import 'package:zenon_syrius_wallet_flutter/rearchitecture/utils/models/card/card.dart'; import 'package:zenon_syrius_wallet_flutter/widgets/widgets.dart'; class DashboardTabChild extends StatefulWidget { - const DashboardTabChild({super.key, this.changePage}); - final void Function( - Tabs, { - bool redirectWithSendContainerLarge, - bool redirectWithReceiveContainerLarge, - })? changePage; + + final void Function(Tabs)? changePage; @override State createState() => _DashboardTabChildState(); @@ -80,8 +77,8 @@ class _DashboardTabChildState extends State { width: defaultCellWidth * 2, ), FluidCell( - child: const LatestTransactions( - version: LatestTransactionsVersion.dashboard, + child: LatestTransactionsCard( + type: CardType.latestTransactionsDashboard, ), width: defaultCellWidth * 2, ), diff --git a/lib/widgets/tab_children_widgets/lock_tab_child.dart b/lib/widgets/tab_children_widgets/lock_tab_child.dart index 8ac249cd..a890b143 100644 --- a/lib/widgets/tab_children_widgets/lock_tab_child.dart +++ b/lib/widgets/tab_children_widgets/lock_tab_child.dart @@ -3,6 +3,7 @@ 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/rearchitecture/features/features.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'; @@ -168,6 +169,11 @@ class _LockTabChildState extends State { }); await InitUtils.initWalletAfterDecryption( Crypto.digest(utf8.encode(_passwordController.text)),); + sl.get().add( + MultipleBalanceFetch( + addresses: kDefaultAddressList.map((String? e) => e!).toList(), + ), + ); widget.afterInitCallback(); } else { await widget.afterUnlockCallback(_passwordController.text); diff --git a/lib/widgets/tab_children_widgets/notifications_tab_child.dart b/lib/widgets/tab_children_widgets/notifications_tab_child.dart index 0c9c7c11..2acb3918 100644 --- a/lib/widgets/tab_children_widgets/notifications_tab_child.dart +++ b/lib/widgets/tab_children_widgets/notifications_tab_child.dart @@ -79,7 +79,7 @@ class _NotificationsTabChildState extends State { style: Theme.of(context).textTheme.titleMedium, ), ), - CopyToClipboardIcon(notification.details), + CopyToClipboardButton(notification.details!), ], ), ), diff --git a/lib/widgets/tab_children_widgets/transfer_tab_child.dart b/lib/widgets/tab_children_widgets/transfer_tab_child.dart index 00e9cce3..0637a739 100644 --- a/lib/widgets/tab_children_widgets/transfer_tab_child.dart +++ b/lib/widgets/tab_children_widgets/transfer_tab_child.dart @@ -1,18 +1,13 @@ import 'package:flutter/material.dart'; import 'package:layout/layout.dart'; +import 'package:zenon_syrius_wallet_flutter/rearchitecture/features/features.dart'; +import 'package:zenon_syrius_wallet_flutter/rearchitecture/utils/models/card/card.dart'; import 'package:zenon_syrius_wallet_flutter/widgets/widgets.dart'; -enum DimensionCard { small, medium, large } - class TransferTabChild extends StatefulWidget { - TransferTabChild({ super.key, - this.sendCard = DimensionCard.medium, - this.receiveCard = DimensionCard.medium, }); - DimensionCard sendCard; - DimensionCard receiveCard; @override State createState() => _TransferTabChildState(); @@ -23,107 +18,39 @@ class _TransferTabChildState extends State { Widget build(BuildContext context) { return StandardFluidLayout( children: [ - _getSendCard(), - _getReceiveCard(), - const FluidCell( - child: LatestTransactions(), + FluidCell( + width: context.layout.value( + xl: kStaggeredNumOfColumns ~/ 2, + lg: kStaggeredNumOfColumns ~/ 2, + md: kStaggeredNumOfColumns ~/ 2, + sm: kStaggeredNumOfColumns, + xs: kStaggeredNumOfColumns, + ), + child: const SendCard(), + ), + FluidCell( + width: context.layout.value( + xl: kStaggeredNumOfColumns ~/ 2, + lg: kStaggeredNumOfColumns ~/ 2, + md: kStaggeredNumOfColumns ~/ 2, + sm: kStaggeredNumOfColumns, + xs: kStaggeredNumOfColumns, + ), + child: const ReceiveCard(), + ), + FluidCell( + child: LatestTransactionsCard( + type: CardType.latestTransactions, + ), width: kStaggeredNumOfColumns ~/ 2, height: kStaggeredNumOfColumns / 3, ), const FluidCell( - child: PendingTransactions(), + child: PendingTransactionsCard(), width: kStaggeredNumOfColumns ~/ 2, height: kStaggeredNumOfColumns / 3, ), ], ); } - - FluidCell _getReceiveCard() => widget.receiveCard == DimensionCard.medium - ? _getMediumFluidCell( - ReceiveMediumCard( - onExpandClicked: _onExpandReceiveCard, - ), - ) - : widget.receiveCard == DimensionCard.small - ? _getSmallFluidCell(ReceiveSmallCard(_onCollapse)) - : _getLargeFluidCell( - ReceiveLargeCard( - extendIcon: true, - onCollapseClicked: _onCollapse, - ), - ); - - FluidCell _getSendCard() => widget.sendCard == DimensionCard.medium - ? _getMediumFluidCell( - SendMediumCard(onExpandClicked: _onExpandSendCard), - ) - : widget.sendCard == DimensionCard.small - ? _getSmallFluidCell(SendSmallCard(_onCollapse)) - : _getLargeFluidCell( - SendLargeCard( - extendIcon: true, - onCollapsePressed: _onCollapse, - ), - ); - - FluidCell _getMediumFluidCell(Widget child) { - return FluidCell( - width: context.layout.value( - xl: kStaggeredNumOfColumns ~/ 2, - lg: kStaggeredNumOfColumns ~/ 2, - md: kStaggeredNumOfColumns ~/ 2, - sm: kStaggeredNumOfColumns, - xs: kStaggeredNumOfColumns, - ), - child: child, - ); - } - - FluidCell _getLargeFluidCell(Widget child) { - return FluidCell( - width: context.layout.value( - xl: kStaggeredNumOfColumns ~/ 1.2, - lg: kStaggeredNumOfColumns ~/ 1.2, - md: kStaggeredNumOfColumns ~/ 1.2, - sm: kStaggeredNumOfColumns, - xs: kStaggeredNumOfColumns, - ), - child: child, - ); - } - - FluidCell _getSmallFluidCell(Widget child) { - return FluidCell( - width: context.layout.value( - xl: kStaggeredNumOfColumns ~/ 6, - lg: kStaggeredNumOfColumns ~/ 6, - md: kStaggeredNumOfColumns ~/ 6, - sm: kStaggeredNumOfColumns, - xs: kStaggeredNumOfColumns, - ), - child: child, - ); - } - - void _onExpandSendCard() { - setState(() { - widget.sendCard = DimensionCard.large; - widget.receiveCard = DimensionCard.small; - }); - } - - void _onExpandReceiveCard() { - setState(() { - widget.sendCard = DimensionCard.small; - widget.receiveCard = DimensionCard.large; - }); - } - - void _onCollapse() { - setState(() { - widget.sendCard = DimensionCard.medium; - widget.receiveCard = DimensionCard.medium; - }); - } } diff --git a/macos/Runner/AppDelegate.swift b/macos/Runner/AppDelegate.swift index 5907b34f..7008a8c3 100644 --- a/macos/Runner/AppDelegate.swift +++ b/macos/Runner/AppDelegate.swift @@ -20,4 +20,8 @@ class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_: NSApplication) -> Bool { return true } + + override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + return true + } } \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index d4720f10..c41f473a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,15 +5,15 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: f256b0c0ba6c7577c15e2e4e114755640a875e885099367bf6e012b19314c834 + sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab" url: "https://pub.dev" source: hosted - version: "72.0.0" + version: "76.0.0" _macros: dependency: transitive description: dart source: sdk - version: "0.3.2" + version: "0.3.3" adaptive_number: dependency: transitive description: @@ -34,18 +34,18 @@ packages: dependency: transitive description: name: analyzer - sha256: b652861553cd3990d8ed361f7979dc6d7053a9ac8843fa73820ab68ce5410139 + sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e" url: "https://pub.dev" source: hosted - version: "6.7.0" + version: "6.11.0" app_links: dependency: "direct main" description: name: app_links - sha256: ad1a6d598e7e39b46a34f746f9a8b011ee147e4c275d407fa457e7a62f84dd99 + sha256: "433df2e61b10519407475d7f69e470789d23d593f28224c38ba1068597be7950" url: "https://pub.dev" source: hosted - version: "6.3.2" + version: "6.3.3" app_links_linux: dependency: transitive description: @@ -158,6 +158,14 @@ packages: url: "https://pub.dev" source: hosted version: "8.1.4" + bloc_concurrency: + dependency: "direct main" + description: + name: bloc_concurrency + sha256: "456b7a3616a7c1ceb975c14441b3f198bf57d81cb95b7c6de5cb0c60201afcd8" + url: "https://pub.dev" + source: hosted + version: "0.2.5" bloc_test: dependency: "direct dev" description: @@ -266,10 +274,10 @@ packages: dependency: transitive description: name: charcode - sha256: fb98c0f6d12c920a02ee2d998da788bca066ca5f148492b7085ee23372b12306 + sha256: fb0f1107cac15a5ea6ef0a6ef71a807b9e4267c713bb93e00e92d737cc8dbd8a url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.4.0" checked_yaml: dependency: transitive description: @@ -282,10 +290,10 @@ packages: dependency: "direct main" description: name: clipboard_watcher - sha256: a6c98d9a30c47d1c5655ed48c46f554b04545da018f28992fb2540d1d14d863a + sha256: "6a9f11ecfe983e9a211458c37c62a9c2830e6ae0db4afc65f1e9fecdd2c204a3" url: "https://pub.dev" source: hosted - version: "0.2.1" + version: "0.3.0" clock: dependency: transitive description: @@ -306,10 +314,10 @@ packages: dependency: "direct main" description: name: collection - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.19.0" convert: dependency: transitive description: @@ -322,10 +330,10 @@ packages: dependency: transitive description: name: coverage - sha256: "88b0fddbe4c92910fefc09cc0248f5e7f0cd23e450ded4c28f16ab8ee8f83268" + sha256: e3493833ea012784c740e341952298f1cc77f1f01b1bbc3eb4eecf6984fb7f43 url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.11.1" cross_file: dependency: transitive description: @@ -370,10 +378,10 @@ packages: dependency: "direct dev" description: name: dependency_validator - sha256: "81b5dc4cc34a1c05d2fa24aa8d658cb8f048ca23e63d5aaec420200190f1c4b0" + sha256: d27143159d8c2e83bf33e794e3e642c14fd888e2da8f512e6ad38bc854bbf3ec url: "https://pub.dev" source: hosted - version: "4.1.1" + version: "4.1.2" desktop_drop: dependency: "direct main" description: @@ -386,18 +394,18 @@ packages: dependency: "direct main" description: name: device_info_plus - sha256: c4af09051b4f0508f6c1dc0a5c085bf014d5c9a4a0678ce1799c2b4d716387a0 + sha256: "4fa68e53e26ab17b70ca39f072c285562cfc1589df5bb1e9295db90f6645f431" url: "https://pub.dev" source: hosted - version: "11.1.0" + version: "11.2.0" device_info_plus_platform_interface: dependency: transitive description: name: device_info_plus_platform_interface - sha256: "282d3cf731045a2feb66abfe61bbc40870ae50a3ed10a4d3d217556c35c8c2ba" + sha256: "0b04e02b30791224b31969eb1b50d723498f402971bff3630bca2ba839bd1ed2" url: "https://pub.dev" source: hosted - version: "7.0.1" + version: "7.0.2" diff_match_patch: dependency: transitive description: @@ -442,10 +450,10 @@ packages: dependency: "direct main" description: name: equatable - sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2 + sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7" url: "https://pub.dev" source: hosted - version: "2.0.5" + version: "2.0.7" event: dependency: transitive description: @@ -498,10 +506,10 @@ packages: dependency: transitive description: name: file_selector_android - sha256: ec439df07c4999faad319ce8ad9e971795c2f1d7132ad5a793b9370a863c6128 + sha256: "98ac58e878b05ea2fdb204e7f4fc4978d90406c9881874f901428e01d3b18fbc" url: "https://pub.dev" source: hosted - version: "0.5.1+10" + version: "0.5.1+12" file_selector_ios: dependency: transitive description: @@ -514,10 +522,10 @@ packages: dependency: transitive description: name: file_selector_linux - sha256: "712ce7fab537ba532c8febdb1a8f167b32441e74acd68c3ccb2e36dcb52c4ab2" + sha256: "54cbbd957e1156d29548c7d9b9ec0c0ebb6de0a90452198683a7d23aed617a33" url: "https://pub.dev" source: hosted - version: "0.9.3" + version: "0.9.3+2" file_selector_macos: dependency: transitive description: @@ -562,10 +570,10 @@ packages: dependency: "direct main" description: name: fl_chart - sha256: "94307bef3a324a0d329d3ab77b2f0c6e5ed739185ffc029ed28c0f9b019ea7ef" + sha256: "74959b99b92b9eebeed1a4049426fd67c4abc3c5a0f4d12e2877097d6a11ae08" url: "https://pub.dev" source: hosted - version: "0.69.0" + version: "0.69.2" flip_card: dependency: "direct main" description: @@ -620,10 +628,10 @@ packages: dependency: "direct main" description: name: flutter_svg - sha256: "7b4ca6cf3304575fe9c8ec64813c8d02ee41d2afe60bcfe0678bcb5375d596a2" + sha256: "54900a1a1243f3c4a5506d853a2b5c2dbc38d5f27e52a52618a8054401431123" url: "https://pub.dev" source: hosted - version: "2.0.10+1" + version: "2.0.16" flutter_test: dependency: "direct dev" description: flutter @@ -670,10 +678,10 @@ packages: dependency: "direct main" description: name: get_it - sha256: d85128a5dae4ea777324730dc65edd9c9f43155c109d5cc0a69cab74139fbac1 + sha256: f126a3e286b7f5b578bf436d5592968706c4c1de28a228b870ce375d9f743103 url: "https://pub.dev" source: hosted - version: "7.7.0" + version: "8.0.3" glob: dependency: transitive description: @@ -742,10 +750,10 @@ packages: dependency: transitive description: name: http_parser - sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + sha256: "76d306a1c3afb33fe82e2bbacad62a61f409b5634c915fceb0d799de1a913360" url: "https://pub.dev" source: hosted - version: "4.0.2" + version: "4.1.1" hydrated_bloc: dependency: "direct main" description: @@ -774,10 +782,10 @@ packages: dependency: transitive description: name: image_picker_android - sha256: "8faba09ba361d4b246dc0a17cb4289b3324c2b9f6db7b3d457ee69106a86bd32" + sha256: fa8141602fde3f7e2f81dbf043613eb44dfa325fa0bcf93c0f142c9f7a2c193e url: "https://pub.dev" source: hosted - version: "0.8.12+17" + version: "0.8.12+18" image_picker_for_web: dependency: transitive description: @@ -830,10 +838,10 @@ packages: dependency: "direct main" description: name: infinite_scroll_pagination - sha256: b68bce20752fcf36c7739e60de4175494f74e99e9a69b4dd2fe3a1dd07a7f16a + sha256: "4047eb8191e8b33573690922a9e995af64c3949dc87efc844f936b039ea279df" url: "https://pub.dev" source: hosted - version: "4.0.0" + version: "4.1.0" intl: dependency: "direct main" description: @@ -846,10 +854,10 @@ packages: dependency: transitive description: name: io - sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e" + sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.0.5" js: dependency: transitive description: @@ -878,10 +886,10 @@ packages: dependency: "direct dev" description: name: json_serializable - sha256: ea1432d167339ea9b5bb153f0571d0039607a873d6e04e0117af043f14a1fd4b + sha256: c2fcb3920cf2b6ae6845954186420fca40bc0a8abcc84903b7801f17d7050d7c url: "https://pub.dev" source: hosted - version: "6.8.0" + version: "6.9.0" launch_at_startup: dependency: "direct main" description: @@ -902,18 +910,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" + sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06" url: "https://pub.dev" source: hosted - version: "10.0.5" + version: "10.0.7" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" + sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379" url: "https://pub.dev" source: hosted - version: "3.0.5" + version: "3.0.8" leak_tracker_testing: dependency: transitive description: @@ -950,18 +958,18 @@ packages: dependency: "direct main" description: name: lottie - sha256: "7afc60865a2429d994144f7d66ced2ae4305fe35d82890b8766e3359872d872c" + sha256: "377d87b8dcef640c04717e93afb86a510f0e1117a399ab94dc4b3f39c85eaa87" url: "https://pub.dev" source: hosted - version: "3.1.3" + version: "3.3.0" macros: dependency: transitive description: name: macros - sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536" + sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656" url: "https://pub.dev" source: hosted - version: "0.1.2-main.4" + version: "0.1.3-main.0" marquee_widget: dependency: "direct main" description: @@ -1062,10 +1070,10 @@ packages: dependency: "direct main" description: name: open_filex - sha256: ba425ea49affd0a98a234aa9344b9ea5d4c4f7625a1377961eae9fe194c3d523 + sha256: dcb7bd3d32db8db5260253a62f1564c02c2c8df64bc0187cd213f65f827519bd url: "https://pub.dev" source: hosted - version: "4.5.0" + version: "4.6.0" overlay_support: dependency: "direct main" description: @@ -1078,26 +1086,26 @@ packages: dependency: transitive description: name: package_config - sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" + sha256: "92d4488434b520a62570293fbd33bb556c7d49230791c1b4bbd973baf6d2dc67" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" package_info_plus: dependency: "direct main" description: name: package_info_plus - sha256: df3eb3e0aed5c1107bb0fdb80a8e82e778114958b1c5ac5644fb1ac9cae8a998 + sha256: "70c421fe9d9cc1a9a7f3b05ae56befd469fe4f8daa3b484823141a55442d858d" url: "https://pub.dev" source: hosted - version: "8.1.0" + version: "8.1.2" package_info_plus_platform_interface: dependency: transitive description: name: package_info_plus_platform_interface - sha256: ac1f4a4847f1ade8e6a87d1f39f5d7c67490738642e2542f559ec38c37489a66 + sha256: a5ef9986efc7bf772f2696183a3992615baa76c1ffb1189318dd8803778fb05b url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" page_transition: dependency: "direct main" description: @@ -1126,34 +1134,34 @@ packages: dependency: transitive description: name: path_parsing - sha256: "45f7d6bba1128761de5540f39d5ca000ea8a1f22f06b76b61094a60a2997bd0e" + sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca" url: "https://pub.dev" source: hosted - version: "1.0.2" + version: "1.1.0" path_provider: dependency: "direct main" description: name: path_provider - sha256: fec0d61223fba3154d87759e3cc27fe2c8dc498f6386c6d6fc80d1afdd1bf378 + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.1.5" path_provider_android: dependency: transitive description: name: path_provider_android - sha256: c464428172cb986b758c6d1724c603097febb8fb855aa265aeecc9280c294d4a + sha256: "4adf4fd5423ec60a29506c76581bc05854c55e3a0b72d35bb28d661c9686edf2" url: "https://pub.dev" source: hosted - version: "2.2.12" + version: "2.2.15" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16 + sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.4.1" path_provider_linux: dependency: transitive description: @@ -1246,10 +1254,10 @@ packages: dependency: transitive description: name: pub_semver - sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" + sha256: "7b3cfbf654f3edd0c6298ecd5be782ce997ddf0e00531b9464b55245185bbbbd" url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.1.5" pubspec_parse: dependency: transitive description: @@ -1382,42 +1390,42 @@ packages: dependency: "direct main" description: name: share_plus - sha256: "3af2cda1752e5c24f2fc04b6083b40f013ffe84fb90472f30c6499a9213d5442" + sha256: "6327c3f233729374d0abaafd61f6846115b2a481b4feddd8534211dc10659400" url: "https://pub.dev" source: hosted - version: "10.1.1" + version: "10.1.3" share_plus_platform_interface: dependency: transitive description: name: share_plus_platform_interface - sha256: c57c0bbfec7142e3a0f55633be504b796af72e60e3c791b44d5a017b985f7a48 + sha256: cc012a23fc2d479854e6c80150696c4a5f5bb62cb89af4de1c505cf78d0a5d0b url: "https://pub.dev" source: hosted - version: "5.0.1" + version: "5.0.2" shared_preferences: dependency: transitive description: name: shared_preferences - sha256: "746e5369a43170c25816cc472ee016d3a66bc13fcf430c0bc41ad7b4b2922051" + sha256: "95f9997ca1fb9799d494d0cb2a780fd7be075818d59f00c43832ed112b158a82" url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.3.3" shared_preferences_android: dependency: transitive description: name: shared_preferences_android - sha256: "3b9febd815c9ca29c9e3520d50ec32f49157711e143b7a4ca039eb87e8ade5ab" + sha256: "02a7d8a9ef346c9af715811b01fbd8e27845ad2c41148eefd31321471b41863d" url: "https://pub.dev" source: hosted - version: "2.3.3" + version: "2.4.0" shared_preferences_foundation: dependency: transitive description: name: shared_preferences_foundation - sha256: "07e050c7cd39bad516f8d64c455f04508d09df104be326d8c02551590a0d513d" + sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" url: "https://pub.dev" source: hosted - version: "2.5.3" + version: "2.5.4" shared_preferences_linux: dependency: transitive description: @@ -1454,10 +1462,10 @@ packages: dependency: transitive description: name: shelf - sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4 + sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 url: "https://pub.dev" source: hosted - version: "1.4.1" + version: "1.4.2" shelf_packages_handler: dependency: transitive description: @@ -1478,10 +1486,10 @@ packages: dependency: transitive description: name: shelf_web_socket - sha256: "073c147238594ecd0d193f3456a5fe91c4b0abbcc68bf5cd95b36c4e194ac611" + sha256: cc36c297b52866d203dbf9332263c94becc2fe0ceaa9681d07b6ef9807023b67 url: "https://pub.dev" source: hosted - version: "2.0.0" + version: "2.0.1" shell_executor: dependency: transitive description: @@ -1502,7 +1510,7 @@ packages: dependency: transitive description: flutter source: sdk - version: "0.0.99" + version: "0.0.0" sliver_tools: dependency: transitive description: @@ -1539,10 +1547,10 @@ packages: dependency: transitive description: name: source_maps - sha256: "708b3f6b97248e5781f493b765c3337db11c5d2c81c3094f10904bfa8004c703" + sha256: "190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812" url: "https://pub.dev" source: hosted - version: "0.10.12" + version: "0.10.13" source_span: dependency: transitive description: @@ -1555,26 +1563,26 @@ packages: dependency: transitive description: name: stack_trace - sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377" url: "https://pub.dev" source: hosted - version: "1.11.1" + version: "1.12.0" stacked: dependency: "direct main" description: name: stacked - sha256: ed19ecdc2dcc682b9be9c7e34646e603c0f770437a914b15c7d2d13391c92a09 + sha256: fe77da8b5dae6488a0caa0feea59c4f79a0fb11cd88a211f87f653411a4c142b url: "https://pub.dev" source: hosted - version: "3.4.3" + version: "3.4.4" stacked_shared: dependency: transitive description: name: stacked_shared - sha256: "26e11dcfe23df81d565d0180eb5bcf4742efed066ba3328623b458f21a82b346" + sha256: "3d69b34d87422b78a7e5123681d3f4bcdd79757170454933f68795c54812d003" url: "https://pub.dev" source: hosted - version: "1.4.1" + version: "1.4.2" stream_channel: dependency: transitive description: @@ -1584,7 +1592,7 @@ packages: source: hosted version: "2.1.2" stream_transform: - dependency: transitive + dependency: "direct main" description: name: stream_transform sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f" @@ -1595,10 +1603,10 @@ packages: dependency: transitive description: name: string_scanner - sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.3.0" synchronized: dependency: transitive description: @@ -1619,42 +1627,42 @@ packages: dependency: transitive description: name: test - sha256: "7ee44229615f8f642b68120165ae4c2a75fe77ae2065b1e55ae4711f6cf0899e" + sha256: "713a8789d62f3233c46b4a90b174737b2c04cb6ae4500f2aa8b1be8f03f5e67f" url: "https://pub.dev" source: hosted - version: "1.25.7" + version: "1.25.8" test_api: dependency: transitive description: name: test_api - sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" + sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" url: "https://pub.dev" source: hosted - version: "0.7.2" + version: "0.7.3" test_core: dependency: transitive description: name: test_core - sha256: "55ea5a652e38a1dfb32943a7973f3681a60f872f8c3a05a14664ad54ef9c6696" + sha256: "12391302411737c176b0b5d6491f466b0dd56d4763e347b6714efbaa74d7953d" url: "https://pub.dev" source: hosted - version: "0.6.4" + version: "0.6.5" timing: dependency: transitive description: name: timing - sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32" + sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe" url: "https://pub.dev" source: hosted - version: "1.0.1" + version: "1.0.2" tray_manager: dependency: "direct main" description: name: tray_manager - sha256: bdc3ac6c36f3d12d871459e4a9822705ce5a1165a17fa837103bc842719bf3f7 + sha256: f231031c5c0eb4ad514e18ddaab27a912ddbe50335c594bc28fb0f9972ab6a84 url: "https://pub.dev" source: hosted - version: "0.2.4" + version: "0.3.1" typed_data: dependency: transitive description: @@ -1683,34 +1691,34 @@ packages: dependency: transitive description: name: url_launcher_android - sha256: "0dea215895a4d254401730ca0ba8204b29109a34a99fb06ae559a2b60988d2de" + sha256: "6fc2f56536ee873eeb867ad176ae15f304ccccc357848b351f6f0d8d4a40d193" url: "https://pub.dev" source: hosted - version: "6.3.13" + version: "6.3.14" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: e43b677296fadce447e987a2f519dcf5f6d1e527dc35d01ffab4fff5b8a7063e + sha256: "16a513b6c12bb419304e72ea0ae2ab4fed569920d1c7cb850263fe3acc824626" url: "https://pub.dev" source: hosted - version: "6.3.1" + version: "6.3.2" url_launcher_linux: dependency: transitive description: name: url_launcher_linux - sha256: e2b9622b4007f97f504cd64c0128309dfb978ae66adbe944125ed9e1750f06af + sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935" url: "https://pub.dev" source: hosted - version: "3.2.0" + version: "3.2.1" url_launcher_macos: dependency: transitive description: name: url_launcher_macos - sha256: "769549c999acdb42b8bcfa7c43d72bf79a382ca7441ab18a808e101149daf672" + sha256: "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2" url: "https://pub.dev" source: hosted - version: "3.2.1" + version: "3.2.2" url_launcher_platform_interface: dependency: transitive description: @@ -1755,26 +1763,26 @@ packages: dependency: transitive description: name: vector_graphics - sha256: "32c3c684e02f9bc0afb0ae0aa653337a2fe022e8ab064bcd7ffda27a74e288e3" + sha256: "27d5fefe86fb9aace4a9f8375b56b3c292b64d8c04510df230f849850d912cb7" url: "https://pub.dev" source: hosted - version: "1.1.11+1" + version: "1.1.15" vector_graphics_codec: dependency: transitive description: name: vector_graphics_codec - sha256: c86987475f162fadff579e7320c7ddda04cd2fdeffbe1129227a85d9ac9e03da + sha256: "2430b973a4ca3c4dbc9999b62b8c719a160100dcbae5c819bae0cacce32c9cdb" url: "https://pub.dev" source: hosted - version: "1.1.11+1" + version: "1.1.12" vector_graphics_compiler: dependency: transitive description: name: vector_graphics_compiler - sha256: "12faff3f73b1741a36ca7e31b292ddeb629af819ca9efe9953b70bd63fc8cd81" + sha256: "1b4b9e706a10294258727674a340ae0d6e64a7231980f9f9a3d12e4b42407aad" url: "https://pub.dev" source: hosted - version: "1.1.11+1" + version: "1.1.16" vector_math: dependency: transitive description: @@ -1803,10 +1811,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" + sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b url: "https://pub.dev" source: hosted - version: "14.2.5" + version: "14.3.0" wakelock_plus: dependency: "direct main" description: @@ -1970,5 +1978,5 @@ packages: source: hosted version: "0.2.3" sdks: - dart: ">=3.5.0 <4.0.0" - flutter: ">=3.24.0" + dart: ">=3.6.0 <4.0.0" + flutter: ">=3.27.0" diff --git a/pubspec.yaml b/pubspec.yaml index 5b107d76..45ec5f5c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -53,11 +53,11 @@ dependencies: json_rpc_2: ^3.0.2 path: ^1.8.2 ffi: ^2.1.0 - get_it: ^7.2.0 + get_it: ^8.0.3 hex: ^0.2.0 local_notifier: ^0.1.5 - tray_manager: ^0.2.0 - open_filex: ^4.3.2 + tray_manager: ^0.3.1 + open_filex: ^4.6.0 launch_at_startup: ^0.3.1 logging: ^1.2.0 collection: ^1.17.0 @@ -67,7 +67,7 @@ dependencies: screen_capturer: ^0.2.3 zxing2: ^0.2.0 image: ^4.0.10 - clipboard_watcher: ^0.2.0 + clipboard_watcher: ^0.3.0 wallet_connect_uri_validator: ^0.1.0 big_decimal: ^0.5.0 ai_barcode_scanner: ^6.0.1 @@ -79,6 +79,8 @@ dependencies: equatable: ^2.0.5 hydrated_bloc: ^9.1.5 json_annotation: ^4.9.0 + bloc_concurrency: ^0.2.5 + stream_transform: ^2.1.0 win32: ^5.9.0 dev_dependencies: diff --git a/test/balance/cubit/balance_cubit_test.dart b/test/balance/cubit/balance_cubit_test.dart index 6a488223..754c1eb1 100644 --- a/test/balance/cubit/balance_cubit_test.dart +++ b/test/balance/cubit/balance_cubit_test.dart @@ -1,5 +1,3 @@ -// ignore_for_file: prefer_const_constructors - import 'package:bloc_test/bloc_test.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; @@ -75,7 +73,7 @@ void main() { group('fromJson/toJson', () { test('can (de)serialize initial state', () { - final BalanceState initialState = BalanceState(); + const BalanceState initialState = BalanceState(); final Map? serialized = balanceCubit.toJson( initialState, @@ -87,7 +85,7 @@ void main() { }); test('can (de)serialize loading state', () { - final BalanceState loadingState = BalanceState( + const BalanceState loadingState = BalanceState( status: TimerStatus.loading, ); @@ -159,7 +157,7 @@ void main() { }, act: (BalanceCubit cubit) => cubit.fetchDataPeriodically(), expect: () => [ - BalanceState(status: TimerStatus.loading), + const BalanceState(status: TimerStatus.loading), BalanceState( status: TimerStatus.failure, error: balanceException, @@ -172,7 +170,7 @@ void main() { build: () => balanceCubit, act: (BalanceCubit cubit) => cubit.fetchDataPeriodically(), expect: () => [ - BalanceState(status: TimerStatus.loading), + const BalanceState(status: TimerStatus.loading), BalanceState(status: TimerStatus.success, data: accountInfo, ), diff --git a/test/delegation/cubit/delegation_cubit_test.dart b/test/delegation/cubit/delegation_cubit_test.dart index 956837c8..930e4e60 100644 --- a/test/delegation/cubit/delegation_cubit_test.dart +++ b/test/delegation/cubit/delegation_cubit_test.dart @@ -1,5 +1,3 @@ -// ignore_for_file: prefer_const_constructors - import 'package:bloc_test/bloc_test.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; @@ -65,7 +63,7 @@ void main() { group('fromJson/toJson', () { test('can (de)serialize initial state', () { - final DelegationState initialState = DelegationState(); + const DelegationState initialState = DelegationState(); final Map? serialized = delegationCubit.toJson( initialState, @@ -77,7 +75,7 @@ void main() { }); test('can (de)serialize loading state', () { - final DelegationState loadingState = DelegationState( + const DelegationState loadingState = DelegationState( status: TimerStatus.loading, ); @@ -148,7 +146,7 @@ void main() { build: () => delegationCubit, act: (DelegationCubit cubit) => cubit.fetchDataPeriodically(), expect: () => [ - DelegationState(status: TimerStatus.loading), + const DelegationState(status: TimerStatus.loading), DelegationState( status: TimerStatus.failure, error: delegationException, @@ -162,7 +160,7 @@ void main() { build: () => delegationCubit, act: (DelegationCubit cubit) => cubit.fetchDataPeriodically(), expect: () => [ - DelegationState(status: TimerStatus.loading), + const DelegationState(status: TimerStatus.loading), DelegationState(status: TimerStatus.success, data: delegationInfo), ], ); diff --git a/test/dual_coin_stats/cubit/dual_coin_stats_cubit_test.dart b/test/dual_coin_stats/cubit/dual_coin_stats_cubit_test.dart index 988bf575..a81474ac 100644 --- a/test/dual_coin_stats/cubit/dual_coin_stats_cubit_test.dart +++ b/test/dual_coin_stats/cubit/dual_coin_stats_cubit_test.dart @@ -1,5 +1,3 @@ -// ignore_for_file: prefer_const_constructors - import 'package:bloc_test/bloc_test.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; @@ -35,14 +33,14 @@ void main() { late MockWsClient mockWsClient; late MockTokenApi mockTokenApi; late DualCoinStatsCubit dualCoinStatsCubit; - late CubitFailureException exception; + late FailureException exception; setUp(() async { mockZenon = MockZenon(); mockEmbedded = MockEmbedded(); mockTokenApi = MockTokenApi(); mockWsClient = MockWsClient(); - exception = CubitFailureException(); + exception = FailureException(); dualCoinStatsCubit = DualCoinStatsCubit( zenon: mockZenon, ); @@ -67,7 +65,7 @@ void main() { }); group('fromJson/toJson', () { test('can (de)serialize initial state', () { - final DualCoinStatsState initialState = DualCoinStatsState(); + const DualCoinStatsState initialState = DualCoinStatsState(); final Map? serialized = dualCoinStatsCubit.toJson( initialState, @@ -79,7 +77,7 @@ void main() { }); test('can (de)serialize loading state', () { - final DualCoinStatsState loadingState = DualCoinStatsState( + const DualCoinStatsState loadingState = DualCoinStatsState( status: TimerStatus.loading, ); @@ -147,7 +145,7 @@ void main() { build: () => dualCoinStatsCubit, act: (DualCoinStatsCubit cubit) => cubit.fetchDataPeriodically(), expect: () => [ - DualCoinStatsState(status: TimerStatus.loading), + const DualCoinStatsState(status: TimerStatus.loading), DualCoinStatsState( status: TimerStatus.failure, error: exception, @@ -160,7 +158,7 @@ void main() { build: () => dualCoinStatsCubit, act: (DualCoinStatsCubit cubit) => cubit.fetchDataPeriodically(), expect: () => [ - DualCoinStatsState(status: TimerStatus.loading), + const DualCoinStatsState(status: TimerStatus.loading), DualCoinStatsState( status: TimerStatus.success, data: [kZnnCoin, kQsrCoin], diff --git a/test/latest_transactions/latest_transactions_bloc_test.dart b/test/latest_transactions/latest_transactions_bloc_test.dart new file mode 100644 index 00000000..6dfb909e --- /dev/null +++ b/test/latest_transactions/latest_transactions_bloc_test.dart @@ -0,0 +1,273 @@ +import 'package:bloc_test/bloc_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:zenon_syrius_wallet_flutter/rearchitecture/features/features.dart'; +import 'package:zenon_syrius_wallet_flutter/rearchitecture/utils/utils.dart'; +import 'package:zenon_syrius_wallet_flutter/utils/zts_utils.dart'; +import 'package:znn_sdk_dart/znn_sdk_dart.dart'; + +import '../helpers/hydrated_bloc.dart'; + +class MockZenon extends Mock implements Zenon {} + +class MockLedger extends Mock implements LedgerApi {} + +class FakeAddress extends Fake implements Address {} + +void main() { + initHydratedStorage(); + + setUpAll(() { + registerFallbackValue(FakeAddress()); + }); + + group('LatestTransactionsBloc', () { + const int kTestPageSize = 1; + late MockZenon mockZenon; + late MockLedger mockLedger; + late LatestTransactionsBloc latestTransactionsBloc; + late AccountBlock accountBlock; + late AccountBlockList accountBlockList; + late FailureException exception; + + setUp(() async { + final Map confirmationDetailJson = { + 'numConfirmations': 42, + 'momentumHeight': 12345, + 'momentumHash': emptyHash.toString(), + 'momentumTimestamp': 1625132800, + }; + + final Map accountBlockJson = { + 'descendantBlocks': [], + 'basePlasma': 1000, + 'usedPlasma': 500, + 'changesHash': emptyHash.toString(), + 'confirmationDetail': confirmationDetailJson, + 'version': 1, + 'chainIdentifier': 1, + 'blockType': 2, + 'hash': emptyHash.toString(), + 'previousHash': emptyHash.toString(), + 'height': 100, + 'momentumAcknowledged': { + 'hash': emptyHash.toString(), + 'height': 99, + }, + 'address': emptyAddress.toString(), + 'toAddress': emptyAddress.toString(), + 'amount': '1000000000', + 'tokenStandard': znnTokenStandard, + 'fromBlockHash': emptyHash.toString(), + 'data': null, + 'fusedPlasma': 0, + 'difficulty': 0, + 'nonce': '1', + 'publicKey': null, + 'signature': null, + 'token': kZnnCoin.toJson(), + 'pairedAccountBlock': null, + }; + + mockZenon = MockZenon(); + mockLedger = MockLedger(); + accountBlock = AccountBlock.fromJson(accountBlockJson); + accountBlockList = AccountBlockList( + count: kTestPageSize, + list: [accountBlock], + more: false, + ); + + exception = FailureException(); + when(() => mockZenon.ledger).thenReturn(mockLedger); + when( + () => mockLedger.getAccountBlocksByPage( + any(), + pageIndex: any(named: 'pageIndex'), + pageSize: any(named: 'pageSize'), + ), + ).thenAnswer((_) async => accountBlockList); + + latestTransactionsBloc = LatestTransactionsBloc( + pageSize: 1, + zenon: mockZenon, + ); + }); + + tearDown(() { + latestTransactionsBloc.close(); + }); + + test('initial state is correct', () { + expect( + latestTransactionsBloc.state.status, + InfiniteListStatus.initial, + ); + }); + + group('fromJson/toJson', () { + test('can (de)serialize initial state', () { + const InfiniteListState initialState = + InfiniteListState.initial(); + + final Map? serialized = latestTransactionsBloc.toJson( + initialState, + ); + final InfiniteListState? deserialized = + latestTransactionsBloc.fromJson(serialized!); + + expect(deserialized, equals(initialState)); + }); + + test('can (de)serialize success state', () { + final InfiniteListState successState = + InfiniteListState( + status: InfiniteListStatus.success, + data: [accountBlock], + ); + + final Map? serialized = latestTransactionsBloc.toJson( + successState, + ); + final InfiniteListState? deserialized = + latestTransactionsBloc.fromJson( + serialized!, + ); + expect(deserialized, isA>()); + expect(deserialized!.status, equals(InfiniteListStatus.success)); + expect(deserialized.data, isA?>()); + }); + + test('can (de)serialize failure state', () { + final InfiniteListState failureState = + InfiniteListState( + status: InfiniteListStatus.failure, + error: exception, + ); + + final Map? serialized = latestTransactionsBloc.toJson( + failureState, + ); + final InfiniteListState? deserialized = + latestTransactionsBloc.fromJson( + serialized!, + ); + expect(deserialized, equals(failureState)); + }); + }); + + blocTest>( + 'emits [success] with data is successfully fetched', + build: () => latestTransactionsBloc, + act: (LatestTransactionsBloc cubit) => cubit.add( + InfiniteListRequested( + address: emptyAddress, + ), + ), + expect: () { + final List data = [accountBlock]; + + final bool hasReachedMax = data.length < kTestPageSize; + + return >[ + InfiniteListState( + status: InfiniteListStatus.success, + data: data, + hasReachedMax: hasReachedMax, + ), + ]; + }, + ); + + blocTest>( + 'emits [failure] on fetch failure', + setUp: () { + when( + () => mockLedger.getAccountBlocksByPage( + any(), + pageSize: kTestPageSize, + ), + ).thenThrow(exception); + }, + build: () => latestTransactionsBloc, + act: (LatestTransactionsBloc cubit) => cubit.add( + InfiniteListRequested( + address: emptyAddress, + ), + ), + expect: () => >[ + InfiniteListState( + status: InfiniteListStatus.failure, + error: exception, + ), + ], + ); + + blocTest>( + 'emits [initial, success] when refresh is requested', + build: () => latestTransactionsBloc, + act: (LatestTransactionsBloc cubit) => cubit.add( + InfiniteListRefreshRequested( + address: emptyAddress, + ), + ), + expect: () { + final List data = [accountBlock]; + + final bool hasReachedMax = data.length < kTestPageSize; + + return >[ + const InfiniteListState.initial(), + InfiniteListState( + status: InfiniteListStatus.success, + data: data, + hasReachedMax: hasReachedMax, + ), + ]; + }, + ); + + blocTest>( + 'emits [initial, success, success] when more transactions are requested', + build: () => latestTransactionsBloc, + act: (LatestTransactionsBloc bloc) async { + bloc.add( + InfiniteListRefreshRequested( + address: emptyAddress, + ), + ); + + // New events sent immediately one after the other will be dropped + await Future.delayed(const Duration(milliseconds: 200)); + + bloc.add( + InfiniteListMoreRequested( + address: emptyAddress, + ), + ); + }, + expect: () { + final List data = [accountBlock]; + + final bool hasReachedMax = data.length < kTestPageSize; + + return >[ + const InfiniteListState.initial(), + InfiniteListState( + status: InfiniteListStatus.success, + data: data, + hasReachedMax: hasReachedMax, + ), + InfiniteListState( + status: InfiniteListStatus.success, + data: [ + ...data, + ...data, + ], + hasReachedMax: hasReachedMax, + ), + ]; + }, + ); + }); +} diff --git a/test/multiple_balance/multiple_balance_bloc_test.dart b/test/multiple_balance/multiple_balance_bloc_test.dart new file mode 100644 index 00000000..7015c28a --- /dev/null +++ b/test/multiple_balance/multiple_balance_bloc_test.dart @@ -0,0 +1,181 @@ +import 'package:bloc_test/bloc_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:zenon_syrius_wallet_flutter/rearchitecture/features/features.dart'; +import 'package:zenon_syrius_wallet_flutter/rearchitecture/utils/exceptions/failure_exception.dart'; +import 'package:zenon_syrius_wallet_flutter/utils/zts_utils.dart'; +import 'package:znn_sdk_dart/znn_sdk_dart.dart'; + +import '../helpers/hydrated_bloc.dart'; + +class MockZenon extends Mock implements Zenon {} + +class MockWsClient extends Mock implements WsClient {} + +class MockLedger extends Mock implements LedgerApi {} + +class FakeAddress extends Fake implements Address {} + +void main() { + initHydratedStorage(); + + registerFallbackValue(FakeAddress()); + + group('TransferBalanceBloc', () { + late MockZenon mockZenon; + late MockLedger mockLedger; + late MockWsClient mockWsClient; + late MultipleBalanceBloc bloc; + late String testAddress; + late BalanceInfoListItem balanceInfoListItem; + late AccountInfo accountInfo; + late FailureException exception; + + setUp(() { + mockZenon = MockZenon(); + mockLedger = MockLedger(); + mockWsClient = MockWsClient(); + testAddress = emptyAddress.toString(); + exception = FailureException(); + + balanceInfoListItem = BalanceInfoListItem( + token: kZnnCoin, + balance: BigInt.from(5), + ); + + accountInfo = AccountInfo( + address: emptyAddress.toString(), + blockCount: 1, + balanceInfoList: [balanceInfoListItem], + ); + + when(() => mockZenon.wsClient).thenReturn(mockWsClient); + when(() => mockZenon.ledger).thenReturn(mockLedger); + when(() => mockLedger.getAccountInfoByAddress(any())) + .thenAnswer((_) async => accountInfo); + bloc = MultipleBalanceBloc( + zenon: mockZenon, + ); + }); + + tearDown(() { + bloc.close(); + }); + + test('failure equality', () { + final MultipleBalanceState failure = MultipleBalanceState( + status: MultipleBalanceStatus.failure, + error: exception, + ); + + expect( + failure, + MultipleBalanceState( + status: MultipleBalanceStatus.failure, + error: FailureException(), + ), + ); + }); + + test('initial state is correct', () { + expect(bloc.state.status, MultipleBalanceStatus.initial); + }); + + group('fromJson/toJson', () { + test('can (de)serialize initial state', () { + const MultipleBalanceState initialState = MultipleBalanceState(); + + final Map? serialized = bloc.toJson( + initialState, + ); + final MultipleBalanceState? deserialized = bloc.fromJson(serialized!); + + expect(deserialized, equals(initialState)); + }); + + test('can (de)serialize loading state', () { + const MultipleBalanceState loadingState = MultipleBalanceState( + status: MultipleBalanceStatus.loading, + ); + + final Map? serialized = bloc.toJson( + loadingState, + ); + final MultipleBalanceState? deserialized = bloc.fromJson( + serialized!, + ); + expect(deserialized, equals(loadingState)); + }); + + test('can (de)serialize success state', () { + final MultipleBalanceState successState = MultipleBalanceState( + status: MultipleBalanceStatus.success, + data: {testAddress: accountInfo}, + ); + + final Map? serialized = bloc.toJson( + successState, + ); + final MultipleBalanceState? deserialized = bloc.fromJson( + serialized!, + ); + expect(deserialized, isA()); + expect(deserialized!.status, equals(MultipleBalanceStatus.success)); + expect(deserialized.data, isA>()); + }); + + test('can (de)serialize failure state', () { + final MultipleBalanceState failureState = MultipleBalanceState( + status: MultipleBalanceStatus.failure, + error: exception, + ); + + final Map? serialized = bloc.toJson( + failureState, + ); + final MultipleBalanceState? deserialized = bloc.fromJson( + serialized!, + ); + expect(deserialized, equals(failureState)); + }); + }); + + blocTest( + 'emits [loading, success] with data on successful fetch', + build: () => bloc, + act: (MultipleBalanceBloc bloc) => bloc.add( + MultipleBalanceFetch( + addresses: [emptyAddress.toString()], + ), + ), + expect: () => [ + const MultipleBalanceState(status: MultipleBalanceStatus.loading), + MultipleBalanceState( + status: MultipleBalanceStatus.success, + data: {testAddress: accountInfo}, + ), + ], + ); + + blocTest( + 'emits [loading, failure] when fetching balances fails', + setUp: () { + when(() => mockLedger.getAccountInfoByAddress(any())) + .thenThrow(exception); + }, + build: () => bloc, + act: (MultipleBalanceBloc bloc) => bloc.add( + MultipleBalanceFetch( + addresses: [emptyAddress.toString()], + ), + ), + expect: () => [ + const MultipleBalanceState(status: MultipleBalanceStatus.loading), + MultipleBalanceState( + status: MultipleBalanceStatus.failure, + error: exception, + ), + ], + ); + }); +} diff --git a/test/node_sync_status/cubit/node_sync_status_cubit_test.dart b/test/node_sync_status/cubit/node_sync_status_cubit_test.dart index 471d8894..df4b977c 100644 --- a/test/node_sync_status/cubit/node_sync_status_cubit_test.dart +++ b/test/node_sync_status/cubit/node_sync_status_cubit_test.dart @@ -1,12 +1,10 @@ -// ignore_for_file: prefer_const_constructors - import 'package:bloc_test/bloc_test.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; import 'package:zenon_syrius_wallet_flutter/blocs/auto_receive_tx_worker.dart'; import 'package:zenon_syrius_wallet_flutter/rearchitecture/features/features.dart'; import 'package:zenon_syrius_wallet_flutter/rearchitecture/rearchitecture.dart'; -import 'package:zenon_syrius_wallet_flutter/rearchitecture/utils/exceptions/cubit_failure_exception.dart'; +import 'package:zenon_syrius_wallet_flutter/rearchitecture/utils/exceptions/failure_exception.dart'; import 'package:zenon_syrius_wallet_flutter/utils/utils.dart'; import 'package:znn_sdk_dart/znn_sdk_dart.dart'; @@ -28,7 +26,7 @@ void main() { late MockWsClient mockWsClient; late MockStatsApi mockStatsApi; late NodeSyncStatusCubit nodeSyncStatusCubit; - late CubitFailureException exception; + late FailureException exception; late SyncInfo syncInfo; late Pair syncPair; @@ -36,7 +34,7 @@ void main() { mockZenon = MockZenon(); mockWsClient = MockWsClient(); mockStatsApi = MockStatsApi(); - exception = CubitFailureException(); + exception = FailureException(); nodeSyncStatusCubit = NodeSyncStatusCubit( zenon: mockZenon, ); @@ -64,7 +62,7 @@ void main() { group('fromJson/toJson', () { test('can (de)serialize initial state', () { - final NodeSyncStatusState initialState = NodeSyncStatusState(); + const NodeSyncStatusState initialState = NodeSyncStatusState(); final Map? serialized = nodeSyncStatusCubit.toJson( initialState, @@ -76,7 +74,7 @@ void main() { }); test('can (de)serialize loading state', () { - final NodeSyncStatusState loadingState = NodeSyncStatusState( + const NodeSyncStatusState loadingState = NodeSyncStatusState( status: TimerStatus.loading, ); @@ -132,7 +130,7 @@ void main() { build: () => nodeSyncStatusCubit, act: (NodeSyncStatusCubit cubit) => cubit.fetchDataPeriodically(), expect: () => [ // Expected state changes - NodeSyncStatusState(status: TimerStatus.loading), + const NodeSyncStatusState(status: TimerStatus.loading), NodeSyncStatusState( status: TimerStatus.success, data: syncPair, @@ -149,7 +147,7 @@ void main() { build: () => nodeSyncStatusCubit, act: (NodeSyncStatusCubit cubit) => cubit.fetchDataPeriodically(), expect: () => [ - NodeSyncStatusState(status: TimerStatus.loading), + const NodeSyncStatusState(status: TimerStatus.loading), NodeSyncStatusState( status: TimerStatus.failure, error: exception, diff --git a/test/pending_transactions/pending_transactions_bloc_test.dart b/test/pending_transactions/pending_transactions_bloc_test.dart new file mode 100644 index 00000000..9aee1479 --- /dev/null +++ b/test/pending_transactions/pending_transactions_bloc_test.dart @@ -0,0 +1,273 @@ +import 'package:bloc_test/bloc_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:zenon_syrius_wallet_flutter/rearchitecture/features/pending_transactions/pending_transactions.dart'; +import 'package:zenon_syrius_wallet_flutter/rearchitecture/utils/utils.dart'; +import 'package:zenon_syrius_wallet_flutter/utils/zts_utils.dart'; +import 'package:znn_sdk_dart/znn_sdk_dart.dart'; + +import '../helpers/hydrated_bloc.dart'; + +class MockZenon extends Mock implements Zenon {} + +class MockLedger extends Mock implements LedgerApi {} + +class FakeAddress extends Fake implements Address {} + +void main() { + initHydratedStorage(); + + setUpAll(() { + registerFallbackValue(FakeAddress()); + }); + + group('PendingTransactionsBloc', () { + const int kTestPageSize = 1; + late MockZenon mockZenon; + late MockLedger mockLedger; + late PendingTransactionsBloc pendingTransactionsBloc; + late AccountBlock accountBlock; + late AccountBlockList accountBlockList; + late FailureException exception; + + setUp(() async { + final Map confirmationDetailJson = { + 'numConfirmations': 42, + 'momentumHeight': 12345, + 'momentumHash': emptyHash.toString(), + 'momentumTimestamp': 1625132800, + }; + + final Map accountBlockJson = { + 'descendantBlocks': [], + 'basePlasma': 1000, + 'usedPlasma': 500, + 'changesHash': emptyHash.toString(), + 'confirmationDetail': confirmationDetailJson, + 'version': 1, + 'chainIdentifier': 1, + 'blockType': 2, + 'hash': emptyHash.toString(), + 'previousHash': emptyHash.toString(), + 'height': 100, + 'momentumAcknowledged': { + 'hash': emptyHash.toString(), + 'height': 99, + }, + 'address': emptyAddress.toString(), + 'toAddress': emptyAddress.toString(), + 'amount': '1000000000', + 'tokenStandard': znnTokenStandard, + 'fromBlockHash': emptyHash.toString(), + 'data': null, + 'fusedPlasma': 0, + 'difficulty': 0, + 'nonce': '1', + 'publicKey': null, + 'signature': null, + 'token': kZnnCoin.toJson(), + 'pairedAccountBlock': null, + }; + + mockZenon = MockZenon(); + mockLedger = MockLedger(); + accountBlock = AccountBlock.fromJson(accountBlockJson); + accountBlockList = AccountBlockList( + count: 1, + list: [accountBlock], + more: false, + ); + + exception = FailureException(); + when(() => mockZenon.ledger).thenReturn(mockLedger); + when( + () => mockLedger.getUnreceivedBlocksByAddress( + any(), + pageIndex: any(named: 'pageIndex'), + pageSize: any(named: 'pageSize'), + ), + ).thenAnswer((_) async => accountBlockList); + + pendingTransactionsBloc = PendingTransactionsBloc( + pageSize: kTestPageSize, + zenon: mockZenon, + ); + }); + + tearDown(() { + pendingTransactionsBloc.close(); + }); + + test('initial state is correct', () { + expect( + pendingTransactionsBloc.state.status, + InfiniteListStatus.initial, + ); + }); + + group('fromJson/toJson', () { + test('can (de)serialize initial state', () { + const InfiniteListState initialState = + InfiniteListState.initial(); + + final Map? serialized = pendingTransactionsBloc.toJson( + initialState, + ); + final InfiniteListState? deserialized = + pendingTransactionsBloc.fromJson(serialized!); + + expect(deserialized, equals(initialState)); + }); + + test('can (de)serialize success state', () { + final InfiniteListState successState = + InfiniteListState( + status: InfiniteListStatus.success, + data: [accountBlock], + ); + + final Map? serialized = pendingTransactionsBloc.toJson( + successState, + ); + final InfiniteListState? deserialized = + pendingTransactionsBloc.fromJson( + serialized!, + ); + expect(deserialized, isA>()); + expect(deserialized!.status, equals(InfiniteListStatus.success)); + expect(deserialized.data, isA?>()); + }); + + test('can (de)serialize failure state', () { + final InfiniteListState failureState = + InfiniteListState( + status: InfiniteListStatus.failure, + error: exception, + ); + + final Map? serialized = pendingTransactionsBloc.toJson( + failureState, + ); + final InfiniteListState? deserialized = + pendingTransactionsBloc.fromJson( + serialized!, + ); + expect(deserialized, equals(failureState)); + }); + }); + + blocTest>( + 'emits [success] with data on successful fetch', + build: () => pendingTransactionsBloc, + act: (PendingTransactionsBloc bloc) => bloc.add( + InfiniteListRequested( + address: emptyAddress, + ), + ), + expect: () { + final List data = [accountBlock]; + + final bool hasReachedMax = data.length < kTestPageSize; + + return >[ + InfiniteListState( + status: InfiniteListStatus.success, + data: [accountBlock], + hasReachedMax: hasReachedMax, + ), + ]; + }, + ); + + blocTest>( + 'emits [failure] on fetch failure', + setUp: () { + when( + () => mockLedger.getUnreceivedBlocksByAddress( + any(), + pageSize: kTestPageSize, + ), + ).thenThrow(exception); + }, + build: () => pendingTransactionsBloc, + act: (PendingTransactionsBloc bloc) => bloc.add( + InfiniteListRequested( + address: emptyAddress, + ), + ), + expect: () => >[ + InfiniteListState( + status: InfiniteListStatus.failure, + error: exception, + ), + ], + ); + + blocTest>( + 'emits [initial, success] when refresh is requested', + build: () => pendingTransactionsBloc, + act: (PendingTransactionsBloc bloc) => bloc.add( + InfiniteListRefreshRequested( + address: emptyAddress, + ), + ), + expect: () { + final List data = [accountBlock]; + + final bool hasReachedMax = data.length < kTestPageSize; + + return >[ + const InfiniteListState.initial(), + InfiniteListState( + status: InfiniteListStatus.success, + data: data, + hasReachedMax: hasReachedMax, + ), + ]; + }, + ); + + blocTest>( + 'emits [initial, success, success] when more transactions are requested', + build: () => pendingTransactionsBloc, + act: (PendingTransactionsBloc bloc) async { + bloc.add( + InfiniteListRefreshRequested( + address: emptyAddress, + ), + ); + + // New events sent immediately one after the other will be dropped + await Future.delayed(const Duration(milliseconds: 200)); + + bloc.add( + InfiniteListMoreRequested( + address: emptyAddress, + ), + ); + }, + expect: () { + final List data = [accountBlock]; + + final bool hasReachedMax = data.length < kTestPageSize; + + return >[ + const InfiniteListState.initial(), + InfiniteListState( + status: InfiniteListStatus.success, + data: data, + hasReachedMax: hasReachedMax, + ), + InfiniteListState( + status: InfiniteListStatus.success, + data: [ + ...data, + ...data, + ], + hasReachedMax: hasReachedMax, + ), + ]; + }, + ); + }); +} diff --git a/test/pillars/cubit/pillars_cubit_test.dart b/test/pillars/cubit/pillars_cubit_test.dart index 0b32c468..0c6b0007 100644 --- a/test/pillars/cubit/pillars_cubit_test.dart +++ b/test/pillars/cubit/pillars_cubit_test.dart @@ -1,10 +1,9 @@ -// ignore_for_file: prefer_const_constructors import 'package:bloc_test/bloc_test.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; import 'package:zenon_syrius_wallet_flutter/rearchitecture/features/features.dart'; import 'package:zenon_syrius_wallet_flutter/rearchitecture/rearchitecture.dart'; -import 'package:zenon_syrius_wallet_flutter/rearchitecture/utils/exceptions/cubit_failure_exception.dart'; +import 'package:zenon_syrius_wallet_flutter/rearchitecture/utils/exceptions/failure_exception.dart'; import 'package:znn_sdk_dart/znn_sdk_dart.dart'; import '../../helpers/hydrated_bloc.dart'; @@ -30,7 +29,7 @@ void main() { late MockEmbedded mockEmbedded; late MockPillar mockPillar; late PillarsCubit pillarsCubit; - late CubitFailureException exception; + late FailureException exception; setUp(() async { mockZenon = MockZenon(); @@ -40,7 +39,7 @@ void main() { pillarsCubit = PillarsCubit( zenon: mockZenon, ); - exception = CubitFailureException(); + exception = FailureException(); when(() => mockZenon.wsClient).thenReturn(mockWsClient); when(() => mockWsClient.isClosed()).thenReturn(false); @@ -57,7 +56,7 @@ void main() { group('fromJson/toJson', () { test('can (de)serialize initial state', () { - final PillarsState initialState = PillarsState(); + const PillarsState initialState = PillarsState(); final Map? serialized = pillarsCubit.toJson( initialState, @@ -69,7 +68,7 @@ void main() { }); test('can (de)serialize loading state', () { - final PillarsState loadingState = PillarsState( + const PillarsState loadingState = PillarsState( status: TimerStatus.loading, ); @@ -83,7 +82,7 @@ void main() { }); test('can (de)serialize success state', () { - final PillarsState successState = PillarsState( + const PillarsState successState = PillarsState( status: TimerStatus.success, data: 100, ); @@ -127,8 +126,8 @@ void main() { }, act: (PillarsCubit cubit) => cubit.fetchDataPeriodically(), expect: () => [ - PillarsState(status: TimerStatus.loading), - PillarsState(status: TimerStatus.success, data: 100), + const PillarsState(status: TimerStatus.loading), + const PillarsState(status: TimerStatus.success, data: 100), ], verify: (_) { verify(() => mockPillar.getAll()).called(1); @@ -143,7 +142,7 @@ void main() { build: () => pillarsCubit, act: (PillarsCubit cubit) => cubit.fetchDataPeriodically(), expect: () => [ - PillarsState(status: TimerStatus.loading), + const PillarsState(status: TimerStatus.loading), PillarsState(status: TimerStatus.failure, error: exception), ], ); diff --git a/test/realtime_statistics/cubit/realtime_statistics_cubit_test.dart b/test/realtime_statistics/cubit/realtime_statistics_cubit_test.dart index b7f130fe..872e8b30 100644 --- a/test/realtime_statistics/cubit/realtime_statistics_cubit_test.dart +++ b/test/realtime_statistics/cubit/realtime_statistics_cubit_test.dart @@ -1,4 +1,3 @@ -// ignore_for_file: prefer_const_constructors import 'package:bloc_test/bloc_test.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; @@ -120,7 +119,7 @@ void main() { group('fromJson/toJson', () { test('can (de)serialize initial state', () { - final RealtimeStatisticsState initialState = RealtimeStatisticsState(); + const RealtimeStatisticsState initialState = RealtimeStatisticsState(); final Map? serialized = statsCubit.toJson( initialState, @@ -132,7 +131,7 @@ void main() { }); test('can (de)serialize loading state', () { - final RealtimeStatisticsState loadingState = RealtimeStatisticsState( + const RealtimeStatisticsState loadingState = RealtimeStatisticsState( status: TimerStatus.loading, ); @@ -197,7 +196,7 @@ void main() { build: () => statsCubit, act: (RealtimeStatisticsCubit cubit) => cubit.fetchDataPeriodically(), expect: () => [ - RealtimeStatisticsState(status: TimerStatus.loading), + const RealtimeStatisticsState(status: TimerStatus.loading), RealtimeStatisticsState( status: TimerStatus.success, data: [accountBlock], @@ -214,7 +213,7 @@ void main() { build: () => statsCubit, act: (RealtimeStatisticsCubit cubit) => cubit.fetchDataPeriodically(), expect: () => [ - RealtimeStatisticsState(status: TimerStatus.loading), + const RealtimeStatisticsState(status: TimerStatus.loading), RealtimeStatisticsState( status: TimerStatus.failure, error: statsException, diff --git a/test/send/bloc/send_transaction_bloc_test.dart b/test/send/bloc/send_transaction_bloc_test.dart new file mode 100644 index 00000000..99532f86 --- /dev/null +++ b/test/send/bloc/send_transaction_bloc_test.dart @@ -0,0 +1,229 @@ +import 'package:bloc_test/bloc_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:zenon_syrius_wallet_flutter/rearchitecture/features/send/send.dart'; +import 'package:zenon_syrius_wallet_flutter/rearchitecture/utils/exceptions/exceptions.dart'; +import 'package:zenon_syrius_wallet_flutter/utils/utils.dart'; +import 'package:znn_sdk_dart/znn_sdk_dart.dart'; + +import '../../helpers/hydrated_bloc.dart'; + +class MockZenonAddressUtils extends Mock implements ZenonAddressUtils {} + +class MockAccountBlockUtils extends Mock implements AccountBlockUtils {} + +class FakeAddress extends Fake implements Address {} + +void main() { + initHydratedStorage(); + + setUpAll(() { + final BigInt testAmount = BigInt.from(1000); + final TokenStandard tokenStandard = znnZts; + final AccountBlockTemplate accountBlockTemplate = + AccountBlockTemplate(blockType: 1); + registerFallbackValue(testAmount); + registerFallbackValue(accountBlockTemplate); + registerFallbackValue(tokenStandard); + registerFallbackValue(FakeAddress()); + }); + + late SendTransactionBloc sendTransactionBloc; + late AccountBlockTemplate testAccBlockTemplate; + late MockAccountBlockUtils mockAccountBlockUtils; + late MockZenonAddressUtils mockZenonAddressUtils; + late String testToAddress; + late String testFromAddress; + late int testAmount; + late List testData; + late SyriusException exception; + + setUp(() { + mockAccountBlockUtils = MockAccountBlockUtils(); + mockZenonAddressUtils = MockZenonAddressUtils(); + sendTransactionBloc = SendTransactionBloc( + accountBlockUtilsHelper: mockAccountBlockUtils, + zenonAddressUtils: mockZenonAddressUtils, + ); + testToAddress = emptyAddress.toString(); + testFromAddress = emptyAddress.toString(); + testAmount = 1000; + testData = [1, 2, 3]; + testAccBlockTemplate = AccountBlockTemplate(blockType: 1); + exception = FailureException(); + + when( + () => mockAccountBlockUtils.createAccountBlock( + any(), + any(), + address: any(named: 'address'), + waitForRequiredPlasma: any(named: 'waitForRequiredPlasma'), + ), + ).thenAnswer((_) async => testAccBlockTemplate); + + when(() => mockZenonAddressUtils.refreshBalance()).thenAnswer((_) {}); + }); + + tearDown(() { + sendTransactionBloc.close(); + }); + + group('fromJson/toJson', () { + test('can (de)serialize initial state', () { + const SendTransactionState initialState = SendTransactionState(); + + final Map? serialized = sendTransactionBloc.toJson( + initialState, + ); + final SendTransactionState? deserialized = + sendTransactionBloc.fromJson(serialized!); + + expect(deserialized, equals(initialState)); + }); + + test('can (de)serialize loading state', () { + const SendTransactionState loadingState = SendTransactionState( + status: SendTransactionStatus.loading, + ); + + final Map? serialized = sendTransactionBloc.toJson( + loadingState, + ); + final SendTransactionState? deserialized = sendTransactionBloc.fromJson( + serialized!, + ); + expect(deserialized, equals(loadingState)); + }); + + test('can (de)serialize success state', () { + final SendTransactionState successState = SendTransactionState( + status: SendTransactionStatus.success, + data: testAccBlockTemplate, + ); + + final Map? serialized = sendTransactionBloc.toJson( + successState, + ); + final SendTransactionState? deserialized = sendTransactionBloc.fromJson( + serialized!, + ); + expect(deserialized, isA()); + expect(deserialized!.status, equals(SendTransactionStatus.success)); + expect(deserialized.data, isA()); + }); + + test('can (de)serialize failure state', () { + final SendTransactionState failureState = SendTransactionState( + status: SendTransactionStatus.failure, + error: exception, + ); + final Map? serialized = sendTransactionBloc.toJson( + failureState, + ); + final SendTransactionState? deserialized = sendTransactionBloc.fromJson( + serialized!, + ); + expect(deserialized, equals(failureState)); + }); + }); + + group('SendTransactionBloc', () { + blocTest( + 'emits [loading, success] when SendTransfer is successful', + build: () => sendTransactionBloc, + act: (SendTransactionBloc bloc) => bloc.add( + SendTransactionInitiate( + toAddress: testToAddress, + fromAddress: testFromAddress, + token: kZnnCoin, + amount: BigInt.from(testAmount), + data: testData, + ), + ), + expect: () => [ + const SendTransactionState(status: SendTransactionStatus.loading), + SendTransactionState( + status: SendTransactionStatus.success, + data: testAccBlockTemplate, + ), + ], + ); + + blocTest( + 'emits [loading, failure] when SendTransfer fails', + setUp: () { + when( + () => mockAccountBlockUtils.createAccountBlock( + any(), + any(), + address: any(named: 'address'), + waitForRequiredPlasma: any(named: 'waitForRequiredPlasma'), + ), + ).thenThrow(exception); + }, + build: () => sendTransactionBloc, + act: (SendTransactionBloc bloc) => bloc.add( + SendTransactionInitiate( + toAddress: testToAddress, + fromAddress: testFromAddress, + token: kZnnCoin, + amount: BigInt.from(testAmount), + data: testData, + ), + ), + expect: () => [ + const SendTransactionState(status: SendTransactionStatus.loading), + SendTransactionState( + status: SendTransactionStatus.failure, + error: exception, + ), + ], + ); + + blocTest( + 'emits [loading, success] when SendTransferWithBlock is successful', + build: () => sendTransactionBloc, + act: (SendTransactionBloc bloc) => bloc.add( + SendTransactionInitiateFromBlock( + block: testAccBlockTemplate, + fromAddress: testFromAddress, + ), + ), + expect: () => [ + const SendTransactionState(status: SendTransactionStatus.loading), + SendTransactionState( + status: SendTransactionStatus.success, + data: testAccBlockTemplate, + ), + ], + ); + + blocTest( + 'emits [loading, failure] when SendTransferWithBlock fails', + setUp: () { + when( + () => mockAccountBlockUtils.createAccountBlock( + any(), + any(), + address: any(named: 'address'), + waitForRequiredPlasma: any(named: 'waitForRequiredPlasma'), + ), + ).thenThrow(exception); + }, + build: () => sendTransactionBloc, + act: (SendTransactionBloc bloc) => bloc.add( + SendTransactionInitiateFromBlock( + block: testAccBlockTemplate, + fromAddress: testFromAddress, + ), + ), + expect: () => [ + const SendTransactionState(status: SendTransactionStatus.loading), + SendTransactionState( + status: SendTransactionStatus.failure, + error: exception, + ), + ], + ); + }); +} diff --git a/test/sentinels/cubit/sentinels_cubit_test.dart b/test/sentinels/cubit/sentinels_cubit_test.dart index bf08132d..b1773c9d 100644 --- a/test/sentinels/cubit/sentinels_cubit_test.dart +++ b/test/sentinels/cubit/sentinels_cubit_test.dart @@ -1,4 +1,3 @@ -// ignore_for_file: prefer_const_constructors import 'package:bloc_test/bloc_test.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; @@ -28,7 +27,7 @@ void main() { late MockEmbedded mockEmbedded; late MockSentinel mockSentinel; late SentinelsCubit sentinelsCubit; - late CubitFailureException exception; + late FailureException exception; late SentinelInfo sentinelInfo; late SentinelInfoList sentinelInfoList; @@ -53,7 +52,7 @@ void main() { sentinelsCubit = SentinelsCubit( zenon: mockZenon, ); - exception = CubitFailureException(); + exception = FailureException(); when(() => mockZenon.wsClient).thenReturn(mockWsClient); when(() => mockWsClient.isClosed()).thenReturn(false); @@ -77,7 +76,7 @@ void main() { group('fromJson/toJson', () { test('can (de)serialize initial state', () { - final SentinelsState initialState = SentinelsState(); + const SentinelsState initialState = SentinelsState(); final Map? serialized = sentinelsCubit.toJson( initialState, @@ -89,7 +88,7 @@ void main() { }); test('can (de)serialize loading state', () { - final SentinelsState loadingState = SentinelsState( + const SentinelsState loadingState = SentinelsState( status: TimerStatus.loading, ); @@ -153,7 +152,7 @@ void main() { build: () => sentinelsCubit, act: (SentinelsCubit cubit) => cubit.fetchDataPeriodically(), expect: () => [ - SentinelsState(status: TimerStatus.loading), + const SentinelsState(status: TimerStatus.loading), SentinelsState( status: TimerStatus.failure, error: exception, @@ -166,7 +165,7 @@ void main() { build: () => sentinelsCubit, act: (SentinelsCubit cubit) => cubit.fetchDataPeriodically(), expect: () => [ - SentinelsState(status: TimerStatus.loading), + const SentinelsState(status: TimerStatus.loading), SentinelsState( status: TimerStatus.success, data: sentinelInfoList, diff --git a/test/staking/cubit/staking_cubit_test.dart b/test/staking/cubit/staking_cubit_test.dart index 9bfd0f54..9396647a 100644 --- a/test/staking/cubit/staking_cubit_test.dart +++ b/test/staking/cubit/staking_cubit_test.dart @@ -1,4 +1,3 @@ -// ignore_for_file: prefer_const_constructors import 'package:bloc_test/bloc_test.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; @@ -76,7 +75,7 @@ void main() { group('fromJson/toJson', () { test('can (de)serialize initial state', () { - final StakingState initialState = StakingState(); + const StakingState initialState = StakingState(); final Map? serialized = stakingCubit.toJson( initialState, @@ -88,7 +87,7 @@ void main() { }); test('can (de)serialize loading state', () { - final StakingState loadingState = StakingState( + const StakingState loadingState = StakingState( status: TimerStatus.loading, ); @@ -156,7 +155,7 @@ void main() { build: () => stakingCubit, act: (StakingCubit cubit) => cubit.fetchDataPeriodically(), expect: () => [ - StakingState(status: TimerStatus.loading), + const StakingState(status: TimerStatus.loading), StakingState( status: TimerStatus.failure, error: stakingException, @@ -169,7 +168,7 @@ void main() { build: () => stakingCubit, act: (StakingCubit cubit) => cubit.fetchDataPeriodically(), expect: () => [ - StakingState(status: TimerStatus.loading), + const StakingState(status: TimerStatus.loading), StakingState( status: TimerStatus.success, data: testStakeList, diff --git a/test/total_hourly_transactions/cubit/total_hourly_transactions_cubit_test.dart b/test/total_hourly_transactions/cubit/total_hourly_transactions_cubit_test.dart index fd913583..c383d17a 100644 --- a/test/total_hourly_transactions/cubit/total_hourly_transactions_cubit_test.dart +++ b/test/total_hourly_transactions/cubit/total_hourly_transactions_cubit_test.dart @@ -1,4 +1,3 @@ -// ignore_for_file: prefer_const_constructors import 'package:bloc_test/bloc_test.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; @@ -76,7 +75,7 @@ void main() { group('fromJson/toJson', () { test('can (de)serialize initial state', () { - final TotalHourlyTransactionsState initialState = + const TotalHourlyTransactionsState initialState = TotalHourlyTransactionsState(); final Map? serialized = transactionsCubit.toJson( @@ -90,7 +89,7 @@ void main() { }); test('can (de)serialize loading state', () { - final TotalHourlyTransactionsState loadingState = + const TotalHourlyTransactionsState loadingState = TotalHourlyTransactionsState( status: TimerStatus.loading, ); @@ -106,7 +105,7 @@ void main() { }); test('can (de)serialize success state', () { - final TotalHourlyTransactionsState successState = + const TotalHourlyTransactionsState successState = TotalHourlyTransactionsState( status: TimerStatus.success, data: 2, @@ -158,8 +157,8 @@ void main() { act: (TotalHourlyTransactionsCubit cubit) => cubit.fetchDataPeriodically(), expect: () => [ - TotalHourlyTransactionsState(status: TimerStatus.loading), - TotalHourlyTransactionsState( + const TotalHourlyTransactionsState(status: TimerStatus.loading), + const TotalHourlyTransactionsState( status: TimerStatus.success, data: 2, ), @@ -176,7 +175,7 @@ void main() { act: (TotalHourlyTransactionsCubit cubit) => cubit.fetchDataPeriodically(), expect: () => [ - TotalHourlyTransactionsState(status: TimerStatus.loading), + const TotalHourlyTransactionsState(status: TimerStatus.loading), TotalHourlyTransactionsState( status: TimerStatus.failure, error: fetchException, diff --git a/test/transfer/receive_transaction/receive_transaction_cubit_test.dart b/test/transfer/receive_transaction/receive_transaction_cubit_test.dart new file mode 100644 index 00000000..99bdb9c0 --- /dev/null +++ b/test/transfer/receive_transaction/receive_transaction_cubit_test.dart @@ -0,0 +1,154 @@ +import 'package:bloc_test/bloc_test.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:zenon_syrius_wallet_flutter/blocs/auto_receive_tx_worker.dart'; +import 'package:zenon_syrius_wallet_flutter/rearchitecture/transfer/receive_transaction/cubit/receive_transaction_cubit.dart'; +import 'package:zenon_syrius_wallet_flutter/rearchitecture/utils/exceptions/failure_exception.dart'; +import 'package:znn_sdk_dart/znn_sdk_dart.dart'; + +import '../../helpers/hydrated_bloc.dart'; + +class MockZenon extends Mock implements Zenon {} + +class MockAutoReceiveTxWorker extends Mock implements AutoReceiveTxWorker {} + +class MockContext extends Mock implements BuildContext {} + +class MockHash extends Fake implements Hash {} + +void main() { + initHydratedStorage(); + + registerFallbackValue(MockHash()); + + group('ReceiveTransactionCubit', () { + late MockAutoReceiveTxWorker mockAutoReceiveTxWorker; + late ReceiveTransactionCubit receiveTransactionCubit; + late AccountBlockTemplate testAccBlockTemplate; + late FailureException exception; + + setUp(() { + mockAutoReceiveTxWorker = MockAutoReceiveTxWorker(); + testAccBlockTemplate = AccountBlockTemplate(blockType: 1); + exception = FailureException(); + + receiveTransactionCubit = ReceiveTransactionCubit( + mockAutoReceiveTxWorker, + ); + + when(() => mockAutoReceiveTxWorker.autoReceiveTransactionHash(any())) + .thenAnswer((_) async => testAccBlockTemplate); + }); + + tearDown(() { + receiveTransactionCubit.close(); + }); + + test('initial state is correct', () { + expect( + receiveTransactionCubit.state.status, + ReceiveTransactionStatus.initial, + ); + }); + + group('fromJson/toJson', () { + test('can (de)serialize initial state', () { + const ReceiveTransactionState initialState = ReceiveTransactionState(); + + final Map? serialized = receiveTransactionCubit.toJson( + initialState, + ); + final ReceiveTransactionState? deserialized = + receiveTransactionCubit.fromJson(serialized!); + + expect(deserialized, equals(initialState)); + }); + + test('can (de)serialize loading state', () { + const ReceiveTransactionState loadingState = ReceiveTransactionState( + status: ReceiveTransactionStatus.loading, + ); + + final Map? serialized = receiveTransactionCubit.toJson( + loadingState, + ); + final ReceiveTransactionState? deserialized = + receiveTransactionCubit.fromJson( + serialized!, + ); + expect(deserialized, equals(loadingState)); + }); + + test('can (de)serialize success state', () { + final ReceiveTransactionState successState = ReceiveTransactionState( + status: ReceiveTransactionStatus.success, + data: testAccBlockTemplate, + ); + + final Map? serialized = receiveTransactionCubit.toJson( + successState, + ); + final ReceiveTransactionState? deserialized = + receiveTransactionCubit.fromJson( + serialized!, + ); + expect(deserialized, isA()); + expect(deserialized!.status, equals(ReceiveTransactionStatus.success)); + expect(deserialized.data, isA()); + }); + + test('can (de)serialize failure state', () { + final ReceiveTransactionState failureState = ReceiveTransactionState( + status: ReceiveTransactionStatus.failure, + error: exception, + ); + + final Map? serialized = receiveTransactionCubit.toJson( + failureState, + ); + final ReceiveTransactionState? deserialized = + receiveTransactionCubit.fromJson( + serialized!, + ); + expect(deserialized, equals(failureState)); + }); + }); + + blocTest( + 'emits [loading, success] on successful transaction receipt', + build: () => receiveTransactionCubit, + act: (ReceiveTransactionCubit cubit) => cubit.receiveTransaction( + emptyHash.toString(), + MockContext(), + ), + expect: () => [ + const ReceiveTransactionState(status: ReceiveTransactionStatus.loading), + ReceiveTransactionState( + status: ReceiveTransactionStatus.success, + data: testAccBlockTemplate, + ), + ], + ); + + blocTest( + 'emits [loading, failure] on transaction receipt failure', + setUp: () { + when(() => mockAutoReceiveTxWorker.autoReceiveTransactionHash(any())) + .thenThrow(exception); + }, + build: () => receiveTransactionCubit, + act: (ReceiveTransactionCubit cubit) => cubit.receiveTransaction( + emptyHash.toString(), + MockContext(), + ), + expect: () => [ + const ReceiveTransactionState(status: ReceiveTransactionStatus.loading), + ReceiveTransactionState( + status: ReceiveTransactionStatus.failure, + error: exception, + ), + ], + ); + }); +}