diff --git a/android/fastlane/metadata/android/en-US/changelogs/49.txt b/android/fastlane/metadata/android/en-US/changelogs/49.txt new file mode 100644 index 0000000000..490961990c --- /dev/null +++ b/android/fastlane/metadata/android/en-US/changelogs/49.txt @@ -0,0 +1,2 @@ +- Update Zendesk/Help link +- Optimized the TransactionStatuses GQL query to only run for items not captured by snapshots, reducing unnecessary network requests and improving performance. diff --git a/assets/config/dev.json b/assets/config/dev.json index 032eaba497..11eec40862 100644 --- a/assets/config/dev.json +++ b/assets/config/dev.json @@ -2,8 +2,8 @@ "defaultArweaveGatewayUrl": "https://arweave.net", "useTurboUpload": true, "useTurboPayment": true, - "defaultTurboUploadUrl": "https://upload.ardrive.dev", - "defaultTurboPaymentUrl": "https://payment.ardrive.dev", + "defaultTurboUploadUrl": "https://upload.ardrive.io", + "defaultTurboPaymentUrl": "https://payment.ardrive.io", "allowedDataItemSizeForTurbo": 500000, "enableQuickSyncAuthoring": true, "enableMultipleFileDownload": true, diff --git a/lib/app_shell.dart b/lib/app_shell.dart index ce37af97d9..813c167871 100644 --- a/lib/app_shell.dart +++ b/lib/app_shell.dart @@ -123,8 +123,8 @@ class AppShellState extends State { : scaffold, ), ); - return ScreenTypeLayout( - desktop: buildPage( + return ScreenTypeLayout.builder( + desktop: (context) => buildPage( Row( children: [ const AppSideBar(), @@ -142,7 +142,7 @@ class AppShellState extends State { ], ), ), - mobile: buildPage(widget.page), + mobile: (context) => buildPage(widget.page), ); }, ); diff --git a/lib/authentication/login/blocs/login_bloc.dart b/lib/authentication/login/blocs/login_bloc.dart index 8c3fd63984..851aa9f2e9 100644 --- a/lib/authentication/login/blocs/login_bloc.dart +++ b/lib/authentication/login/blocs/login_bloc.dart @@ -82,7 +82,7 @@ class LoginBloc extends Bloc { return wallet; } catch (e) { - debugPrint('Invalid wallet file: $e'); + logger.d('Invalid wallet file: $e'); return null; } diff --git a/lib/authentication/login/views/login_page.dart b/lib/authentication/login/views/login_page.dart index 58ce99981c..e8ddba0f73 100644 --- a/lib/authentication/login/views/login_page.dart +++ b/lib/authentication/login/views/login_page.dart @@ -73,8 +73,8 @@ class _LoginPageScaffoldState extends State { @override Widget build(BuildContext context) { - return ScreenTypeLayout( - desktop: Material( + return ScreenTypeLayout.builder( + desktop: (context) => Material( color: ArDriveTheme.of(context).themeData.backgroundColor, child: Row( children: [ @@ -95,7 +95,7 @@ class _LoginPageScaffoldState extends State { ], ), ), - mobile: Scaffold( + mobile: (context) => Scaffold( resizeToAvoidBottomInset: true, body: SingleChildScrollView( child: Padding( @@ -315,9 +315,9 @@ class _PromptWalletViewState extends State { mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ - ScreenTypeLayout( - desktop: const SizedBox.shrink(), - mobile: ArDriveImage( + ScreenTypeLayout.builder( + desktop: (context) => const SizedBox.shrink(), + mobile: (context) => ArDriveImage( image: AssetImage(Resources.images.brand.logo1), height: 50, ), @@ -515,7 +515,7 @@ class _LoginCard extends StatelessWidget { final deviceType = getDeviceType(MediaQuery.of(context).size); switch (deviceType) { - case DeviceScreenType.desktop: + case DeviceScreenType.desktop: if (constraints.maxWidth >= 512) { horizontalPadding = 72; } else { @@ -715,9 +715,9 @@ class _CreatePasswordViewState extends State { mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ - ScreenTypeLayout( - desktop: const SizedBox.shrink(), - mobile: ArDriveImage( + ScreenTypeLayout.builder( + desktop: (context) => const SizedBox.shrink(), + mobile: (context) => ArDriveImage( image: AssetImage(Resources.images.brand.logo1), height: 50, ), @@ -996,8 +996,8 @@ class OnBoardingViewState extends State { @override Widget build(BuildContext context) { - return ScreenTypeLayout( - desktop: Material( + return ScreenTypeLayout.builder( + desktop: (context) => Material( color: ArDriveTheme.of(context).themeData.colors.themeBgCanvas, child: Row( mainAxisAlignment: MainAxisAlignment.center, @@ -1027,7 +1027,7 @@ class OnBoardingViewState extends State { ], ), ), - mobile: Scaffold( + mobile: (context) => Scaffold( resizeToAvoidBottomInset: true, body: Container( color: ArDriveTheme.of(context).themeData.colors.themeBgCanvas, diff --git a/lib/blocs/create_manifest/create_manifest_cubit.dart b/lib/blocs/create_manifest/create_manifest_cubit.dart index ad1611c508..f515fc8c40 100644 --- a/lib/blocs/create_manifest/create_manifest_cubit.dart +++ b/lib/blocs/create_manifest/create_manifest_cubit.dart @@ -7,6 +7,7 @@ import 'package:ardrive/entities/string_types.dart'; import 'package:ardrive/models/models.dart'; import 'package:ardrive/services/services.dart'; import 'package:ardrive/utils/ar_cost_to_usd.dart'; +import 'package:ardrive/utils/logger/logger.dart'; import 'package:arweave/arweave.dart'; import 'package:arweave/utils.dart'; import 'package:collection/collection.dart'; @@ -336,7 +337,7 @@ class CreateManifestCubit extends Cubit { emit(CreateManifestFailure()); super.onError(error, stackTrace); - print('Failed to create manifest: $error $stackTrace'); + logger.i('Failed to create manifest: $error $stackTrace'); } } diff --git a/lib/blocs/create_snapshot/create_snapshot_cubit.dart b/lib/blocs/create_snapshot/create_snapshot_cubit.dart index 4f2f8376c3..fe2369397a 100644 --- a/lib/blocs/create_snapshot/create_snapshot_cubit.dart +++ b/lib/blocs/create_snapshot/create_snapshot_cubit.dart @@ -12,6 +12,7 @@ import 'package:ardrive/services/arweave/arweave.dart'; import 'package:ardrive/services/pst/pst.dart'; import 'package:ardrive/utils/ar_cost_to_usd.dart'; import 'package:ardrive/utils/html/html_util.dart'; +import 'package:ardrive/utils/logger/logger.dart'; import 'package:ardrive/utils/metadata_cache.dart'; import 'package:ardrive/utils/snapshots/height_range.dart'; import 'package:ardrive/utils/snapshots/range.dart'; @@ -139,7 +140,7 @@ class CreateSnapshotCubit extends Cubit { } // ignore: avoid_print - print( + logger.i( 'Trusted range to be snapshotted (Current height: $_currentHeight): $_range', ); } @@ -183,7 +184,7 @@ class CreateSnapshotCubit extends Cubit { Uint8List data, ) async { // ignore: avoid_print - print('About to prepare and sign snapshot transaction'); + logger.i('About to prepare and sign snapshot transaction'); final isArConnectProfile = await _profileCubit.isCurrentProfileArConnect(); @@ -205,7 +206,7 @@ class CreateSnapshotCubit extends Cubit { try { // ignore: avoid_print - print( + logger.i( 'Preparing snapshot transaction with ${isArConnectProfile ? 'ArConnect' : 'JSON wallet'}', ); @@ -220,7 +221,7 @@ class CreateSnapshotCubit extends Cubit { final isTabFocused = _tabVisibility.isTabFocused(); if (isArConnectProfile && !isTabFocused) { // ignore: avoid_print - print( + logger.i( 'Preparing snapshot transaction while user is not focusing the tab. Waiting...', ); await _tabVisibility.onTabGetsFocusedFuture( @@ -228,7 +229,7 @@ class CreateSnapshotCubit extends Cubit { ); } else { // ignore: avoid_print - print( + logger.i( 'Error preparing snapshot transaction - $e isArConnectProfile: $isArConnectProfile, isTabFocused: $isTabFocused'); rethrow; } @@ -242,7 +243,7 @@ class CreateSnapshotCubit extends Cubit { try { // ignore: avoid_print - print( + logger.i( 'Signing snapshot transaction with ${isArConnectProfile ? 'ArConnect' : 'JSON wallet'}', ); @@ -257,7 +258,7 @@ class CreateSnapshotCubit extends Cubit { final isTabFocused = _tabVisibility.isTabFocused(); if (isArConnectProfile && !isTabFocused) { // ignore: avoid_print - print( + logger.i( 'Signing snapshot transaction while user is not focusing the tab. Waiting...', ); await _tabVisibility.onTabGetsFocusedFuture( @@ -267,7 +268,7 @@ class CreateSnapshotCubit extends Cubit { ); } else { // ignore: avoid_print - print( + logger.i( 'Error signing snapshot transaction - $e isArConnectProfile: $isArConnectProfile, isTabFocused: $isTabFocused'); rethrow; } @@ -276,7 +277,7 @@ class CreateSnapshotCubit extends Cubit { Future _getSnapshotData() async { // ignore: avoid_print - print('Computing snapshot data'); + logger.i('Computing snapshot data'); emit(ComputingSnapshotData( driveId: _driveId, @@ -304,7 +305,7 @@ class CreateSnapshotCubit extends Cubit { } // ignore: avoid_print - print('Finished computing snapshot data'); + logger.i('Finished computing snapshot data'); final data = dataBuffer.takeBytes(); return data; @@ -399,7 +400,7 @@ class CreateSnapshotCubit extends Cubit { Future confirmSnapshotCreation() async { if (await _profileCubit.logoutIfWalletMismatch()) { // ignore: avoid_print - print('Failed to confirm the upload: Wallet mismatch'); + logger.i('Failed to confirm the upload: Wallet mismatch'); emit(SnapshotUploadFailure(errorMessage: 'Wallet mismatch.')); return; } @@ -412,7 +413,7 @@ class CreateSnapshotCubit extends Cubit { emit(SnapshotUploadSuccess()); } catch (err) { // ignore: avoid_print - print( + logger.i( 'Error while posting the snapshot transaction: ${(err as TypeError).stackTrace}'); emit(SnapshotUploadFailure(errorMessage: '$err')); } @@ -420,7 +421,7 @@ class CreateSnapshotCubit extends Cubit { void cancelSnapshotCreation() { // ignore: avoid_print - print('User cancelled the snapshot creation'); + logger.i('User cancelled the snapshot creation'); _wasSnapshotDataComputingCanceled = true; } diff --git a/lib/blocs/drive_attach/drive_attach_cubit.dart b/lib/blocs/drive_attach/drive_attach_cubit.dart index 4c6d4a7a9a..ab2b3345e5 100644 --- a/lib/blocs/drive_attach/drive_attach_cubit.dart +++ b/lib/blocs/drive_attach/drive_attach_cubit.dart @@ -6,6 +6,7 @@ import 'package:ardrive/entities/entities.dart'; import 'package:ardrive/entities/string_types.dart'; import 'package:ardrive/models/models.dart'; import 'package:ardrive/services/services.dart'; +import 'package:ardrive/utils/logger/logger.dart'; import 'package:arweave/utils.dart'; import 'package:cryptography/cryptography.dart'; import 'package:equatable/equatable.dart'; @@ -235,6 +236,6 @@ class DriveAttachCubit extends Cubit { emit(DriveAttachFailure()); super.onError(error, stackTrace); - print('Failed to attach drive: $error $stackTrace'); + logger.i('Failed to attach drive: $error $stackTrace'); } } diff --git a/lib/blocs/drive_create/drive_create_cubit.dart b/lib/blocs/drive_create/drive_create_cubit.dart index 5928150d9e..75c760d9af 100644 --- a/lib/blocs/drive_create/drive_create_cubit.dart +++ b/lib/blocs/drive_create/drive_create_cubit.dart @@ -2,6 +2,7 @@ import 'package:ardrive/blocs/blocs.dart'; import 'package:ardrive/entities/entities.dart'; import 'package:ardrive/models/models.dart'; import 'package:ardrive/services/services.dart'; +import 'package:ardrive/utils/logger/logger.dart'; import 'package:arweave/arweave.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter/foundation.dart'; @@ -143,6 +144,6 @@ class DriveCreateCubit extends Cubit { emit(DriveCreateFailure()); super.onError(error, stackTrace); - print('Failed to create drive: $error $stackTrace'); + logger.e('Failed to create drive: $error $stackTrace'); } } diff --git a/lib/blocs/file_download/file_download_cubit.dart b/lib/blocs/file_download/file_download_cubit.dart index e4671cdb29..8559a290a6 100644 --- a/lib/blocs/file_download/file_download_cubit.dart +++ b/lib/blocs/file_download/file_download_cubit.dart @@ -10,6 +10,7 @@ import 'package:ardrive/models/models.dart'; import 'package:ardrive/services/services.dart'; import 'package:ardrive/utils/app_platform.dart'; import 'package:ardrive/utils/data_size.dart'; +import 'package:ardrive/utils/logger/logger.dart'; import 'package:ardrive_http/ardrive_http.dart'; import 'package:ardrive_io/ardrive_io.dart'; import 'package:cryptography/cryptography.dart'; diff --git a/lib/blocs/file_download/personal_file_download_cubit.dart b/lib/blocs/file_download/personal_file_download_cubit.dart index 59521ca23f..0574989053 100644 --- a/lib/blocs/file_download/personal_file_download_cubit.dart +++ b/lib/blocs/file_download/personal_file_download_cubit.dart @@ -164,7 +164,7 @@ class ProfileFileDownloadCubit extends FileDownloadCubit { @visibleForTesting bool isSizeAbovePrivateLimit(int size) { - debugPrint(_privateFileLimit.toString()); + logger.d(_privateFileLimit.toString()); return size > _privateFileLimit; } @@ -192,6 +192,6 @@ class ProfileFileDownloadCubit extends FileDownloadCubit { ); super.onError(error, stackTrace); - debugPrint('Failed to download personal file: $error $stackTrace'); + logger.e('Failed to download personal file: $error $stackTrace'); } } diff --git a/lib/blocs/folder_create/folder_create_cubit.dart b/lib/blocs/folder_create/folder_create_cubit.dart index f83a58669a..16c838d7eb 100644 --- a/lib/blocs/folder_create/folder_create_cubit.dart +++ b/lib/blocs/folder_create/folder_create_cubit.dart @@ -2,6 +2,7 @@ import 'package:ardrive/blocs/blocs.dart'; import 'package:ardrive/entities/entities.dart'; import 'package:ardrive/models/models.dart'; import 'package:ardrive/services/services.dart'; +import 'package:ardrive/utils/logger/logger.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -120,6 +121,6 @@ class FolderCreateCubit extends Cubit { emit(FolderCreateFailure()); super.onError(error, stackTrace); - print('Failed to create folder: $error $stackTrace'); + logger.e('Failed to create folder: $error $stackTrace'); } } diff --git a/lib/blocs/fs_entry_activity/fs_entry_activity_cubit.dart b/lib/blocs/fs_entry_activity/fs_entry_activity_cubit.dart index 2d8951d810..805418ce08 100644 --- a/lib/blocs/fs_entry_activity/fs_entry_activity_cubit.dart +++ b/lib/blocs/fs_entry_activity/fs_entry_activity_cubit.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:ardrive/models/models.dart'; import 'package:ardrive/pages/pages.dart'; +import 'package:ardrive/utils/logger/logger.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -67,7 +68,7 @@ class FsEntryActivityCubit extends Cubit { emit(FsEntryActivityFailure()); super.onError(error, stackTrace); - print('Failed to load entity activity: $error $stackTrace'); + logger.e('Failed to load entity activity: $error $stackTrace'); } @override diff --git a/lib/blocs/fs_entry_info/fs_entry_info_cubit.dart b/lib/blocs/fs_entry_info/fs_entry_info_cubit.dart index 41e3948d22..f3029e0b6e 100644 --- a/lib/blocs/fs_entry_info/fs_entry_info_cubit.dart +++ b/lib/blocs/fs_entry_info/fs_entry_info_cubit.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:ardrive/models/models.dart'; import 'package:ardrive/pages/pages.dart'; +import 'package:ardrive/utils/logger/logger.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -91,7 +92,7 @@ class FsEntryInfoCubit extends Cubit { emit(FsEntryInfoFailure()); super.onError(error, stackTrace); - print('Failed to load entity info: $error $stackTrace'); + logger.e('Failed to load entity info: $error $stackTrace'); } @override diff --git a/lib/blocs/fs_entry_move/fs_entry_move_bloc.dart b/lib/blocs/fs_entry_move/fs_entry_move_bloc.dart index f5d672503f..ee2bb364ac 100644 --- a/lib/blocs/fs_entry_move/fs_entry_move_bloc.dart +++ b/lib/blocs/fs_entry_move/fs_entry_move_bloc.dart @@ -5,6 +5,7 @@ import 'package:ardrive/core/crypto/crypto.dart'; import 'package:ardrive/models/models.dart'; import 'package:ardrive/pages/drive_detail/drive_detail_page.dart'; import 'package:ardrive/services/services.dart'; +import 'package:ardrive/utils/logger/logger.dart'; import 'package:arweave/arweave.dart'; import 'package:bloc_concurrency/bloc_concurrency.dart'; import 'package:equatable/equatable.dart'; @@ -77,7 +78,7 @@ class FsEntryMoveBloc extends Bloc { parentFolder: folderInView, ); } catch (err) { - print('Error moving items: $err'); + logger.e('Error moving items: $err'); } emit(const FsEntryMoveSuccess()); } else { diff --git a/lib/blocs/fs_entry_rename/fs_entry_rename_cubit.dart b/lib/blocs/fs_entry_rename/fs_entry_rename_cubit.dart index 1dd74dc18d..70dc44bf40 100644 --- a/lib/blocs/fs_entry_rename/fs_entry_rename_cubit.dart +++ b/lib/blocs/fs_entry_rename/fs_entry_rename_cubit.dart @@ -2,6 +2,7 @@ import 'package:ardrive/blocs/blocs.dart'; import 'package:ardrive/core/crypto/crypto.dart'; import 'package:ardrive/models/models.dart'; import 'package:ardrive/services/services.dart'; +import 'package:ardrive/utils/logger/logger.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:reactive_forms/reactive_forms.dart'; @@ -184,10 +185,10 @@ class FsEntryRenameCubit extends Cubit { void onError(Object error, StackTrace stackTrace) { if (_isRenamingFolder) { emit(const FolderEntryRenameFailure()); - print('Failed to rename folder: $error $stackTrace'); + logger.e('Failed to rename folder: $error $stackTrace'); } else { emit(const FileEntryRenameFailure()); - print('Failed to rename file: $error $stackTrace'); + logger.e('Failed to rename file: $error $stackTrace'); } super.onError(error, stackTrace); diff --git a/lib/blocs/ghost_fixer/ghost_fixer_cubit.dart b/lib/blocs/ghost_fixer/ghost_fixer_cubit.dart index 2875037dd0..3db0495d25 100644 --- a/lib/blocs/ghost_fixer/ghost_fixer_cubit.dart +++ b/lib/blocs/ghost_fixer/ghost_fixer_cubit.dart @@ -5,6 +5,7 @@ import 'package:ardrive/l11n/validation_messages.dart'; import 'package:ardrive/models/models.dart'; import 'package:ardrive/pages/pages.dart'; import 'package:ardrive/services/services.dart'; +import 'package:ardrive/utils/logger/logger.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -195,6 +196,6 @@ class GhostFixerCubit extends Cubit { emit(GhostFixerFailure()); super.onError(error, stackTrace); - print('Failed to create folder: $error $stackTrace'); + logger.e('Failed to create folder: $error $stackTrace'); } } diff --git a/lib/blocs/profile_add/profile_add_cubit.dart b/lib/blocs/profile_add/profile_add_cubit.dart index 24c18fa5d5..5dc8b47cb1 100644 --- a/lib/blocs/profile_add/profile_add_cubit.dart +++ b/lib/blocs/profile_add/profile_add_cubit.dart @@ -24,7 +24,6 @@ class ProfileAddCubit extends Cubit { final ProfileCubit _profileCubit; final ProfileDao _profileDao; final ArweaveService _arweave; - final BiometricAuthentication _biometricAuthentication; final ArDriveCrypto _crypto; ProfileAddCubit({ @@ -36,7 +35,6 @@ class ProfileAddCubit extends Cubit { required BiometricAuthentication biometricAuthentication, }) : _profileCubit = profileCubit, _profileDao = profileDao, - _biometricAuthentication = biometricAuthentication, _arweave = arweave, _crypto = crypto, super(ProfileAddPromptWallet()); diff --git a/lib/blocs/shared_file/shared_file_cubit.dart b/lib/blocs/shared_file/shared_file_cubit.dart index 0bbd3259ad..1e63cbbe7f 100644 --- a/lib/blocs/shared_file/shared_file_cubit.dart +++ b/lib/blocs/shared_file/shared_file_cubit.dart @@ -2,6 +2,7 @@ import 'package:ardrive/entities/entities.dart'; import 'package:ardrive/entities/string_types.dart'; import 'package:ardrive/models/models.dart'; import 'package:ardrive/services/services.dart'; +import 'package:ardrive/utils/logger/logger.dart'; import 'package:ardrive/utils/open_url.dart'; import 'package:arweave/utils.dart'; import 'package:cryptography/cryptography.dart'; @@ -115,7 +116,7 @@ class SharedFileCubit extends Cubit { return; } catch (e) { - debugPrint(e.toString()); + logger.e(e.toString()); } emit(SharedFileKeyInvalid()); diff --git a/lib/blocs/sync/sync_cubit.dart b/lib/blocs/sync/sync_cubit.dart index bf467d8bc5..d7a2dcad94 100644 --- a/lib/blocs/sync/sync_cubit.dart +++ b/lib/blocs/sync/sync_cubit.dart @@ -32,6 +32,7 @@ part 'utils/add_file_entity_revisions.dart'; part 'utils/add_folder_entity_revisions.dart'; part 'utils/create_ghosts.dart'; part 'utils/generate_paths.dart'; +part 'utils/get_all_file_entities.dart'; part 'utils/log_sync.dart'; part 'utils/parse_drive_transactions.dart'; part 'utils/sync_drive.dart'; @@ -159,7 +160,7 @@ class SyncCubit extends Cubit { Future arconnectSync() async { final isTabFocused = _tabVisibility.isTabFocused(); - print('[ArConnect SYNC] isTabFocused: $isTabFocused'); + logger.i('[ArConnect SYNC] isTabFocused: $isTabFocused'); if (isTabFocused && await _profileCubit.logoutIfWalletMismatch()) { emit(SyncWalletMismatch()); return; @@ -336,13 +337,25 @@ class SyncCubit extends Cubit { logSync('Updating transaction statuses...'); - await Future.wait([ - if (profile is ProfileLoggedIn) _profileCubit.refreshBalance(), - _updateTransactionStatuses( - driveDao: _driveDao, - arweave: _arweave, - ), - ]); + final allFileRevisions = await _getAllFileEntities(driveDao: _driveDao); + final metadataTxsFromSnapshots = + await SnapshotItemOnChain.getAllCachedTransactionIds(); + + final confirmedFileTxIds = allFileRevisions + .where((file) => metadataTxsFromSnapshots.contains(file.metadataTxId)) + .map((file) => file.dataTxId) + .toList(); + + await Future.wait( + [ + if (profile is ProfileLoggedIn) _profileCubit.refreshBalance(), + _updateTransactionStatuses( + driveDao: _driveDao, + arweave: _arweave, + txsIdsToSkip: confirmedFileTxIds, + ), + ], + ); logSync('Transaction statuses updated'); diff --git a/lib/blocs/sync/utils/get_all_file_entities.dart b/lib/blocs/sync/utils/get_all_file_entities.dart new file mode 100644 index 0000000000..7a6d161d0a --- /dev/null +++ b/lib/blocs/sync/utils/get_all_file_entities.dart @@ -0,0 +1,7 @@ +part of 'package:ardrive/blocs/sync/sync_cubit.dart'; + +Future> _getAllFileEntities({ + required DriveDao driveDao, +}) async { + return await driveDao.db.fileRevisions.select().get(); +} diff --git a/lib/blocs/sync/utils/log_sync.dart b/lib/blocs/sync/utils/log_sync.dart index 33fd09d3e2..0c241e708d 100644 --- a/lib/blocs/sync/utils/log_sync.dart +++ b/lib/blocs/sync/utils/log_sync.dart @@ -2,7 +2,7 @@ part of 'package:ardrive/blocs/sync/sync_cubit.dart'; void logSync(String message) { // ignore: avoid_print - print('sync: $message'); + logger.i('sync: $message'); } void logSyncError(Object error, StackTrace stackTrace) { diff --git a/lib/blocs/sync/utils/sync_drive.dart b/lib/blocs/sync/utils/sync_drive.dart index 53e7163029..24c2e23ab6 100644 --- a/lib/blocs/sync/utils/sync_drive.dart +++ b/lib/blocs/sync/utils/sync_drive.dart @@ -1,3 +1,5 @@ +// ignore_for_file: avoid_logger.i + part of 'package:ardrive/blocs/sync/sync_cubit.dart'; const fetchPhaseWeight = 0.1; @@ -45,7 +47,7 @@ Stream _syncDrive( List snapshotItems = []; if (configService.config.enableSyncFromSnapshot) { - logger.i('Syncing from snapshot'); + logger.e('Syncing from snapshot'); final snapshotsStream = arweave.getAllSnapshotsOfDrive( driveId, @@ -84,11 +86,11 @@ Stream _syncDrive( ownerAddress: ownerAddress, ); - print('Total range to query for: ${totalRangeToQueryFor.rangeSegments}'); - print( + logger.e('Total range to query for: ${totalRangeToQueryFor.rangeSegments}'); + logger.e( 'Sub ranges in snapshots (DRIVE ID: $driveId): ${snapshotDriveHistory.subRanges.rangeSegments}', ); - print( + logger.e( 'Sub ranges in GQL (DRIVE ID: $driveId): ${gqlDriveHistorySubRanges.rangeSegments}', ); @@ -124,7 +126,7 @@ Stream _syncDrive( 'The transaction block is null. \nTransaction node id: ${t.id}', ); - print('New fetch-phase percentage: $fetchPhasePercentage'); + logger.e('New fetch-phase percentage: $fetchPhasePercentage'); /// if the block is null, we don't calculate and keep the same percentage return fetchPhasePercentage; @@ -137,7 +139,7 @@ Stream _syncDrive( if (block != null) { firstBlockHeight = block.height; totalBlockHeightDifference = currentBlockHeight - firstBlockHeight; - print( + logger.e( 'First height: $firstBlockHeight, totalHeightDiff: $totalBlockHeightDifference', ); } else { @@ -146,10 +148,10 @@ Stream _syncDrive( ); } } else { - print('Block attribute is already present - $firstBlockHeight'); + logger.e('Block attribute is already present - $firstBlockHeight'); } - print('Adding transaction ${t.id}'); + logger.e('Adding transaction ${t.id}'); transactions.add(t); /// We can only calculate the fetch percentage if we have the `firstBlockHeight` @@ -158,7 +160,7 @@ Stream _syncDrive( fetchPhasePercentage = calculatePercentageBasedOnBlockHeights(); } else { // If the difference is zero means that the first phase was concluded. - print('The first phase just finished!'); + logger.e('The first phase just finished!'); fetchPhasePercentage = 1; } final percentage = @@ -166,7 +168,7 @@ Stream _syncDrive( yield percentage; } } - print('Done fetching data - ${gqlDriveHistory.driveId}'); + logger.e('Done fetching data - ${gqlDriveHistory.driveId}'); final fetchPhaseTotalTime = DateTime.now().difference(fetchPhaseStartDT).inMilliseconds; @@ -197,7 +199,7 @@ Stream _syncDrive( (parseProgress) => parseProgress * 0.9, ); } catch (e) { - print('[Sync Drive] Error while parsing transactions: $e'); + logger.e('[Sync Drive] Error while parsing transactions: $e'); rethrow; } diff --git a/lib/blocs/sync/utils/update_transaction_statuses.dart b/lib/blocs/sync/utils/update_transaction_statuses.dart index b2d976feb5..46f0aa9f61 100644 --- a/lib/blocs/sync/utils/update_transaction_statuses.dart +++ b/lib/blocs/sync/utils/update_transaction_statuses.dart @@ -1,13 +1,26 @@ +// ignore_for_file: avoid_print + part of 'package:ardrive/blocs/sync/sync_cubit.dart'; Future _updateTransactionStatuses({ required DriveDao driveDao, required ArweaveService arweave, + List txsIdsToSkip = const [], }) async { final pendingTxMap = { for (final tx in await driveDao.pendingTransactions().get()) tx.id: tx, }; + /// Remove all confirmed transactions from the pending map + /// and update the status of the remaining ones + + logger.i( + 'Skipping status update for ${txsIdsToSkip.length} transactions that were captured in snapshots', + ); + for (final txId in txsIdsToSkip) { + pendingTxMap.remove(txId); + } + final length = pendingTxMap.length; final list = pendingTxMap.keys.toList(); @@ -80,8 +93,19 @@ Future _updateTransactionStatuses({ } } }); + await Future.delayed(const Duration(milliseconds: 200)); } + await driveDao.transaction(() async { + for (final txId in txsIdsToSkip) { + await driveDao.writeToTransaction( + NetworkTransactionsCompanion( + id: Value(txId), + status: const Value(TransactionStatus.confirmed), + ), + ); + } + }); } bool _isOverThePendingTime(DateTime? transactionCreatedDate) { diff --git a/lib/blocs/upload/upload_cubit.dart b/lib/blocs/upload/upload_cubit.dart index 7b9b49db4b..70964c16f1 100644 --- a/lib/blocs/upload/upload_cubit.dart +++ b/lib/blocs/upload/upload_cubit.dart @@ -7,7 +7,6 @@ import 'package:ardrive/blocs/upload/models/models.dart'; import 'package:ardrive/blocs/upload/upload_file_checker.dart'; import 'package:ardrive/models/models.dart'; import 'package:ardrive/services/services.dart'; -import 'package:ardrive/utils/extensions.dart'; import 'package:ardrive/utils/logger/logger.dart'; import 'package:ardrive/utils/upload_plan_utils.dart'; import 'package:ardrive_io/ardrive_io.dart'; @@ -311,7 +310,7 @@ class UploadCubit extends Cubit { }) async { bool hasEmittedError = false; - debugPrint('Starting upload...'); + logger.d('Starting upload...'); final profile = _profileCubit.state as ProfileLoggedIn; @@ -327,15 +326,15 @@ class UploadCubit extends Cubit { ), ); - debugPrint('Wallet verified'); + logger.d('Wallet verified'); - debugPrint('Starting bundle preparation....'); - debugPrint('Number of bundles: ${uploadPlan.bundleUploadHandles.length}'); + logger.d('Starting bundle preparation....'); + logger.d('Number of bundles: ${uploadPlan.bundleUploadHandles.length}'); // Upload Bundles for (var bundleHandle in uploadPlan.bundleUploadHandles) { try { - debugPrint('Starting bundle with ${bundleHandle.size}'); + logger.d('Starting bundle with ${bundleHandle.size}'); await bundleHandle.prepareAndSignBundleTransaction( arweaveService: _arweave, @@ -345,13 +344,13 @@ class UploadCubit extends Cubit { isArConnect: await _profileCubit.isCurrentProfileArConnect(), ); - debugPrint('Bundle preparation finished'); + logger.d('Bundle preparation finished'); } catch (error) { - debugPrint(error.toString()); + logger.d(error.toString()); addError(error); } - debugPrint('Starting bundle uploads'); + logger.d('Starting bundle uploads'); await for (final _ in bundleHandle .upload(_arweave, _turbo) @@ -369,7 +368,7 @@ class UploadCubit extends Cubit { await bundleHandle.writeBundleItemsToDatabase(driveDao: _driveDao); - debugPrint('Disposing bundle'); + logger.d('Disposing bundle'); bundleHandle.dispose(); } @@ -459,7 +458,7 @@ class UploadCubit extends Cubit { @override void onError(Object error, StackTrace stackTrace) { emit(UploadFailure()); - 'Failed to upload file: $error $stackTrace'.logError(); + logger.e('Failed to upload file: $error $stackTrace'); super.onError(error, stackTrace); } } diff --git a/lib/blocs/upload/upload_handles/bundle_upload_handle.dart b/lib/blocs/upload/upload_handles/bundle_upload_handle.dart index 69a633f1ba..085d2bdfc2 100644 --- a/lib/blocs/upload/upload_handles/bundle_upload_handle.dart +++ b/lib/blocs/upload/upload_handles/bundle_upload_handle.dart @@ -66,9 +66,9 @@ class BundleUploadHandle implements UploadHandle { folderDataItemUploadHandles), ); - debugPrint('Bundle mounted'); + logger.d('Bundle mounted'); - debugPrint('Creating bundle transaction'); + logger.d('Creating bundle transaction'); if (useTurbo) { bundleDataItem = await arweaveService.prepareBundledDataItem( bundle, @@ -84,19 +84,19 @@ class BundleUploadHandle implements UploadHandle { bundleId = bundleTx.id; - debugPrint('Bundle transaction created'); + logger.d('Bundle transaction created'); - debugPrint('Adding tip'); + logger.d('Adding tip'); await pstService.addCommunityTipToTx(bundleTx); - debugPrint('Tip added'); + logger.d('Tip added'); - debugPrint('Signing bundle'); + logger.d('Signing bundle'); await bundleTx.sign(wallet); - debugPrint('Bundle signed'); + logger.d('Bundle signed'); } } @@ -105,7 +105,7 @@ class BundleUploadHandle implements UploadHandle { }) async { if (hasError) return; - debugPrint('Writing bundle items to database'); + logger.d('Writing bundle items to database'); // Write entities to database for (var folder in folderDataItemUploadHandles) { diff --git a/lib/components/create_snapshot_dialog.dart b/lib/components/create_snapshot_dialog.dart index bfdee33dcd..50f6d18d72 100644 --- a/lib/components/create_snapshot_dialog.dart +++ b/lib/components/create_snapshot_dialog.dart @@ -9,6 +9,7 @@ import 'package:ardrive/theme/theme.dart'; import 'package:ardrive/utils/app_localizations_wrapper.dart'; import 'package:ardrive/utils/filesize.dart'; import 'package:ardrive/utils/html/html_util.dart'; +import 'package:ardrive/utils/logger/logger.dart'; import 'package:ardrive/utils/split_localizations.dart'; import 'package:ardrive_ui/ardrive_ui.dart'; import 'package:flutter/material.dart'; @@ -413,13 +414,13 @@ Widget _confirmDialog( ModalAction( title: appLocalizationsOf(context).cancelEmphasized, action: () { - print('Cancel snapshot creation'); + logger.i('Cancel snapshot creation'); Navigator.of(context).pop(); }, ), ModalAction( action: () async => { - print('Confirm snapshot creation'), + logger.i('Confirm snapshot creation'), await createSnapshotCubit.confirmSnapshotCreation(), }, title: appLocalizationsOf(context).uploadEmphasized, diff --git a/lib/components/details_panel.dart b/lib/components/details_panel.dart index 9d949ff45d..4d91f3eb49 100644 --- a/lib/components/details_panel.dart +++ b/lib/components/details_panel.dart @@ -138,8 +138,8 @@ class _DetailsPanelState extends State { content: Column( children: [ if (!widget.isSharePage) - ScreenTypeLayout( - desktop: Column( + ScreenTypeLayout.builder( + desktop: (context) => Column( children: [ DetailsPanelToolbar( item: widget.item, @@ -149,7 +149,7 @@ class _DetailsPanelState extends State { ), ], ), - mobile: const SizedBox.shrink(), + mobile: (context) => const SizedBox.shrink(), ), ArDriveCard( contentPadding: const EdgeInsets.all(24), @@ -174,9 +174,9 @@ class _DetailsPanelState extends State { ), if (widget.currentDrive != null && !widget.isSharePage) - ScreenTypeLayout( - desktop: const SizedBox.shrink(), - mobile: EntityActionsMenu( + ScreenTypeLayout.builder( + desktop: (context) => const SizedBox.shrink(), + mobile: (context) => EntityActionsMenu( drive: widget.currentDrive, withInfo: false, item: widget.item, diff --git a/lib/components/ghost_fixer_form.dart b/lib/components/ghost_fixer_form.dart index e08a07e498..cce352b04e 100644 --- a/lib/components/ghost_fixer_form.dart +++ b/lib/components/ghost_fixer_form.dart @@ -263,15 +263,15 @@ class _GhostFixerFormState extends State { const Divider(), Padding( padding: const EdgeInsets.only(right: 16), - child: ScreenTypeLayout( - desktop: Row( + child: ScreenTypeLayout.builder( + desktop: (context) => Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ buildCreateFolderButton(), buildButtonBar(), ], ), - mobile: Wrap( + mobile: (context) => Wrap( alignment: WrapAlignment.spaceBetween, children: [ buildCreateFolderButton(), diff --git a/lib/components/keyboard_handler.dart b/lib/components/keyboard_handler.dart index 05cac5dcb1..c536ea8b25 100644 --- a/lib/components/keyboard_handler.dart +++ b/lib/components/keyboard_handler.dart @@ -2,7 +2,6 @@ import 'package:ardrive/blocs/blocs.dart'; import 'package:ardrive/dev_tools/app_dev_tools.dart'; import 'package:ardrive/dev_tools/shortcut_handler.dart'; import 'package:ardrive/services/config/config_service.dart'; -import 'package:ardrive/utils/extensions.dart'; import 'package:ardrive/utils/logger/logger.dart'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:flutter/material.dart'; @@ -68,7 +67,7 @@ Future isCtrlOrMetaKeyPressed(RawKeyEvent event) async { } return ctrlMetaKeyPressed; } catch (e) { - 'Unable to compute platform'.logError(); + logger.e('Unable to compute platform'); return false; } } diff --git a/lib/components/profile_overlay.dart b/lib/components/profile_overlay.dart index 3bc5f99e99..636ab43b5b 100644 --- a/lib/components/profile_overlay.dart +++ b/lib/components/profile_overlay.dart @@ -4,6 +4,7 @@ import 'package:ardrive/misc/resources.dart'; import 'package:ardrive/pages/profile_auth/components/profile_auth_add_screen.dart'; import 'package:ardrive/theme/theme.dart'; import 'package:ardrive/utils/app_localizations_wrapper.dart'; +import 'package:ardrive/utils/logger/logger.dart'; import 'package:ardrive/utils/open_url.dart'; import 'package:arweave/utils.dart' as utils; import 'package:flutter/material.dart'; @@ -130,7 +131,7 @@ class _ProfileOverlayState extends State { onDisableBiometric: () {}, onEnableBiometric: () {}, onError: () { - debugPrint('close profile overlay'); + logger.d('close profile overlay'); widget.onCloseProfileOverlay?.call(); }, ), diff --git a/lib/components/side_bar.dart b/lib/components/side_bar.dart index 60b68dbc8c..e09bb88151 100644 --- a/lib/components/side_bar.dart +++ b/lib/components/side_bar.dart @@ -38,9 +38,9 @@ class _AppSideBarState extends State { Widget build(BuildContext context) { return Material( color: ArDriveTheme.of(context).themeData.backgroundColor, - child: ScreenTypeLayout( - mobile: _mobileView(), - desktop: _desktopView(), + child: ScreenTypeLayout.builder( + mobile: (context) => _mobileView(), + desktop: (context) => _desktopView(), ), ); } @@ -454,16 +454,9 @@ class _AppSideBarState extends State { BuildContext context, bool isMobile, ) { - final minimumWalletBalance = BigInt.from(10000000); - final profileState = context.watch().state; if (profileState is ProfileLoggedIn) { - final profile = profileState; - final notEnoughARInWallet = !profile.hasMinimumBalanceForUpload( - minimumWalletBalance: minimumWalletBalance, - ); - return AnimatedSwitcher( duration: const Duration(milliseconds: 200), child: Column( diff --git a/lib/components/top_up_dialog.dart b/lib/components/top_up_dialog.dart index 19e25fe245..8bfce670aa 100644 --- a/lib/components/top_up_dialog.dart +++ b/lib/components/top_up_dialog.dart @@ -40,7 +40,8 @@ class _TopUpEstimationViewState extends State { return BlocBuilder( bloc: paymentBloc, - buildWhen: (_, current) => current is! EstimationLoading, + buildWhen: (_, current) => + current is! EstimationLoading && current is! EstimationLoadError, builder: (context, state) { if (state is EstimationLoading) { return const SizedBox( @@ -154,7 +155,8 @@ class _TopUpEstimationViewState extends State { builder: (context, state) { return ArDriveButton( isDisabled: paymentBloc.currentAmount == 0 || - state is EstimationLoading, + state is EstimationLoading || + state is EstimationLoadError, maxWidth: 143, maxHeight: 40, fontStyle: ArDriveTypography.body @@ -248,8 +250,6 @@ class _PresetAmountSelectorState extends State { super.didChangeDependencies(); } - DateTime lastChanged = DateTime.now(); - void _onAmountChanged(String amount) { widget.onAmountSelected(int.parse(amount)); } @@ -594,7 +594,28 @@ class PriceEstimateView extends StatelessWidget { @override Widget build(BuildContext context) { return BlocBuilder( + buildWhen: (previous, current) { + return current is EstimationLoaded || current is EstimationLoadError; + }, builder: (context, state) { + if (state is EstimationLoadError) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Divider(height: 32), + Text( + 'Unable to fetch the estimate at this time.', + style: ArDriveTypography.body.buttonNormalBold( + color: ArDriveTheme.of(context) + .themeData + .colors + .themeErrorDefault, + ), + ), + ], + ); + } + return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ diff --git a/lib/entities/snapshot_entity.dart b/lib/entities/snapshot_entity.dart index 0b34551103..6cdd92a57a 100644 --- a/lib/entities/snapshot_entity.dart +++ b/lib/entities/snapshot_entity.dart @@ -3,6 +3,7 @@ import 'dart:typed_data'; import 'package:ardrive/core/crypto/crypto.dart'; import 'package:ardrive/entities/string_types.dart'; import 'package:ardrive/services/services.dart'; +import 'package:ardrive/utils/logger/logger.dart'; import 'package:arweave/arweave.dart'; import 'package:cryptography/cryptography.dart'; import 'package:json_annotation/json_annotation.dart'; @@ -59,7 +60,7 @@ class SnapshotEntity extends Entity { ..createdAt = transaction.getCommitTime(); } catch (_) { // ignore: avoid_print - print('Error parsing transaction: ${transaction.id}'); + logger.e('Error parsing transaction: ${transaction.id}'); throw EntityTransactionParseException(transactionId: transaction.id); } } diff --git a/lib/main.dart b/lib/main.dart index 6c2ec85ac0..725dcf46b2 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -77,7 +77,7 @@ void main() async { } } - debugPrint('Starting without crashlytics'); + logger.d('Starting without crashlytics'); _runWithoutCrashlytics(); } diff --git a/lib/misc/resources.dart b/lib/misc/resources.dart index 54aa948f50..8443fc62db 100644 --- a/lib/misc/resources.dart +++ b/lib/misc/resources.dart @@ -1,17 +1,17 @@ class Resources { static const images = Images(); static const arHelpLink = - 'https://ar-io.zendesk.com/hc/en-us/articles/5258520347419-Fund-Your-Wallet'; + 'https://ardrive.zendesk.com/hc/en-us/articles/5258520347419-Fund-Your-Wallet'; static const manifestLearnMoreLink = - 'https://ar-io.zendesk.com/hc/en-us/articles/5300353421467-Arweave-Manifests'; + 'https://ardrive.zendesk.com/hc/en-us/articles/5300353421467-Arweave-Manifests'; static const surveyFeedbackFormUrl = 'https://ar-io.typeform.com/UserSurvey'; - static const helpLink = 'https://ar-io.zendesk.com/hc/en-us'; + static const helpLink = 'https://ardrive.zendesk.com/hc/en-us'; static const agreementLink = 'https://ardrive.io/tos-and-privacy/'; static const getWalletLink = 'https://www.arconnect.io/'; static const howDoesKeyFileLoginWork = - 'https://ar-io.zendesk.com/hc/en-us/articles/15412384724251-How-Does-Keyfile-Login-Work-'; + 'https://ardrive.zendesk.com/hc/en-us/articles/15412384724251-How-Does-Keyfile-Login-Work-'; } class Images { diff --git a/lib/models/daos/profile_dao.dart b/lib/models/daos/profile_dao.dart index 456e497b08..52582bbb5b 100644 --- a/lib/models/daos/profile_dao.dart +++ b/lib/models/daos/profile_dao.dart @@ -5,6 +5,7 @@ import 'package:ardrive/entities/profile_types.dart'; import 'package:ardrive/models/models.dart'; import 'package:ardrive/services/arconnect/arconnect.dart'; import 'package:ardrive/services/arconnect/arconnect_wallet.dart'; +import 'package:ardrive/utils/logger/logger.dart'; import 'package:arweave/arweave.dart'; import 'package:cryptography/cryptography.dart'; import 'package:drift/drift.dart'; @@ -98,7 +99,7 @@ class ProfileDao extends DatabaseAccessor with _$ProfileDaoMixin { Wallet wallet, ProfileType profileType, ) async { - debugPrint('Adding profile $username with type $profileType'); + logger.d('Adding profile $username with type $profileType'); final profileKdRes = await compute, ProfileKeyDerivationResult>( @@ -106,7 +107,7 @@ class ProfileDao extends DatabaseAccessor with _$ProfileDaoMixin { {'password': password}, ); - debugPrint('Profile key derivation result: $profileKdRes'); + logger.d('Profile key derivation result: $profileKdRes'); final profileSalt = profileKdRes.salt; final encryptedWallet = await () async { @@ -125,11 +126,11 @@ class ProfileDao extends DatabaseAccessor with _$ProfileDaoMixin { } }(); - debugPrint('Encrypted wallet finished'); + logger.d('Encrypted wallet finished'); final publicKey = await wallet.getOwner(); - debugPrint('Public key finished'); + logger.d('Public key finished'); final encryptedPublicKey = await compute, SecretBox>(encryptPublicKey, { @@ -137,7 +138,7 @@ class ProfileDao extends DatabaseAccessor with _$ProfileDaoMixin { 'profileKdRes': profileKdRes, }); - debugPrint('Encrypted public key: $encryptedPublicKey'); + logger.d('Encrypted public key: $encryptedPublicKey'); await into(profiles).insert( ProfilesCompanion.insert( diff --git a/lib/models/database/database.dart b/lib/models/database/database.dart index 8028859f00..d7ea42e7aa 100644 --- a/lib/models/database/database.dart +++ b/lib/models/database/database.dart @@ -1,4 +1,5 @@ import 'package:ardrive/models/daos/daos.dart'; +import 'package:ardrive/utils/logger/logger.dart'; import 'package:drift/drift.dart'; import 'unsupported.dart' @@ -23,7 +24,7 @@ class Database extends _$Database { return m.createAll(); }, onUpgrade: (Migrator m, int from, int to) async { - print('schema changed from $from to $to '); + logger.i('schema changed from $from to $to '); if (from >= 1 && from < schemaVersion) { // Reset the database. for (final table in allTables) { diff --git a/lib/pages/drive_detail/components/custom_paginated_data_table.dart b/lib/pages/drive_detail/components/custom_paginated_data_table.dart deleted file mode 100644 index be1b50d2e3..0000000000 --- a/lib/pages/drive_detail/components/custom_paginated_data_table.dart +++ /dev/null @@ -1,522 +0,0 @@ -import 'dart:math' as math; - -import 'package:ardrive/theme/theme.dart'; -import 'package:ardrive/utils/app_localizations_wrapper.dart'; -import 'package:flutter/material.dart'; -import 'package:intersperse/intersperse.dart'; - -class CustomPaginatedDataTable extends StatefulWidget { - CustomPaginatedDataTable({ - Key? key, - required this.columns, - required this.showCheckboxColumn, - this.sortColumnIndex, - this.sortAscending = true, - this.dataRowHeight = kMinInteractiveDimension, - this.headingRowHeight = 56.0, - this.horizontalMargin = 24.0, - this.columnSpacing = 56.0, - this.showFirstLastButtons = false, - this.initialFirstRowIndex = 0, - this.onPageChanged, - this.rowsPerPage = defaultRowsPerPage, - this.availableRowsPerPage = const [ - defaultRowsPerPage, - defaultRowsPerPage * 2, - defaultRowsPerPage * 5, - defaultRowsPerPage * 10 - ], - this.onRowsPerPageChanged, - this.arrowHeadColor, - required this.source, - this.checkboxHorizontalMargin, - this.tableKey, - }) : assert(columns.isNotEmpty), - assert(sortColumnIndex == null || - (sortColumnIndex >= 0 && sortColumnIndex < columns.length)), - assert(rowsPerPage > 0), - assert(() { - if (onRowsPerPageChanged != null) { - assert(availableRowsPerPage.contains(rowsPerPage)); - } - return true; - }()), - super(key: key); - - /// The configuration and labels for the columns in the table. - final List columns; - - /// The current primary sort key's column. - /// - /// See [DataTable.sortColumnIndex]. - final int? sortColumnIndex; - - final Key? tableKey; - - /// Whether the column mentioned in [sortColumnIndex], if any, is sorted - /// in ascending order. - /// - /// See [DataTable.sortAscending]. - final bool sortAscending; - final bool showCheckboxColumn; - - /// The height of each row (excluding the row that contains column headings). - /// - /// This value is optional and defaults to kMinInteractiveDimension if not - /// specified. - final double dataRowHeight; - - /// The height of the heading row. - /// - /// This value is optional and defaults to 56.0 if not specified. - final double headingRowHeight; - - /// The horizontal margin between the edges of the table and the content - /// in the first and last cells of each row. - /// - /// When a checkbox is displayed, it is also the margin between the checkbox - /// the content in the first data column. - /// - /// This value defaults to 24.0 to adhere to the Material Design specifications. - /// - /// If [checkboxHorizontalMargin] is null, then [horizontalMargin] is also the - /// margin between the edge of the table and the checkbox, as well as the - /// margin between the checkbox and the content in the first data column. - final double horizontalMargin; - - /// The horizontal margin between the contents of each data column. - /// - /// This value defaults to 56.0 to adhere to the Material Design specifications. - final double columnSpacing; - - /// Flag to display the pagination buttons to go to the first and last pages. - final bool showFirstLastButtons; - - /// The index of the first row to display when the widget is first created. - final int? initialFirstRowIndex; - - /// Invoked when the user switches to another page. - /// - /// The value is the index of the first row on the currently displayed page. - final ValueChanged? onPageChanged; - - /// The number of rows to show on each page. - /// - /// See also: - /// - /// * [onRowsPerPageChanged] - /// * [defaultRowsPerPage] - final int rowsPerPage; - - /// The default value for [rowsPerPage]. - /// - /// Useful when initializing the field that will hold the current - /// [rowsPerPage], when implemented [onRowsPerPageChanged]. - static const int defaultRowsPerPage = 10; - - /// The options to offer for the rowsPerPage. - /// - /// The current [rowsPerPage] must be a value in this list. - /// - /// The values in this list should be sorted in ascending order. - final List availableRowsPerPage; - - /// Invoked when the user selects a different number of rows per page. - /// - /// If this is null, then the value given by [rowsPerPage] will be used - /// and no affordance will be provided to change the value. - final ValueChanged? onRowsPerPageChanged; - - /// The data source which provides data to show in each row. Must be non-null. - /// - /// This object should generally have a lifetime longer than the - /// [CustomPaginatedDataTable] widget itself; it should be reused each time the - /// [CustomPaginatedDataTable] constructor is called. - final DataTableSource source; - - /// Horizontal margin around the checkbox, if it is displayed. - /// - /// If null, then [horizontalMargin] is used as the margin between the edge - /// of the table and the checkbox, as well as the margin between the checkbox - /// and the content in the first data column. This value defaults to 24.0. - final double? checkboxHorizontalMargin; - - /// Defines the color of the arrow heads in the footer. - final Color? arrowHeadColor; - - @override - CustomPaginatedDataTableState createState() => - CustomPaginatedDataTableState(); -} - -/// Holds the state of a [CustomPaginatedDataTable]. -/// -/// The table can be programmatically paged using the [pageTo] method. -class CustomPaginatedDataTableState extends State { - late int _firstRowIndex; - late int _rowCount; - late bool _rowCountApproximate; - final Map _rows = {}; - final int _pagesToShow = 5; - final ScrollController scrollController = ScrollController(); - @override - void initState() { - super.initState(); - _firstRowIndex = PageStorage.of(context)?.readState(context) as int? ?? - widget.initialFirstRowIndex ?? - 0; - widget.source.addListener(_handleDataSourceChanged); - _handleDataSourceChanged(); - } - - @override - void didUpdateWidget(CustomPaginatedDataTable oldWidget) { - super.didUpdateWidget(oldWidget); - if (oldWidget.source != widget.source) { - oldWidget.source.removeListener(_handleDataSourceChanged); - widget.source.addListener(_handleDataSourceChanged); - _handleDataSourceChanged(); - } - } - - @override - void dispose() { - widget.source.removeListener(_handleDataSourceChanged); - super.dispose(); - } - - void _handleDataSourceChanged() { - setState(() { - _rowCount = widget.source.rowCount; - _rowCountApproximate = widget.source.isRowCountApproximate; - _rows.clear(); - }); - } - - /// Ensures that the given row is visible. - void pageTo(int rowIndex) { - final oldFirstRowIndex = _firstRowIndex; - setState(() { - final rowsPerPage = widget.rowsPerPage; - _firstRowIndex = (rowIndex ~/ rowsPerPage) * rowsPerPage; - }); - if ((widget.onPageChanged != null) && - (oldFirstRowIndex != _firstRowIndex)) { - widget.onPageChanged!(_firstRowIndex); - } - _resetScroll(); - } - - DataRow _getProgressIndicatorRowFor(int index) { - var haveProgressIndicator = false; - final cells = widget.columns.map((DataColumn column) { - if (!column.numeric) { - haveProgressIndicator = true; - return const DataCell(CircularProgressIndicator()); - } - return DataCell.empty; - }).toList(); - if (!haveProgressIndicator) { - haveProgressIndicator = true; - cells[0] = const DataCell(CircularProgressIndicator()); - } - return DataRow.byIndex( - index: index, - cells: cells, - ); - } - - List _getRows(int firstRowIndex, int rowsPerPage) { - final result = []; - final nextPageFirstRowIndex = firstRowIndex + rowsPerPage; - var haveProgressIndicator = false; - for (var index = firstRowIndex; index < nextPageFirstRowIndex; index += 1) { - DataRow? row; - if (index < _rowCount || _rowCountApproximate) { - row = _rows.putIfAbsent(index, () => widget.source.getRow(index)); - if (row == null && !haveProgressIndicator) { - row ??= _getProgressIndicatorRowFor(index); - haveProgressIndicator = true; - } - } - if (row != null) { - result.add(row); - } - } - return result; - } - - void _resetScroll() { - scrollController.animateTo( - 0, - duration: const Duration(milliseconds: 10), - curve: Curves.easeIn, - ); - } - - void _handleFirst() { - pageTo(0); - } - - void _handlePrevious() { - pageTo(math.max(_firstRowIndex - widget.rowsPerPage, 0)); - } - - void _handleNext() { - pageTo(_firstRowIndex + widget.rowsPerPage); - } - - void _handleLast() { - pageTo(((_rowCount - 1) / widget.rowsPerPage).floor() * widget.rowsPerPage); - } - - int _getPageCount() { - return (_rowCount / widget.rowsPerPage).ceil(); - } - - int _getCurrentPage() { - return (_firstRowIndex + 1) ~/ widget.rowsPerPage; - } - - bool _isNextPageUnavailable() => - !_rowCountApproximate && - (_firstRowIndex + widget.rowsPerPage >= _rowCount); - - final pageButtonStyle = TextButton.styleFrom( - padding: EdgeInsets.zero, - minimumSize: const Size(10, 10), - textStyle: const TextStyle( - color: kOnSurfaceBodyTextColor, - ), - ); - - @override - Widget build(BuildContext context) { - final footerTextStyle = Theme.of(context) - .textTheme - .caption! - .copyWith(color: kOnSurfaceBodyTextColor); - - TextButton pageButton({ - required int page, - required Function onPressed, - }) { - return TextButton( - style: pageButtonStyle, - onPressed: () => pageTo(widget.rowsPerPage * page), - child: Text( - (page + 1).toString(), - style: footerTextStyle.copyWith( - color: page == _getCurrentPage() - ? kPrimarySwatch.shade500 - : kOnSurfaceBodyTextColor, - ), - ), - ); - } - - const ellipsisSeparator = Text('...'); - const pageNumberSeparator = Text('|'); - - Widget pageRow(int pagesToShow) { - return Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - if (_getPageCount() > pagesToShow) ...[ - if (_getCurrentPage() < (pagesToShow - 1)) ...[ - for (var i = 0; i < pagesToShow; i++) - pageButton( - page: i, - onPressed: () => pageTo(widget.rowsPerPage * i), - ), - ellipsisSeparator, - pageButton( - page: _getPageCount() - 1, - onPressed: () => _handleLast(), - ), - ] else if (_getCurrentPage() >= (pagesToShow - 1) && - _getCurrentPage() < _getPageCount() - pagesToShow) ...[ - pageButton( - page: 0, - onPressed: () => _handleFirst(), - ), - ellipsisSeparator, - for (var i = _getCurrentPage() - 2; - i <= _getCurrentPage() + 2; - i++) - pageButton( - page: i, - onPressed: () => pageTo(widget.rowsPerPage * i), - ), - ellipsisSeparator, - pageButton( - page: _getPageCount() - 1, - onPressed: () => _handleLast(), - ), - ] else ...[ - pageButton( - page: 0, - onPressed: () => _handleFirst(), - ), - ellipsisSeparator, - for (var i = _getPageCount() - pagesToShow; - i < _getPageCount(); - i++) - pageButton( - page: i, - onPressed: () => pageTo(widget.rowsPerPage * i), - ), - ] - ] else - for (var i = 0; i < _getPageCount(); i++) - pageButton( - page: i, - onPressed: () => pageTo(widget.rowsPerPage * i), - ), - ].intersperse(pageNumberSeparator).toList(), - ); - } - - final footerWidgets = []; - if (widget.onRowsPerPageChanged != null) { - final List availableRowsPerPage = widget.availableRowsPerPage - .where( - (int value) => value <= _rowCount || value == widget.rowsPerPage) - .map>((int value) { - return DropdownMenuItem( - value: value, - child: Text('$value'), - ); - }).toList(); - footerWidgets.addAll([ - Container( - width: 14.0, - ), - Text( - appLocalizationsOf(context).rowsPerPage, - ), - ConstrainedBox( - constraints: const BoxConstraints(minWidth: 56.0), - child: Align( - alignment: AlignmentDirectional.centerEnd, - child: DropdownButtonHideUnderline( - child: DropdownButton( - style: footerTextStyle, - items: availableRowsPerPage.cast>(), - value: widget.rowsPerPage, - onChanged: (rowsPerPage) { - widget.onRowsPerPageChanged?.call(rowsPerPage); - _handleFirst(); - }, - ), - ), - ), - ), - ]); - } - footerWidgets.addAll([ - Container(width: 32.0), - Text( - appLocalizationsOf(context).displayedPaginatedData( - _firstRowIndex + 1, - _firstRowIndex + widget.rowsPerPage, - _rowCount, - ), - ), - Container(width: 32.0), - if (widget.showFirstLastButtons) - IconButton( - iconSize: 16, - icon: Icon( - Icons.skip_previous, - color: widget.arrowHeadColor, - ), - constraints: const BoxConstraints(maxWidth: 20), - padding: EdgeInsets.zero, - tooltip: appLocalizationsOf(context).goToFirst, - onPressed: _firstRowIndex <= 0 ? null : _handleFirst, - ), - IconButton( - iconSize: 16, - icon: Icon( - Icons.chevron_left, - color: widget.arrowHeadColor, - ), - constraints: const BoxConstraints(maxWidth: 20), - padding: EdgeInsets.zero, - tooltip: appLocalizationsOf(context).previous, - onPressed: _firstRowIndex <= 0 ? null : _handlePrevious, - ), - pageRow(_pagesToShow), - IconButton( - iconSize: 16, - icon: Icon( - Icons.chevron_right, - color: widget.arrowHeadColor, - ), - constraints: const BoxConstraints(maxWidth: 20), - padding: EdgeInsets.zero, - tooltip: appLocalizationsOf(context).next, - onPressed: _isNextPageUnavailable() ? null : _handleNext, - ), - if (widget.showFirstLastButtons) - IconButton( - iconSize: 16, - icon: Icon( - Icons.skip_next, - color: widget.arrowHeadColor, - ), - constraints: const BoxConstraints(maxWidth: 20), - padding: EdgeInsets.zero, - tooltip: appLocalizationsOf(context).goToLast, - onPressed: _isNextPageUnavailable() ? null : _handleLast, - ), - Container(width: 14.0), - ]); - - return Scrollbar( - controller: scrollController, - child: SingleChildScrollView( - controller: scrollController, - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - ConstrainedBox( - constraints: const BoxConstraints(minWidth: double.infinity), - child: DataTable( - key: widget.tableKey, - columns: widget.columns, - showCheckboxColumn: false, - sortColumnIndex: widget.sortColumnIndex, - sortAscending: widget.sortAscending, - dataRowHeight: widget.dataRowHeight, - headingRowHeight: widget.headingRowHeight, - horizontalMargin: widget.horizontalMargin, - checkboxHorizontalMargin: widget.checkboxHorizontalMargin, - columnSpacing: widget.columnSpacing, - showBottomBorder: true, - rows: _getRows(_firstRowIndex, widget.rowsPerPage), - ), - ), - DefaultTextStyle( - style: footerTextStyle, - child: IconTheme.merge( - data: const IconThemeData( - opacity: 0.54, - ), - child: SizedBox( - height: 56.0, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: footerWidgets, - ), - ), - ), - ), - ], - ), - ), - ); - } -} diff --git a/lib/pages/drive_detail/components/drive_detail_breadcrumb_row.dart b/lib/pages/drive_detail/components/drive_detail_breadcrumb_row.dart index 1e4d2369d5..2cd1206a8d 100644 --- a/lib/pages/drive_detail/components/drive_detail_breadcrumb_row.dart +++ b/lib/pages/drive_detail/components/drive_detail_breadcrumb_row.dart @@ -16,12 +16,12 @@ class DriveDetailBreadcrumbRow extends StatelessWidget { const mobileBreadcrumbCount = 2; const desktopBreadcrumbCount = 4; - return ScreenTypeLayout( - desktop: _buildBreadcrumbs( + return ScreenTypeLayout.builder( + desktop: (context) => _buildBreadcrumbs( desktopBreadcrumbCount, context, ), - mobile: _buildBreadcrumbs( + mobile: (context) => _buildBreadcrumbs( mobileBreadcrumbCount, context, ), diff --git a/lib/pages/drive_detail/components/fs_entry_preview_widget.dart b/lib/pages/drive_detail/components/fs_entry_preview_widget.dart index ebf0ceb0a7..62cc977745 100644 --- a/lib/pages/drive_detail/components/fs_entry_preview_widget.dart +++ b/lib/pages/drive_detail/components/fs_entry_preview_widget.dart @@ -60,7 +60,7 @@ class _VideoPlayerWidgetState extends State { @override void initState() { - debugPrint('Initializing video player: ${widget.videoUrl}'); + logger.d('Initializing video player: ${widget.videoUrl}'); super.initState(); _videoPlayerController = VideoPlayerController.network(widget.videoUrl); _chewieController = ChewieController( @@ -86,7 +86,7 @@ class _VideoPlayerWidgetState extends State { @override void dispose() { - debugPrint('Disposing video player'); + logger.d('Disposing video player'); _chewieController.videoPlayerController.dispose(); _chewieController.dispose(); @@ -95,7 +95,7 @@ class _VideoPlayerWidgetState extends State { @override Widget build(BuildContext context) { - debugPrint('Building video player'); + logger.d('Building video player'); return VisibilityDetector( key: const Key('video-player'), onVisibilityChanged: (VisibilityInfo info) { diff --git a/lib/pages/drive_detail/drive_detail_page.dart b/lib/pages/drive_detail/drive_detail_page.dart index eb91997172..172b809b9b 100644 --- a/lib/pages/drive_detail/drive_detail_page.dart +++ b/lib/pages/drive_detail/drive_detail_page.dart @@ -26,6 +26,7 @@ import 'package:ardrive/theme/theme.dart'; import 'package:ardrive/utils/app_localizations_wrapper.dart'; import 'package:ardrive/utils/compare_alphabetically_and_natural.dart'; import 'package:ardrive/utils/filesize.dart'; +import 'package:ardrive/utils/logger/logger.dart'; import 'package:ardrive/utils/open_url.dart'; import 'package:ardrive/utils/size_constants.dart'; import 'package:ardrive/utils/user_utils.dart'; @@ -83,15 +84,15 @@ class _DriveDetailPageState extends State { state.currentDrive.isPublic && !state.hasFoldersSelected; - return ScreenTypeLayout( - desktop: _desktopView( + return ScreenTypeLayout.builder( + desktop: (context) => _desktopView( isDriveOwner: isOwner, state: state, hasSubfolders: hasSubfolders, hasFiles: hasFiles, canDownloadMultipleFiles: canDownloadMultipleFiles, ), - mobile: Scaffold( + mobile: (context) => Scaffold( drawerScrimColor: Colors.transparent, drawer: const AppSideBar(), appBar: (state.showSelectedItemDetails && @@ -522,48 +523,8 @@ class _DriveDetailPageState extends State { ); } - Widget _buildGridView(DriveDetailLoadSuccess state, bool hasSubfolders, - bool hasFiles, List items) { - return GridView.builder( - controller: _scrollController, - gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 2, - childAspectRatio: 1, - mainAxisSpacing: 8, - crossAxisSpacing: 8, - ), - itemCount: items.length, - itemBuilder: (context, index) { - final file = items[index]; - return ArDriveGridItem( - item: file, - drive: state.currentDrive, - ); - }, - ); - } - _buildItem(String name, ArDriveIcon icon) { return ArDriveDropdownItemTile(name: name, icon: icon); - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 41.0), - child: ConstrainedBox( - constraints: const BoxConstraints( - minWidth: 375, - ), - child: Row( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - name, - style: ArDriveTypography.body.buttonNormalBold(), - ), - icon, - ], - ), - ), - ); } } diff --git a/lib/pages/no_drives/no_drives_page.dart b/lib/pages/no_drives/no_drives_page.dart index 73c0a1fd26..8123e562a5 100644 --- a/lib/pages/no_drives/no_drives_page.dart +++ b/lib/pages/no_drives/no_drives_page.dart @@ -16,8 +16,8 @@ class NoDrivesPage extends StatelessWidget { }) : super(key: key); @override - Widget build(BuildContext context) => ScreenTypeLayout( - desktop: Padding( + Widget build(BuildContext context) => ScreenTypeLayout.builder( + desktop: (context) => Padding( padding: const EdgeInsets.only(top: 32, right: 16), child: Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -39,7 +39,7 @@ class NoDrivesPage extends StatelessWidget { ], ), ), - mobile: Scaffold( + mobile: (context) => Scaffold( bottomNavigationBar: BlocBuilder( builder: (context, state) { return const CustomBottomNavigation(); diff --git a/lib/pages/profile_auth/components/profile_auth_shell.dart b/lib/pages/profile_auth/components/profile_auth_shell.dart index e60fab559e..5b7565ff4a 100644 --- a/lib/pages/profile_auth/components/profile_auth_shell.dart +++ b/lib/pages/profile_auth/components/profile_auth_shell.dart @@ -88,8 +88,8 @@ class ProfileAuthShell extends StatelessWidget { ), ], ); - return ScreenTypeLayout( - desktop: Material( + return ScreenTypeLayout.builder( + desktop: (context) => Material( child: Row( children: [ Expanded( @@ -105,7 +105,7 @@ class ProfileAuthShell extends StatelessWidget { ], ), ), - mobile: Scaffold( + mobile: (context) => Scaffold( resizeToAvoidBottomInset: resizeToAvoidBottomInset, body: SingleChildScrollView( child: Padding( diff --git a/lib/pages/shared_file/shared_file_page.dart b/lib/pages/shared_file/shared_file_page.dart index 21e1ce7289..dccda08a40 100644 --- a/lib/pages/shared_file/shared_file_page.dart +++ b/lib/pages/shared_file/shared_file_page.dart @@ -60,8 +60,8 @@ class SharedFilePage extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ - ScreenTypeLayout( - desktop: Row( + ScreenTypeLayout.builder( + desktop: (context) => Row( children: [ ArDriveImage( image: AssetImage( @@ -75,7 +75,7 @@ class SharedFilePage extends StatelessWidget { ), ], ), - mobile: Row( + mobile: (context) => Row( mainAxisAlignment: MainAxisAlignment.center, children: [ ArDriveImage( @@ -196,8 +196,8 @@ class SharedFilePage extends StatelessWidget { return Padding( padding: const EdgeInsets.all(24.0), - child: ScreenTypeLayout( - desktop: Row( + child: ScreenTypeLayout.builder( + desktop: (context) => Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Flexible( @@ -215,7 +215,7 @@ class SharedFilePage extends StatelessWidget { } ], ), - mobile: SingleChildScrollView( + mobile: (context) => SingleChildScrollView( primary: true, child: SizedBox( child: Column( diff --git a/lib/pst/pst_contract_data_builder.dart b/lib/pst/pst_contract_data_builder.dart index 3a8887a22a..dcd3af0094 100644 --- a/lib/pst/pst_contract_data_builder.dart +++ b/lib/pst/pst_contract_data_builder.dart @@ -1,5 +1,6 @@ import 'package:ardrive/pst/pst_contract_data.dart'; import 'package:ardrive/types/arweave_address.dart'; +import 'package:ardrive/utils/logger/logger.dart'; import 'package:equatable/equatable.dart'; class CommunityContractDataBuilder { @@ -208,7 +209,7 @@ class CommunityContractDataBuilder { default: // ignore: avoid_print - print('Ignoring unknown field: .votes[number].$key = $value'); + logger.i('Ignoring unknown field: .votes[number].$key = $value'); break; } }); @@ -276,7 +277,7 @@ class CommunityContractDataBuilder { default: // ignore: avoid_print - print( + logger.i( 'Ignoring unknown field: .settings[number][1] ($key: $value)', ); break; @@ -361,7 +362,7 @@ class CommunityContractDataBuilder { break; default: // ignore: avoid_print - print('Ignoring unknown field .vault[address][number].$key'); + logger.i('Ignoring unknown field .vault[address][number].$key'); } }); } diff --git a/lib/services/arweave/arweave_service.dart b/lib/services/arweave/arweave_service.dart index 7d8fffcfae..7b90bc6d59 100644 --- a/lib/services/arweave/arweave_service.dart +++ b/lib/services/arweave/arweave_service.dart @@ -8,7 +8,6 @@ import 'package:ardrive/entities/entities.dart'; import 'package:ardrive/entities/string_types.dart'; import 'package:ardrive/services/arweave/error/gateway_error.dart'; import 'package:ardrive/services/services.dart'; -import 'package:ardrive/utils/extensions.dart'; import 'package:ardrive/utils/graphql_retry.dart'; import 'package:ardrive/utils/http_retry.dart'; import 'package:ardrive/utils/internet_checker.dart'; @@ -163,8 +162,8 @@ class ArweaveService { break; } } catch (e) { - print('Error fetching snapshots for drive $driveId - $e'); - print('This drive and ones after will fall back to GQL'); + logger.i('Error fetching snapshots for drive $driveId - $e'); + logger.i('This drive and ones after will fall back to GQL'); break; } } @@ -795,8 +794,7 @@ class ArweaveService { ), ); } on EntityTransactionParseException catch (parseException) { - 'Failed to parse transaction with id ${parseException.transactionId}' - .logError(); + logger.e('Failed to parse transaction with id ${parseException.transactionId}'); } } diff --git a/lib/services/authentication/biometric_authentication.dart b/lib/services/authentication/biometric_authentication.dart index 07ef24f8c3..b9540bf1ed 100644 --- a/lib/services/authentication/biometric_authentication.dart +++ b/lib/services/authentication/biometric_authentication.dart @@ -3,8 +3,8 @@ import 'dart:io'; import 'package:app_settings/app_settings.dart'; import 'package:ardrive/utils/local_key_value_store.dart'; +import 'package:ardrive/utils/logger/logger.dart'; import 'package:ardrive/utils/secure_key_value_store.dart'; -import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:local_auth/error_codes.dart' as error_codes; import 'package:local_auth/local_auth.dart'; @@ -93,8 +93,8 @@ class BiometricAuthentication { return authenticated; } on PlatformException catch (e) { - debugPrint(e.toString()); - debugPrint(e.code); + logger.e(e.toString()); + logger.e(e.code); disable(); switch (e.code) { diff --git a/lib/services/turbo/payment_service.dart b/lib/services/turbo/payment_service.dart index 7378463b61..2f5a4bcb99 100644 --- a/lib/services/turbo/payment_service.dart +++ b/lib/services/turbo/payment_service.dart @@ -73,9 +73,9 @@ class PaymentService { 'x-public-key': publicKey, }, ).onError((ArDriveHTTPException error, stackTrace) { - logger.e('Error getting balance', error, stackTrace); - + logger.e('error getting balance', error, stackTrace); if (error.statusCode == 404) { + logger.e('user not found'); throw TurboUserNotFound(); } diff --git a/lib/turbo/topup/blocs/payment_form/payment_form_bloc.dart b/lib/turbo/topup/blocs/payment_form/payment_form_bloc.dart index 0c8f842035..cf18f06280 100644 --- a/lib/turbo/topup/blocs/payment_form/payment_form_bloc.dart +++ b/lib/turbo/topup/blocs/payment_form/payment_form_bloc.dart @@ -27,8 +27,15 @@ class PaymentFormBloc extends Bloc { emit(PaymentFormQuoteLoaded(priceEstimate, _expirationTimeInSeconds(turbo.maxQuoteExpirationDate))); - } catch (e) { - logger.e(e); + } catch (e, s) { + logger.e('Error upading the quote.', e, s); + + emit( + PaymentFormQuoteLoadFailure( + state.priceEstimate, + _expirationTimeInSeconds(turbo.maxQuoteExpirationDate), + ), + ); } } }, diff --git a/lib/turbo/topup/blocs/payment_form/payment_form_state.dart b/lib/turbo/topup/blocs/payment_form/payment_form_state.dart index 7136550f01..1973535a5e 100644 --- a/lib/turbo/topup/blocs/payment_form/payment_form_state.dart +++ b/lib/turbo/topup/blocs/payment_form/payment_form_state.dart @@ -38,3 +38,13 @@ class PaymentFormQuoteLoaded extends PaymentFormState { @override List get props => []; } + +class PaymentFormQuoteLoadFailure extends PaymentFormState { + const PaymentFormQuoteLoadFailure( + super.priceEstimate, + super.quoteExpirationTime, + ); + + @override + List get props => [priceEstimate, quoteExpirationTimeInSeconds]; +} diff --git a/lib/turbo/topup/blocs/payment_review/payment_review_bloc.dart b/lib/turbo/topup/blocs/payment_review/payment_review_bloc.dart index 7011496ff4..d80018857a 100644 --- a/lib/turbo/topup/blocs/payment_review/payment_review_bloc.dart +++ b/lib/turbo/topup/blocs/payment_review/payment_review_bloc.dart @@ -39,14 +39,7 @@ class PaymentReviewBloc extends Bloc { PaymentReviewFinishPayment event, ) async { try { - emit( - PaymentReviewLoading( - credits: _getCreditsFromPaymentModel(), - subTotal: _getSubTotalFromPaymentModel(), - total: _getTotalFromPaymentModel(), - quoteExpirationDate: _quoteExpirationDate!, - ), - ); + _emitPaymentReviewLoading(emit); logger.d(event.paymentUserInformation.toString()); @@ -55,97 +48,42 @@ class PaymentReviewBloc extends Bloc { ); if (paymentStatus == PaymentStatus.success) { - emit( - PaymentReviewPaymentSuccess( - credits: _getCreditsFromPaymentModel(), - subTotal: _getSubTotalFromPaymentModel(), - total: _getTotalFromPaymentModel(), - quoteExpirationDate: _quoteExpirationDate!, - ), - ); + _emitPaymentSuccess(emit); } else { - emit( - PaymentReviewPaymentError( - credits: _getCreditsFromPaymentModel(), - subTotal: _getSubTotalFromPaymentModel(), - total: _getTotalFromPaymentModel(), - errorType: TurboErrorType.unknown, - quoteExpirationDate: _quoteExpirationDate!, - ), - ); + _emitPaymentReviewError(emit); } } catch (e) { - emit( - PaymentReviewPaymentError( - credits: _getCreditsFromPaymentModel(), - subTotal: _getSubTotalFromPaymentModel(), - total: _getTotalFromPaymentModel(), - errorType: TurboErrorType.unknown, - quoteExpirationDate: _quoteExpirationDate!, - ), - ); + _emitPaymentReviewError(emit); } } Future _handlePaymentReviewRefreshQuote( Emitter emit) async { try { - emit( - PaymentReviewLoadingQuote( - // paymentUserInformation: state.paymentUserInformation, - credits: _getCreditsFromPaymentModel(), - subTotal: _getSubTotalFromPaymentModel(), - total: _getTotalFromPaymentModel(), - quoteExpirationDate: _quoteExpirationDate!, - ), - ); + _emitPaymentReviewLoadingQuote(emit); await _createPaymentIntent(); - emit( - PaymentReviewQuoteLoaded( - credits: _getCreditsFromPaymentModel(), - subTotal: _getSubTotalFromPaymentModel(), - total: _getTotalFromPaymentModel(), - quoteExpirationDate: _quoteExpirationDate!, - ), - ); + _emitPaymentReviewQuoteLoaded(emit); } catch (e) { - emit( - PaymentReviewQuoteError( - errorType: TurboErrorType.unknown, - quoteExpirationDate: _quoteExpirationDate!, - credits: _getCreditsFromPaymentModel(), - subTotal: _getSubTotalFromPaymentModel(), - total: _getTotalFromPaymentModel(), - ), - ); + _emitPaymentReviewQuoteError(emit); } } Future _handlePaymentReviewLoadPaymentModel( Emitter emit) async { try { - emit(const PaymentReviewLoadingPaymentModel()); + _emitPaymentReviewLoadingPaymentModel(emit); await _createPaymentIntent(); - await Future.delayed(const Duration(seconds: 1)); + // await Future.delayed(const Duration(seconds: 1)); - emit( - PaymentReviewPaymentModelLoaded( - quoteExpirationDate: _quoteExpirationDate!, - credits: _getCreditsFromPaymentModel(), - subTotal: _getSubTotalFromPaymentModel(), - total: _getTotalFromPaymentModel(), - ), - ); - } catch (e) { - logger.e('Error loading payment model: $e'); + _emitStatePaymentReviewPaymentModelLoaded(emit); + } catch (e, s) { + logger.e('Error loading payment model', e, s); - emit(const PaymentReviewErrorLoadingPaymentModel( - errorType: TurboErrorType.unknown, - )); + _emitPaymentReviewErrorLoadingPaymentModel(emit); } } @@ -158,6 +96,100 @@ class PaymentReviewBloc extends Bloc { _quoteExpirationDate = turbo.quoteExpirationDate; } + void _emitStatePaymentReviewPaymentModelLoaded( + Emitter emit) { + emit( + PaymentReviewPaymentModelLoaded( + quoteExpirationDate: _quoteExpirationDate!, + credits: _getCreditsFromPaymentModel(), + subTotal: _getSubTotalFromPaymentModel(), + total: _getTotalFromPaymentModel(), + ), + ); + } + + void _emitPaymentReviewLoadingQuote(Emitter emi) { + emi( + PaymentReviewLoadingQuote( + credits: _getCreditsFromPaymentModel(), + subTotal: _getSubTotalFromPaymentModel(), + total: _getTotalFromPaymentModel(), + quoteExpirationDate: _quoteExpirationDate!, + ), + ); + } + + void _emitPaymentReviewLoading(Emitter emit) { + emit( + PaymentReviewLoading( + credits: _getCreditsFromPaymentModel(), + subTotal: _getSubTotalFromPaymentModel(), + total: _getTotalFromPaymentModel(), + quoteExpirationDate: _quoteExpirationDate!, + ), + ); + } + + void _emitPaymentReviewQuoteLoaded(Emitter emit) { + emit( + PaymentReviewQuoteLoaded( + credits: _getCreditsFromPaymentModel(), + subTotal: _getSubTotalFromPaymentModel(), + total: _getTotalFromPaymentModel(), + quoteExpirationDate: _quoteExpirationDate!, + ), + ); + } + + void _emitPaymentReviewError(Emitter emit) { + emit( + PaymentReviewPaymentError( + errorType: TurboErrorType.unknown, + quoteExpirationDate: _quoteExpirationDate!, + credits: _getCreditsFromPaymentModel(), + subTotal: _getSubTotalFromPaymentModel(), + total: _getTotalFromPaymentModel(), + ), + ); + } + + void _emitPaymentReviewQuoteError(Emitter emit) { + emit( + PaymentReviewQuoteError( + errorType: TurboErrorType.unknown, + quoteExpirationDate: _quoteExpirationDate!, + credits: _getCreditsFromPaymentModel(), + subTotal: _getSubTotalFromPaymentModel(), + total: _getTotalFromPaymentModel(), + ), + ); + } + + void _emitPaymentSuccess(Emitter emit) { + emit( + PaymentReviewPaymentSuccess( + credits: _getCreditsFromPaymentModel(), + subTotal: _getSubTotalFromPaymentModel(), + total: _getTotalFromPaymentModel(), + quoteExpirationDate: _quoteExpirationDate!, + ), + ); + } + + void _emitPaymentReviewErrorLoadingPaymentModel(Emitter emit) { + emit( + const PaymentReviewErrorLoadingPaymentModel( + errorType: TurboErrorType.unknown, + ), + ); + } + + void _emitPaymentReviewLoadingPaymentModel(Emitter emit) { + emit( + const PaymentReviewLoadingPaymentModel(), + ); + } + String _getCreditsFromPaymentModel() => convertCreditsToLiteralString( BigInt.from(int.parse(_paymentModel!.topUpQuote.winstonCreditAmount))); diff --git a/lib/turbo/topup/blocs/payment_review/payment_review_event.dart b/lib/turbo/topup/blocs/payment_review/payment_review_event.dart index 5048616ba7..3fc4ebe504 100644 --- a/lib/turbo/topup/blocs/payment_review/payment_review_event.dart +++ b/lib/turbo/topup/blocs/payment_review/payment_review_event.dart @@ -15,13 +15,10 @@ class PaymentReviewFinishPayment extends PaymentReviewEvent { }); @override - List get props => []; + List get props => [paymentUserInformation]; } -class PaymentReviewRefreshQuote extends PaymentReviewEvent { - @override - List get props => []; -} +class PaymentReviewRefreshQuote extends PaymentReviewEvent {} class PaymentReviewLoadPaymentModel extends PaymentReviewEvent { @override diff --git a/lib/turbo/topup/blocs/topup_estimation_bloc.dart b/lib/turbo/topup/blocs/topup_estimation_bloc.dart index ec30c3f2f9..b10d5b0339 100644 --- a/lib/turbo/topup/blocs/topup_estimation_bloc.dart +++ b/lib/turbo/topup/blocs/topup_estimation_bloc.dart @@ -38,6 +38,7 @@ class TurboTopUpEstimationBloc (event, emit) async { if (event is LoadInitialData) { try { + emit(EstimationLoading()); logger.i('initializing the estimation view'); logger.i('getting the balance'); await _getBalance(); @@ -48,6 +49,7 @@ class TurboTopUpEstimationBloc currentAmount: 0, currentCurrency: currentCurrency, currentDataUnit: currentDataUnit, + shouldRethrow: true, ); } catch (e, s) { logger.e('error initializing the estimation view', e, s); @@ -111,35 +113,46 @@ class TurboTopUpEstimationBloc required int currentAmount, required String currentCurrency, required FileSizeUnit currentDataUnit, + bool shouldRethrow = false, }) async { - emit(EstimationLoading()); - - final priceEstimate = await turbo.computePriceEstimate( - currentAmount: currentAmount, - currentCurrency: currentCurrency, - currentDataUnit: currentDataUnit, - ); - - final estimatedStorageForBalance = - await turbo.computeStorageEstimateForCredits( - credits: _balance, - outputDataUnit: currentDataUnit, - ); - - logger.i('selected amount: ${priceEstimate.priceInCurrency}'); - emit( - EstimationLoaded( - balance: _balance, - estimatedStorageForBalance: - estimatedStorageForBalance.toStringAsFixed(2), - selectedAmount: priceEstimate.priceInCurrency, - creditsForSelectedAmount: priceEstimate.credits, - estimatedStorageForSelectedAmount: - priceEstimate.estimatedStorage.toStringAsFixed(2), - currencyUnit: currentCurrency, - dataUnit: currentDataUnit, - ), - ); + try { + emit(EstimationLoading()); + final priceEstimate = await turbo.computePriceEstimate( + currentAmount: currentAmount, + currentCurrency: currentCurrency, + currentDataUnit: currentDataUnit, + ); + + final estimatedStorageForBalance = + await turbo.computeStorageEstimateForCredits( + credits: _balance, + outputDataUnit: currentDataUnit, + ); + + logger.i('selected amount: ${priceEstimate.priceInCurrency}'); + + emit( + EstimationLoaded( + balance: _balance, + estimatedStorageForBalance: + estimatedStorageForBalance.toStringAsFixed(2), + selectedAmount: priceEstimate.priceInCurrency, + creditsForSelectedAmount: priceEstimate.credits, + estimatedStorageForSelectedAmount: + priceEstimate.estimatedStorage.toStringAsFixed(2), + currencyUnit: currentCurrency, + dataUnit: currentDataUnit, + ), + ); + } catch (e, s) { + logger.e('Error calculating the estimation', e, s); + + if (shouldRethrow) { + rethrow; + } + + emit(EstimationLoadError()); + } } Future _getBalance() async { @@ -147,7 +160,7 @@ class TurboTopUpEstimationBloc _balance = await turbo.getBalance(); } catch (e) { logger.e(e); - _balance = BigInt.zero; + rethrow; } } } diff --git a/lib/turbo/topup/views/topup_payment_form.dart b/lib/turbo/topup/views/topup_payment_form.dart index f66eb0263a..d76c55d133 100644 --- a/lib/turbo/topup/views/topup_payment_form.dart +++ b/lib/turbo/topup/views/topup_payment_form.dart @@ -47,9 +47,9 @@ class TurboPaymentFormViewState extends State { return ArDriveTheme( key: const ValueKey('turbo_payment_form'), themeData: textTheme, - child: ScreenTypeLayout( - mobile: _mobileView(context), - desktop: _desktopView(context, textTheme.textFieldTheme), + child: ScreenTypeLayout.builder( + mobile: (context) => _mobileView(context), + desktop: (context) => _desktopView(context, textTheme.textFieldTheme), ), ); } @@ -120,10 +120,6 @@ class TurboPaymentFormViewState extends State { ); } - Widget _quoteRefresh(BuildContext context) { - return const QuoteRefreshWidget(); - } - Widget _credits(BuildContext context) { return SizedBox( width: double.maxFinite, @@ -167,13 +163,11 @@ class TurboPaymentFormViewState extends State { ], ), ), - Flexible( + const Flexible( flex: 1, child: Padding( - padding: const EdgeInsets.only(left: 8.0), - child: _quoteRefresh( - context, - ), + padding: EdgeInsets.only(left: 8.0), + child: QuoteRefreshWidget(), ), ), ], @@ -240,16 +234,14 @@ class TurboPaymentFormViewState extends State { ArDriveButton( maxHeight: 44, maxWidth: 143, - // TODO: localize text: 'Review', fontStyle: ArDriveTypography.body.buttonLargeBold( color: Colors.white, ), isDisabled: !(card?.complete ?? false), onPressed: () { - // TODO: check payment-form-and-checkout branch context.read().add( - TurboTopUpShowPaymentReviewView(), + const TurboTopUpShowPaymentReviewView(), ); }, ), @@ -584,20 +576,26 @@ class QuoteRefreshWidgetState extends State { mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, children: [ - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - // TODO: localize - Text( - 'Quote updates in ', - style: ArDriveTypography.body.captionBold( - color: - ArDriveTheme.of(context).themeData.colors.themeFgDisabled, - ), - ), - BlocBuilder( - builder: (context, state) { - return TimerWidget( + BlocBuilder( + builder: (context, state) { + if (state is PaymentFormQuoteLoadFailure) { + return const SizedBox(); + } + + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + // TODO: localize + Text( + 'Quote updates in ', + style: ArDriveTypography.body.captionBold( + color: ArDriveTheme.of(context) + .themeData + .colors + .themeFgDisabled, + ), + ), + TimerWidget( key: state is PaymentFormQuoteLoaded ? const ValueKey('reset_timer') : null, @@ -608,57 +606,98 @@ class QuoteRefreshWidgetState extends State { .read() .add(PaymentFormUpdateQuote()); }, - ); - }, - ), - ], + ), + ], + ); + }, ), const SizedBox(height: 4), - BlocBuilder( - builder: (context, state) { - if (state is PaymentFormLoadingQuote) { - return const SizedBox( - height: 16, - width: 16, - child: CircularProgressIndicator( - strokeWidth: 2, - ), - ); - } + Flexible( + child: BlocBuilder( + builder: (context, state) { + if (state is PaymentFormLoadingQuote) { + return const SizedBox( + height: 16, + width: 16, + child: CircularProgressIndicator( + strokeWidth: 2, + ), + ); + } - return ArDriveClickArea( - child: GestureDetector( - onTap: () { - context - .read() - .add(PaymentFormUpdateQuote()); - }, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - ArDriveIcons.refresh( - color: ArDriveTheme.of(context) - .themeData - .colors - .themeFgDisabled, - size: 16, + if (state is PaymentFormQuoteLoadFailure) { + return ArDriveClickArea( + child: GestureDetector( + onTap: () { + context + .read() + .add(PaymentFormUpdateQuote()); + }, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Flexible( + child: Text( + 'Unable to update quote. Please try again.', + style: ArDriveTypography.body.captionBold( + color: ArDriveTheme.of(context) + .themeData + .colors + .themeErrorDefault, + ), + ), + ), + const SizedBox(width: 4), + ArDriveIcons.refresh( + color: ArDriveTheme.of(context) + .themeData + .colors + .themeErrorDefault, + size: 16, + ), + ], + ), ), - const SizedBox(width: 4), - // TODO: localize - Text( - 'Refresh', - style: ArDriveTypography.body.captionBold( + ), + ); + } + + return ArDriveClickArea( + child: GestureDetector( + onTap: () { + context + .read() + .add(PaymentFormUpdateQuote()); + }, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ArDriveIcons.refresh( color: ArDriveTheme.of(context) .themeData .colors .themeFgDisabled, + size: 16, ), - ), - ], + const SizedBox(width: 4), + // TODO: localize + Text( + 'Refresh', + style: ArDriveTypography.body.captionBold( + color: ArDriveTheme.of(context) + .themeData + .colors + .themeFgDisabled, + ), + ), + ], + ), ), - ), - ); - }, + ); + }, + ), ) ], ), diff --git a/lib/turbo/topup/views/topup_review_view.dart b/lib/turbo/topup/views/topup_review_view.dart index dacbe1bce2..7961830cc2 100644 --- a/lib/turbo/topup/views/topup_review_view.dart +++ b/lib/turbo/topup/views/topup_review_view.dart @@ -79,6 +79,7 @@ class _TurboReviewViewState extends State { }, ), ), + barrierDismissible: false, barrierColor: ArDriveTheme.of(context) .themeData .colors @@ -101,7 +102,7 @@ class _TurboReviewViewState extends State { }, ), ), - barrierDismissible: true, + barrierDismissible: false, barrierColor: ArDriveTheme.of(context) .themeData .colors @@ -185,6 +186,10 @@ class _TurboReviewViewState extends State { ), BlocBuilder( + buildWhen: (previous, current) { + return current + is PaymentReviewPaymentModelLoaded; + }, builder: (context, state) { if (state is PaymentReviewPaymentModelLoaded) { @@ -239,6 +244,10 @@ class _TurboReviewViewState extends State { const Spacer(), BlocBuilder( + buildWhen: (previous, current) { + return current + is PaymentReviewPaymentModelLoaded; + }, builder: (context, state) { if (state is PaymentReviewPaymentModelLoaded) { @@ -253,7 +262,6 @@ class _TurboReviewViewState extends State { } return Text( - // '\$${state.total}', '\$0', style: ArDriveTypography.body .buttonNormalBold() @@ -389,7 +397,7 @@ class _TurboReviewViewState extends State { }, ), const Spacer(), - RefreshQuoteButton(), + const RefreshQuoteButton(), ], ), ); @@ -549,7 +557,7 @@ class _TurboReviewViewState extends State { class RefreshButton extends StatefulWidget { final void Function() onPressed; - RefreshButton({required this.onPressed}); + RefreshButton({super.key, required this.onPressed}); @override _RefreshButtonState createState() => _RefreshButtonState(); @@ -621,11 +629,13 @@ class _RefreshButtonState extends State } class RefreshQuoteButton extends StatefulWidget { + const RefreshQuoteButton({super.key}); + @override - _RefreshQuoteButtonState createState() => _RefreshQuoteButtonState(); + RefreshQuoteButtonState createState() => RefreshQuoteButtonState(); } -class _RefreshQuoteButtonState extends State +class RefreshQuoteButtonState extends State with SingleTickerProviderStateMixin { late AnimationController _controller; late Animation _fadeAndSizeAnimation; diff --git a/lib/turbo/topup/views/turbo_error_view.dart b/lib/turbo/topup/views/turbo_error_view.dart index 21f6d9f43a..6b693e4aea 100644 --- a/lib/turbo/topup/views/turbo_error_view.dart +++ b/lib/turbo/topup/views/turbo_error_view.dart @@ -23,7 +23,7 @@ class TurboErrorView extends StatelessWidget { case TurboErrorType.server: return 'The payment was not successful. Please check your card information and try again.'; case TurboErrorType.fetchPaymentIntentFailed: - return 'We could not load the payment check-out. Please try again.'; + return 'Payment processor is currently unavailable, please try again later'; case TurboErrorType.fetchEstimationInformationFailed: return 'Error loading information. Please try again.'; default: diff --git a/lib/turbo/turbo.dart b/lib/turbo/turbo.dart index c624b72824..d50fe9a759 100644 --- a/lib/turbo/turbo.dart +++ b/lib/turbo/turbo.dart @@ -234,7 +234,16 @@ class TurboBalanceRetriever { }); Future getBalance(Wallet wallet) async { - return paymentService.getBalance(wallet: wallet); + try { + final balance = await paymentService.getBalance(wallet: wallet); + return balance; + } catch (e) { + if (e is TurboUserNotFound) { + logger.e('Error getting balance: $e'); + return BigInt.zero; + } + rethrow; + } } } diff --git a/lib/user/repositories/user_repository.dart b/lib/user/repositories/user_repository.dart index 11af0a88b7..573f2e96af 100644 --- a/lib/user/repositories/user_repository.dart +++ b/lib/user/repositories/user_repository.dart @@ -2,8 +2,8 @@ import 'package:ardrive/entities/profile_types.dart'; import 'package:ardrive/models/daos/daos.dart'; import 'package:ardrive/services/arweave/arweave.dart'; import 'package:ardrive/user/user.dart'; +import 'package:ardrive/utils/logger/logger.dart'; import 'package:arweave/arweave.dart'; -import 'package:flutter/foundation.dart'; abstract class UserRepository { Future hasUser(); @@ -54,7 +54,7 @@ class _UserRepository implements UserRepository { ), ); - debugPrint('Loaded user: ${user.walletAddress}'); + logger.d('Loaded user: ${user.walletAddress}'); return user; } @@ -62,7 +62,7 @@ class _UserRepository implements UserRepository { @override Future saveUser( String password, ProfileType profileType, Wallet wallet) async { - debugPrint('Saving user'); + logger.d('Saving user'); await _profileDao.addProfile( // FIXME: This is a hack to get the username from the user object diff --git a/lib/utils/extensions.dart b/lib/utils/extensions.dart deleted file mode 100644 index c80c3cd11e..0000000000 --- a/lib/utils/extensions.dart +++ /dev/null @@ -1,8 +0,0 @@ -extension StringExtensions on String { - String logError() { - // TODO: Log here with Sentry or whatever we end up using - // ignore: avoid_print - print(this); - return this; - } -} diff --git a/lib/utils/graphql_retry.dart b/lib/utils/graphql_retry.dart index 17bd2bb688..7492f5720a 100644 --- a/lib/utils/graphql_retry.dart +++ b/lib/utils/graphql_retry.dart @@ -1,6 +1,6 @@ import 'package:ardrive/utils/exceptions.dart'; -import 'package:ardrive/utils/extensions.dart'; import 'package:ardrive/utils/internet_checker.dart'; +import 'package:ardrive/utils/logger/logger.dart'; import 'package:artemis/client.dart'; import 'package:artemis/schema/graphql_query.dart'; import 'package:artemis/schema/graphql_response.dart'; @@ -26,11 +26,10 @@ class GraphQLRetry { maxAttempts: maxAttempts, onRetry: (exception) { onRetry?.call(exception); - ''' + logger.i(''' Retrying Query: ${query.toString()}\n On Exception: ${exception.toString()} - ''' - .logError(); + '''); }, ); @@ -38,12 +37,11 @@ class GraphQLRetry { } catch (e) { final isConnected = await _internetChecker.isConnected(); - ''' + logger.e(''' Fatal error while querying: ${query.operationName}\n Number of retries exceeded. Exception: ${e.toString()} - ''' - .logError(); + '''); if (!isConnected) { throw NoConnectionException(); diff --git a/lib/utils/http_retry.dart b/lib/utils/http_retry.dart index 18174df48e..0e69b0397a 100644 --- a/lib/utils/http_retry.dart +++ b/lib/utils/http_retry.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:ardrive/utils/logger/logger.dart'; import 'package:ardrive/utils/response_handler.dart'; import 'package:http/http.dart'; import 'package:retry/retry.dart'; @@ -28,7 +29,7 @@ class HttpRetry { responseHandler.handle(response); if (retryAttempts > 0) { - print( + logger.i( 'Succesfully get the response after $retryAttempts attempt(s)'); } diff --git a/lib/utils/logger/logger.dart b/lib/utils/logger/logger.dart index 55058ea0de..2c7135bcdc 100644 --- a/lib/utils/logger/logger.dart +++ b/lib/utils/logger/logger.dart @@ -1,3 +1,13 @@ import 'package:logger/logger.dart'; -final logger = Logger(); +final logger = Logger( + printer: PrettyPrinter( + methodCount: 0, + errorMethodCount: 8, + lineLength: 120, + colors: true, + printEmojis: true, + printTime: true, + noBoxingByDefault: true, + ), +); diff --git a/lib/utils/snapshots/snapshot_item.dart b/lib/utils/snapshots/snapshot_item.dart index fb4843a093..0509aec21c 100644 --- a/lib/utils/snapshots/snapshot_item.dart +++ b/lib/utils/snapshots/snapshot_item.dart @@ -5,6 +5,7 @@ import 'dart:convert'; import 'package:ardrive/entities/string_types.dart'; import 'package:ardrive/services/arweave/arweave.dart'; +import 'package:ardrive/utils/logger/logger.dart'; import 'package:ardrive/utils/snapshots/height_range.dart'; import 'package:ardrive/utils/snapshots/range.dart'; import 'package:ardrive/utils/snapshots/segmented_gql_data.dart'; @@ -73,7 +74,7 @@ abstract class SnapshotItem implements SegmentedGQLData { arweave: arweave, ); } catch (e) { - print('Ignoring snapshot transaction with invalid block range - $e'); + logger.e('Ignoring snapshot transaction with invalid block range - $e'); continue; } @@ -140,6 +141,7 @@ class SnapshotItemOnChain implements SnapshotItem { final ArweaveService _arweave; static final Map> _jsonMetadataCaches = {}; + static final Set allTxs = {}; SnapshotItemOnChain({ required this.blockEnd, @@ -196,7 +198,7 @@ class SnapshotItemOnChain implements SnapshotItem { try { node = DriveHistoryTransaction.fromJson(item['gqlNode']); } catch (e, s) { - print( + logger.i( 'Error while parsing GQLNode from snapshot item ($txId) - $e, $s', ); continue; @@ -231,6 +233,7 @@ class SnapshotItemOnChain implements SnapshotItem { final Cache cache = await _lazilyInitCache(driveId); await cache.put(txId, data); + allTxs.add(txId); return data; } @@ -244,6 +247,10 @@ class SnapshotItemOnChain implements SnapshotItem { return value; } + static Future> getAllCachedTransactionIds() async { + return allTxs.toList(); + } + static Future> _lazilyInitCache(DriveID driveId) async { if (!_jsonMetadataCaches.containsKey(driveId)) { final store = await newMemoryCacheStore(); diff --git a/pubspec.yaml b/pubspec.yaml index 8c4fcb9cc4..70ce6933f6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -3,7 +3,7 @@ description: Secure, permanent storage publish_to: 'none' -version: 2.4.0 +version: 2.4.1 environment: sdk: '>=2.18.5 <3.0.0' diff --git a/test/turbo/blocs/payment_form/payment_form_bloc_test.dart b/test/turbo/blocs/payment_form/payment_form_bloc_test.dart index 7f1bfbaa80..889fa306b6 100644 --- a/test/turbo/blocs/payment_form/payment_form_bloc_test.dart +++ b/test/turbo/blocs/payment_form/payment_form_bloc_test.dart @@ -76,6 +76,7 @@ void main() { }, expect: () => [ isA(), + isA(), ], ); }); diff --git a/test/turbo/blocs/payment_review/payment_review_bloc_test.dart b/test/turbo/blocs/payment_review/payment_review_bloc_test.dart new file mode 100644 index 0000000000..5ed91c1205 --- /dev/null +++ b/test/turbo/blocs/payment_review/payment_review_bloc_test.dart @@ -0,0 +1,371 @@ +import 'package:ardrive/turbo/models/payment_user_information.dart'; +import 'package:ardrive/turbo/topup/blocs/payment_review/payment_review_bloc.dart'; +import 'package:ardrive/turbo/topup/blocs/turbo_topup_flow_bloc.dart'; +import 'package:ardrive/turbo/topup/models/payment_model.dart'; +import 'package:ardrive/turbo/topup/models/price_estimate.dart'; +import 'package:ardrive/turbo/turbo.dart'; +import 'package:bloc_test/bloc_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:test/test.dart'; + +class MockTurbo extends Mock implements Turbo {} + +void main() { + setUpAll(() { + registerFallbackValue(FakePaymentUserInformation()); + }); + + final mockPaymentSession = PaymentSession( + clientSecret: 'clientSecret', + id: 'paymentIntentId', + ); + + final mockTopUpQuote = TopUpQuote( + currencyType: 'usd', + destinationAddress: 'destinationAddress', + destinationAddressType: 'address', + paymentAmount: 100, + paymentProvider: 'stripe', + quoteExpirationDate: DateTime.now().toIso8601String(), + quoteId: 'quoteId', + winstonCreditAmount: '5000', + ); + + group('PaymentReviewBloc', () { + late Turbo mockTurbo; + late PaymentReviewBloc paymentReviewBloc; + late PriceEstimate mockPriceEstimate; + + group('PaymentReviewLoadPaymentModel', () { + setUp(() { + mockTurbo = MockTurbo(); + mockPriceEstimate = PriceEstimate( + priceInCurrency: 100, + credits: BigInt.from(100), + estimatedStorage: 1); + paymentReviewBloc = PaymentReviewBloc(mockTurbo, mockPriceEstimate); + }); + + blocTest( + 'emits [PaymentReviewLoadingPaymentModel, PaymentReviewPaymentModelLoaded]', + build: () => paymentReviewBloc, + act: (bloc) => bloc.add(PaymentReviewLoadPaymentModel()), + setUp: () { + when( + () => mockTurbo.createPaymentIntent( + amount: any(named: 'amount'), + currency: any(named: 'currency'), + ), + ).thenAnswer( + (invocation) => Future.value( + PaymentModel( + paymentSession: mockPaymentSession, + topUpQuote: mockTopUpQuote, + ), + ), + ); + + when(() => mockTurbo.quoteExpirationDate).thenAnswer( + (invocation) => DateTime.now().add(const Duration(minutes: 5))); + }, + expect: () => [ + const PaymentReviewLoadingPaymentModel(), + // You should adjust the expected states and the returned values to your specific use case. + PaymentReviewPaymentModelLoaded( + quoteExpirationDate: DateTime.now(), + total: '10.00', + subTotal: '10.00', + credits: '10', + ), + ], + ); + + blocTest( + 'emits [PaymentReviewLoadingPaymentModel, PaymentReviewErrorLoadingPaymentModel] when ', + build: () => paymentReviewBloc, + act: (bloc) => bloc.add(PaymentReviewLoadPaymentModel()), + setUp: () { + when( + () => mockTurbo.createPaymentIntent( + amount: any(named: 'amount'), + currency: any(named: 'currency'), + ), + ).thenThrow(Exception()); + + when(() => mockTurbo.quoteExpirationDate).thenAnswer( + (invocation) => DateTime.now().add(const Duration(minutes: 5))); + }, + expect: () => [ + const PaymentReviewLoadingPaymentModel(), + const PaymentReviewErrorLoadingPaymentModel( + errorType: TurboErrorType.unknown, + ) + ], + ); + }); + group('PaymentReviewFinishPayment', () { + setUp(() { + mockTurbo = MockTurbo(); + mockPriceEstimate = PriceEstimate( + priceInCurrency: 100, + credits: BigInt.from(100), + estimatedStorage: 1); + paymentReviewBloc = PaymentReviewBloc(mockTurbo, mockPriceEstimate); + }); + + blocTest( + 'emits [PaymentReviewLoadingPaymentModel, PaymentReviewPaymentSuccess]', + build: () => paymentReviewBloc, + act: (bloc) async { + bloc.add(PaymentReviewLoadPaymentModel()); + await Future.delayed(const Duration(milliseconds: 100)); + bloc.add(PaymentReviewFinishPayment( + paymentUserInformation: FakePaymentUserInformation())); + }, + setUp: () { + when( + () => mockTurbo.createPaymentIntent( + amount: any(named: 'amount'), + currency: any(named: 'currency'), + ), + ).thenAnswer( + (invocation) => Future.value( + PaymentModel( + paymentSession: mockPaymentSession, + topUpQuote: mockTopUpQuote), + ), + ); + + when(() => mockTurbo.quoteExpirationDate).thenAnswer( + (invocation) => DateTime.now().add(const Duration(minutes: 5))); + when( + () => mockTurbo.confirmPayment( + userInformation: any(named: 'userInformation'), + ), + ).thenAnswer( + (invocation) => Future.value( + PaymentStatus.success, + ), + ); + + when(() => mockTurbo.quoteExpirationDate).thenAnswer( + (invocation) => DateTime.now().add(const Duration(minutes: 5))); + }, + expect: () => [ + const PaymentReviewLoadingPaymentModel(), + isA(), + isA(), + isA(), + ], + ); + + blocTest( + 'emits [PaymentReviewLoadingPaymentModel, PaymentReviewError] when turbo throws creating the payment intent', + build: () => paymentReviewBloc, + act: (bloc) async { + bloc.add(PaymentReviewLoadPaymentModel()); + await Future.delayed(const Duration(milliseconds: 100)); + bloc.add(PaymentReviewFinishPayment( + paymentUserInformation: FakePaymentUserInformation())); + }, + setUp: () { + when( + () => mockTurbo.createPaymentIntent( + amount: any(named: 'amount'), + currency: any(named: 'currency'), + ), + ).thenAnswer( + (invocation) => Future.value( + PaymentModel( + paymentSession: mockPaymentSession, + topUpQuote: mockTopUpQuote), + ), + ); + + when(() => mockTurbo.quoteExpirationDate).thenAnswer( + (invocation) => DateTime.now().add(const Duration(minutes: 5))); + when( + () => mockTurbo.confirmPayment( + userInformation: any(named: 'userInformation'), + ), + ).thenThrow(Exception()); + + when(() => mockTurbo.quoteExpirationDate).thenAnswer( + (invocation) => DateTime.now().add(const Duration(minutes: 5))); + }, + expect: () => [ + const PaymentReviewLoadingPaymentModel(), + isA(), + isA(), + isA(), + ], + ); + blocTest( + 'emits [PaymentReviewLoadingPaymentModel, PaymentReviewError] when turbo returns a paymetn status failed', + build: () => paymentReviewBloc, + act: (bloc) async { + bloc.add(PaymentReviewLoadPaymentModel()); + await Future.delayed(const Duration(milliseconds: 100)); + bloc.add(PaymentReviewFinishPayment( + paymentUserInformation: FakePaymentUserInformation())); + }, + setUp: () { + when( + () => mockTurbo.createPaymentIntent( + amount: any(named: 'amount'), + currency: any(named: 'currency'), + ), + ).thenAnswer( + (invocation) => Future.value( + PaymentModel( + paymentSession: mockPaymentSession, + topUpQuote: mockTopUpQuote), + ), + ); + + when(() => mockTurbo.quoteExpirationDate).thenAnswer( + (invocation) => DateTime.now().add(const Duration(minutes: 5))); + when( + () => mockTurbo.confirmPayment( + userInformation: any(named: 'userInformation'), + ), + ).thenAnswer((invocation) => Future.value(PaymentStatus.failed)); + + when(() => mockTurbo.quoteExpirationDate).thenAnswer( + (invocation) => DateTime.now().add(const Duration(minutes: 5))); + }, + expect: () => [ + const PaymentReviewLoadingPaymentModel(), + isA(), + isA(), + isA(), + ], + ); + blocTest( + 'emits [PaymentReviewLoadingPaymentModel, PaymentReviewError] when turbo returns a paymetn status quoteExpired', + build: () => paymentReviewBloc, + act: (bloc) async { + bloc.add(PaymentReviewLoadPaymentModel()); + await Future.delayed(const Duration(milliseconds: 100)); + bloc.add(PaymentReviewFinishPayment( + paymentUserInformation: FakePaymentUserInformation())); + }, + setUp: () { + when( + () => mockTurbo.createPaymentIntent( + amount: any(named: 'amount'), + currency: any(named: 'currency'), + ), + ).thenAnswer( + (invocation) => Future.value( + PaymentModel( + paymentSession: mockPaymentSession, + topUpQuote: mockTopUpQuote), + ), + ); + + when(() => mockTurbo.quoteExpirationDate).thenAnswer( + (invocation) => DateTime.now().add(const Duration(minutes: 5))); + when( + () => mockTurbo.confirmPayment( + userInformation: any(named: 'userInformation'), + ), + ).thenAnswer( + (invocation) => Future.value(PaymentStatus.quoteExpired)); + + when(() => mockTurbo.quoteExpirationDate).thenAnswer( + (invocation) => DateTime.now().add(const Duration(minutes: 5))); + }, + expect: () => [ + const PaymentReviewLoadingPaymentModel(), + isA(), + isA(), + isA(), + ], + ); + }); + + group('PaymentReviewRefreshQuote', () { + setUp(() { + mockTurbo = MockTurbo(); + mockPriceEstimate = PriceEstimate( + priceInCurrency: 100, + credits: BigInt.from(100), + estimatedStorage: 1); + paymentReviewBloc = PaymentReviewBloc(mockTurbo, mockPriceEstimate); + }); + blocTest( + 'emits [PaymentReviewLoadingPaymentModel, PaymentReviewPaymentModelLoaded] when turbo returns a payment model', + build: () => paymentReviewBloc, + act: (bloc) async { + bloc.add(PaymentReviewLoadPaymentModel()); + await Future.delayed(const Duration(milliseconds: 100)); + bloc.add(PaymentReviewRefreshQuote()); + }, + setUp: () { + when( + () => mockTurbo.createPaymentIntent( + amount: any(named: 'amount'), + currency: any(named: 'currency'), + ), + ).thenAnswer( + (invocation) => Future.value( + PaymentModel( + paymentSession: mockPaymentSession, + topUpQuote: mockTopUpQuote), + ), + ); + + when(() => mockTurbo.quoteExpirationDate).thenAnswer( + (invocation) => DateTime.now().add(const Duration(minutes: 5))); + }, + expect: () => [ + isA(), + isA(), + isA(), + isA(), + ], + ); + blocTest( + 'emits [PaymentReviewLoadingPaymentModel, PaymentReviewError] when turbo throws an exception', + build: () => paymentReviewBloc, + act: (bloc) async { + when( + () => mockTurbo.createPaymentIntent( + amount: any(named: 'amount'), + currency: any(named: 'currency'), + ), + ).thenAnswer( + (invocation) => Future.value( + PaymentModel( + paymentSession: mockPaymentSession, + topUpQuote: mockTopUpQuote), + ), + ); + bloc.add(PaymentReviewLoadPaymentModel()); + await Future.delayed(const Duration(milliseconds: 100)); + when( + () => mockTurbo.createPaymentIntent( + amount: any(named: 'amount'), + currency: any(named: 'currency'), + ), + ).thenThrow(Exception()); + bloc.add(PaymentReviewRefreshQuote()); + }, + setUp: () { + when(() => mockTurbo.quoteExpirationDate).thenAnswer( + (invocation) => DateTime.now().add(const Duration(minutes: 5))); + }, + expect: () => [ + isA(), + isA(), + isA(), + isA(), + ], + ); + }); + }); +} + +class FakePaymentUserInformation extends Fake + implements PaymentUserInformation {} diff --git a/test/turbo/blocs/topup_estimation_bloc_test.dart b/test/turbo/blocs/topup_estimation_bloc_test.dart index 902fdfa533..e7752cf537 100644 --- a/test/turbo/blocs/topup_estimation_bloc_test.dart +++ b/test/turbo/blocs/topup_estimation_bloc_test.dart @@ -191,6 +191,54 @@ void main() { ), ], ); + blocTest( + 'should emit [EstimationLoading] and [EstimationLoadError] when throws calculating new estimation', + build: () => TurboTopUpEstimationBloc(turbo: mockTurbo), + setUp: () { + when(() => mockTurbo.onPriceEstimateChanged) + .thenAnswer((_) => const Stream.empty()); + final mockPriceEstimate = PriceEstimate( + credits: BigInt.from(10), + priceInCurrency: 10, + estimatedStorage: 1); + + when(() => mockTurbo.getBalance()) + .thenAnswer((_) async => BigInt.from(10)); + when(() => mockTurbo.computePriceEstimate( + currentAmount: 0, + currentCurrency: 'usd', + currentDataUnit: FileSizeUnit.gigabytes, + )).thenAnswer((_) async => mockPriceEstimate); + when(() => mockTurbo.computePriceEstimate( + currentAmount: 0, + currentCurrency: 'eur', + currentDataUnit: FileSizeUnit.gigabytes, + )).thenThrow(Exception()); + when(() => mockTurbo.computeStorageEstimateForCredits( + credits: BigInt.from(10), + outputDataUnit: FileSizeUnit.gigabytes, + )).thenAnswer((_) async => 1); + }, + act: (bloc) async { + bloc.add(LoadInitialData()); + await Future.delayed(const Duration(milliseconds: 500)); + bloc.add(const CurrencyUnitChanged('eur')); + }, + expect: () => [ + EstimationLoading(), + // first loads with usd + EstimationLoaded( + balance: BigInt.from(10), + estimatedStorageForBalance: '1.00', + selectedAmount: 10, + creditsForSelectedAmount: BigInt.from(10), + estimatedStorageForSelectedAmount: '1.00', + currencyUnit: 'usd', + dataUnit: FileSizeUnit.gigabytes, + ), + EstimationLoading(), + EstimationLoadError() + ]); }); group('DataUnitChanged', () { @@ -199,6 +247,7 @@ void main() { build: () => TurboTopUpEstimationBloc(turbo: mockTurbo), act: (bloc) async { bloc.add(LoadInitialData()); + await Future.delayed(const Duration(milliseconds: 500)); bloc.add(const DataUnitChanged(FileSizeUnit.kilobytes)); }, setUp: () { @@ -245,6 +294,8 @@ void main() { currencyUnit: 'usd', dataUnit: FileSizeUnit.gigabytes, ), + EstimationLoading(), + // then emit eur EstimationLoaded( balance: BigInt.from(10), @@ -257,6 +308,57 @@ void main() { ), ], ); + + blocTest( + 'should emit [EstimationLoading] and [EstimationLoadError] when throws calculating new estimation', + build: () => TurboTopUpEstimationBloc(turbo: mockTurbo), + setUp: () { + when(() => mockTurbo.onPriceEstimateChanged) + .thenAnswer((_) => const Stream.empty()); + final mockPriceEstimate = PriceEstimate( + credits: BigInt.from(10), + priceInCurrency: 10, + estimatedStorage: 1); + + when(() => mockTurbo.getBalance()) + .thenAnswer((_) async => BigInt.from(10)); + // GiB + when(() => mockTurbo.computePriceEstimate( + currentAmount: 0, + currentCurrency: 'usd', + currentDataUnit: FileSizeUnit.gigabytes, + )).thenAnswer((_) async => mockPriceEstimate); + when(() => mockTurbo.computeStorageEstimateForCredits( + credits: BigInt.from(10), + outputDataUnit: FileSizeUnit.gigabytes, + )).thenAnswer((_) async => 1); + + // KiB + when(() => mockTurbo.computePriceEstimate( + currentAmount: 0, + currentCurrency: 'usd', + currentDataUnit: FileSizeUnit.kilobytes, + )).thenThrow((_) => Exception()); + }, + act: (bloc) async { + bloc.add(LoadInitialData()); + await Future.delayed(const Duration(milliseconds: 500)); + bloc.add(const DataUnitChanged(FileSizeUnit.kilobytes)); + }, + expect: () => [ + EstimationLoading(), + EstimationLoaded( + balance: BigInt.from(10), + estimatedStorageForBalance: '1.00', + selectedAmount: 10, + creditsForSelectedAmount: BigInt.from(10), + estimatedStorageForSelectedAmount: '1.00', + currencyUnit: 'usd', + dataUnit: FileSizeUnit.gigabytes, + ), + EstimationLoading(), + EstimationLoadError() + ]); }); group('FiatAmountSelected', () { @@ -326,5 +428,54 @@ void main() { ), ], ); + blocTest( + 'should emit [EstimationLoading] and [EstimationLoadError] when throws calculating new estimation', + build: () => TurboTopUpEstimationBloc(turbo: mockTurbo), + setUp: () { + when(() => mockTurbo.onPriceEstimateChanged) + .thenAnswer((_) => const Stream.empty()); + final mockPriceEstimate = PriceEstimate( + credits: BigInt.from(10), + priceInCurrency: 0, + estimatedStorage: 1); + when(() => mockTurbo.getBalance()) + .thenAnswer((_) async => BigInt.from(10)); + when(() => mockTurbo.computePriceEstimate( + currentAmount: 0, + currentCurrency: 'usd', + currentDataUnit: FileSizeUnit.gigabytes, + )).thenAnswer((_) async => mockPriceEstimate); + when(() => mockTurbo.computeStorageEstimateForCredits( + credits: BigInt.from(10), + outputDataUnit: FileSizeUnit.gigabytes, + )).thenAnswer((_) async => 1); + + // second call with 100 amount + when(() => mockTurbo.computePriceEstimate( + currentAmount: 100, + currentCurrency: 'usd', + currentDataUnit: FileSizeUnit.gigabytes, + )).thenThrow(Exception()); + }, + act: (bloc) async { + bloc.add(LoadInitialData()); + await Future.delayed(const Duration(milliseconds: 1000)); + bloc.add(const FiatAmountSelected(100)); + }, + expect: () => [ + EstimationLoading(), + // start with 0 + EstimationLoaded( + balance: BigInt.from(10), + estimatedStorageForBalance: '1.00', + selectedAmount: 0, + creditsForSelectedAmount: BigInt.from(10), + estimatedStorageForSelectedAmount: '1.00', + currencyUnit: 'usd', + dataUnit: FileSizeUnit.gigabytes, + ), + EstimationLoading(), + EstimationLoadError() + ]); }); } diff --git a/test/turbo/turbo_test.dart b/test/turbo/turbo_test.dart index 1c68fb351d..9c2d7db3f6 100644 --- a/test/turbo/turbo_test.dart +++ b/test/turbo/turbo_test.dart @@ -175,7 +175,7 @@ void main() { test( 'calls priceEstimator.computeStorageEstimateForCredits once with the correct arguments', () async { - final mockStorageEstimate = 1.0; + const mockStorageEstimate = 1.0; final mockCredits = BigInt.from(100); when(() => mockPriceEstimator.computeStorageEstimateForCredits( credits: mockCredits, @@ -213,9 +213,6 @@ void main() { test('priceEstimate updates when price quote expires', () async { fakeAsync((async) async { - final mockStorageEstimate = 1.0; - final mockCredits = BigInt.from(100); - final mockPriceEstimate1 = PriceEstimate( credits: BigInt.from(100), estimatedStorage: 1, @@ -244,7 +241,7 @@ void main() { expect(priceEstimate, mockPriceEstimate1); - await Future.delayed(Duration(seconds: 2)); + await Future.delayed(const Duration(seconds: 2)); when(() => mockPriceEstimator.computePriceEstimate( currentAmount: 100, @@ -283,7 +280,7 @@ void main() { expect(priceEstimate, mockPriceEstimate); - await Future.delayed(Duration(seconds: 2)); + await Future.delayed(const Duration(seconds: 2)); when(() => mockPriceEstimator.computePriceEstimate( currentAmount: 0, @@ -586,6 +583,25 @@ void main() { expect(balance, equals(mockBalance)); verify(() => mockPaymentService.getBalance(wallet: mockWallet)).called(1); }); + + test( + 'getBalance returns 0 when PaymentService.getBalance returns TurboUserNotFound', + () async { + when(() => mockPaymentService.getBalance(wallet: mockWallet)) + .thenThrow(TurboUserNotFound()); + + final balance = await balanceRetriever.getBalance(mockWallet); + + expect(balance, BigInt.zero); + verify(() => mockPaymentService.getBalance(wallet: mockWallet)).called(1); + }); + + test('getBalance throws when PaymentService.getBalance throws', () async { + when(() => mockPaymentService.getBalance(wallet: mockWallet)) + .thenThrow(Exception()); + + expect(() => balanceRetriever.getBalance(mockWallet), throwsException); + }); }); group('TurboSessionManager', () { late TurboSessionManager sessionManager; @@ -702,7 +718,7 @@ void main() { expect(priceEstimate.estimatedStorage, expectedEstimatedStorage); }); - group("computeStorageEstimateForCredits", () { + group('computeStorageEstimateForCredits', () { test('should return correct estimate', () async { // Setup the method call response final expectedCredits = BigInt.from(100); diff --git a/test/utils/http_retry_test.dart b/test/utils/http_retry_test.dart index ead84ba705..b120a18cf6 100644 --- a/test/utils/http_retry_test.dart +++ b/test/utils/http_retry_test.dart @@ -1,3 +1,5 @@ +// ignore_for_file: avoid_print + import 'package:ardrive/utils/http_retry.dart'; import 'package:ardrive/utils/response_handler.dart'; import 'package:flutter_test/flutter_test.dart';