diff --git a/android/fastlane/metadata/android/en-US/changelogs/154.txt b/android/fastlane/metadata/android/en-US/changelogs/154.txt new file mode 100644 index 000000000..7b714dbe5 --- /dev/null +++ b/android/fastlane/metadata/android/en-US/changelogs/154.txt @@ -0,0 +1,2 @@ + - Improves handling of errors on the assign name flow + - Displays the file(s) size(s) instead of data items or bundle sizes at the upload page diff --git a/lib/arns/presentation/assign_name_bloc/assign_name_bloc.dart b/lib/arns/presentation/assign_name_bloc/assign_name_bloc.dart index 088a65df9..ecc369cf3 100644 --- a/lib/arns/presentation/assign_name_bloc/assign_name_bloc.dart +++ b/lib/arns/presentation/assign_name_bloc/assign_name_bloc.dart @@ -24,16 +24,22 @@ class AssignNameBloc extends Bloc { _arnsRepository = arnsRepository, super(AssignNameInitial()) { on((event, emit) async { - emit(LoadingNames()); + try { + emit(LoadingNames()); - final walletAddress = await _auth.getWalletAddress(); + final walletAddress = await _auth.getWalletAddress(); - final names = await _arnsRepository.getAntRecordsForWallet(walletAddress!); + final names = + await _arnsRepository.getAntRecordsForWallet(walletAddress!); - if (names.isEmpty) { - emit(AssignNameEmptyState()); - } else { - emit(NamesLoaded(names: names)); + if (names.isEmpty) { + emit(AssignNameEmptyState()); + } else { + emit(NamesLoaded(names: names)); + } + } catch (e) { + logger.e('Failed to load ArNS names', e); + emit(LoadingNamesFailed()); } }); @@ -94,41 +100,37 @@ class AssignNameBloc extends Bloc { on((event, emit) async { try { emit(ConfirmingSelection()); - try { - if (fileDataTableItem == null) { - throw StateError('File data table item is null'); - } - - ARNSUndername undername; - - if (_selectedUndername == null) { - undername = ARNSUndername( - name: '@', - record: ARNSRecord( - transactionId: fileDataTableItem.dataTxId, ttlSeconds: 3600), - domain: _selectedANTRecord!.domain, - ); - } else { - undername = ARNSUndername( - name: _selectedUndername!.name, - record: ARNSRecord( - transactionId: fileDataTableItem.dataTxId, - ttlSeconds: 3600, - ), - domain: _selectedANTRecord!.domain, - ); - } - - await _arnsRepository.setUndernamesToFile( - undername: undername, - fileId: fileDataTableItem.fileId, - driveId: fileDataTableItem.driveId, - processId: _selectedANTRecord!.processId, + if (fileDataTableItem == null) { + throw StateError('File data table item is null'); + } + + ARNSUndername undername; + + if (_selectedUndername == null) { + undername = ARNSUndername( + name: '@', + record: ARNSRecord( + transactionId: fileDataTableItem.dataTxId, ttlSeconds: 3600), + domain: _selectedANTRecord!.domain, + ); + } else { + undername = ARNSUndername( + name: _selectedUndername!.name, + record: ARNSRecord( + transactionId: fileDataTableItem.dataTxId, + ttlSeconds: 3600, + ), + domain: _selectedANTRecord!.domain, ); - } catch (e) { - logger.e('Failed to set ARNS', e); } + await _arnsRepository.setUndernamesToFile( + undername: undername, + fileId: fileDataTableItem.fileId, + driveId: fileDataTableItem.driveId, + processId: _selectedANTRecord!.processId, + ); + final (address, arAddress) = getAddressesFromArns( domain: _selectedANTRecord!.domain, undername: _selectedUndername?.name, diff --git a/lib/arns/presentation/assign_name_bloc/assign_name_state.dart b/lib/arns/presentation/assign_name_bloc/assign_name_state.dart index d2a8bc7c6..10b609dcf 100644 --- a/lib/arns/presentation/assign_name_bloc/assign_name_state.dart +++ b/lib/arns/presentation/assign_name_bloc/assign_name_state.dart @@ -93,4 +93,6 @@ final class LoadingUndernames extends AssignNameState {} class SelectionFailed extends AssignNameState {} +final class LoadingNamesFailed extends AssignNameState {} + final class EmptySelection extends AssignNameState {} diff --git a/lib/arns/presentation/assign_name_modal.dart b/lib/arns/presentation/assign_name_modal.dart index 388ad8dd8..26d04c4aa 100644 --- a/lib/arns/presentation/assign_name_modal.dart +++ b/lib/arns/presentation/assign_name_modal.dart @@ -271,6 +271,28 @@ class _AssignArNSNameModalState extends State<_AssignArNSNameModal> { return const Center(child: CircularProgressIndicator()); } else if (state is AssignNameEmptyState) { return const _AssignNameEmptyState(); + } else if (state is SelectionFailed) { + final colorTokens = + ArDriveTheme.of(context).themeData.colorTokens; + return Center( + child: Text( + 'Error assigning ArNS name. Please try again later', + style: typography.paragraphLarge( + color: colorTokens.textMid, + ), + ), + ); + } else if (state is LoadingNamesFailed) { + final colorTokens = + ArDriveTheme.of(context).themeData.colorTokens; + return Center( + child: Text( + 'Error fetching ArNS names. Please try again later', + style: typography.paragraphLarge( + color: colorTokens.textMid, + ), + ), + ); } return const SizedBox(); @@ -299,6 +321,42 @@ class _AssignArNSNameModalState extends State<_AssignArNSNameModal> { return []; } + if (state is LoadingNamesFailed) { + return [ + ModalAction( + action: () { + Navigator.of(context).pop(); + }, + title: 'Cancel', + ), + ModalAction( + action: () { + context + .read() + .add(const LoadNames(updateARNSRecords: true)); + }, + title: 'Try again', + ), + ]; + } + + if (state is SelectionFailed) { + return [ + ModalAction( + action: () { + Navigator.of(context).pop(); + }, + title: 'Cancel', + ), + ModalAction( + action: () { + context.read().add(ConfirmSelectionAndUpload()); + }, + title: 'Try again', + ), + ]; + } + late final bool isButtonEnabled; if (state is NamesLoaded && state.selectedName != null) { diff --git a/lib/blocs/upload/upload_cubit.dart b/lib/blocs/upload/upload_cubit.dart index 0c758da48..37d8a0cbc 100644 --- a/lib/blocs/upload/upload_cubit.dart +++ b/lib/blocs/upload/upload_cubit.dart @@ -120,6 +120,7 @@ class UploadCubit extends Cubit { showArnsCheckbox: showArnsCheckbox, showArnsNameSelection: false, loadingArNSNames: true, + totalSize: await _getTotalSize(), ), ); @@ -156,6 +157,7 @@ class UploadCubit extends Cubit { isArConnect: (state as UploadReadyToPrepare).isArConnect, showArnsCheckbox: showArnsCheckbox, showArnsNameSelection: false, + totalSize: await _getTotalSize(), ), ); } @@ -176,6 +178,16 @@ class UploadCubit extends Cubit { } } + Future _getTotalSize() async { + int size = 0; + + for (final file in files) { + size += await file.ioFile.length; + } + + return size; + } + void initialScreenNext({required LicenseCategory licenseCategory}) { if (state is UploadReady) { final readyState = state as UploadReady; diff --git a/lib/blocs/upload/upload_state.dart b/lib/blocs/upload/upload_state.dart index a855aa938..5cacacf24 100644 --- a/lib/blocs/upload/upload_state.dart +++ b/lib/blocs/upload/upload_state.dart @@ -97,6 +97,7 @@ class UploadReady extends UploadState { final bool showArnsNameSelection; final bool loadingArNSNames; final bool loadingArNSNamesError; + final int totalSize; final bool isArConnect; @@ -112,6 +113,7 @@ class UploadReady extends UploadState { required this.showArnsNameSelection, this.loadingArNSNames = false, this.loadingArNSNamesError = false, + required this.totalSize, }); // copyWith @@ -129,6 +131,7 @@ class UploadReady extends UploadState { bool? showArnsNameSelection, bool? loadingArNSNames, bool? loadingArNSNamesError, + int? totalSize, }) { return UploadReady( loadingArNSNames: loadingArNSNames ?? this.loadingArNSNames, @@ -144,6 +147,7 @@ class UploadReady extends UploadState { showArnsNameSelection ?? this.showArnsNameSelection, loadingArNSNamesError: loadingArNSNamesError ?? this.loadingArNSNamesError, + totalSize: totalSize ?? this.totalSize, ); } @@ -155,7 +159,6 @@ class UploadReady extends UploadState { loadingArNSNamesError, loadingArNSNames, showArnsCheckbox, - ]; @override diff --git a/lib/components/upload_form.dart b/lib/components/upload_form.dart index 93a81f82c..1b7c9c84b 100644 --- a/lib/components/upload_form.dart +++ b/lib/components/upload_form.dart @@ -1568,7 +1568,7 @@ class _StatsScreenState extends State { ), TextSpan( text: filesize( - widget.readyState.paymentInfo.totalSize, + widget.readyState.totalSize, ), style: typography.paragraphNormal( fontWeight: ArFontWeight.bold, diff --git a/pubspec.yaml b/pubspec.yaml index 2417029a0..274d7fbeb 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -3,7 +3,7 @@ description: Secure, permanent storage publish_to: 'none' -version: 2.54.3 +version: 2.54.4 environment: sdk: '>=3.2.0 <4.0.0' diff --git a/test/arns/presentation/assign_name_bloc/assign_name_bloc_test.dart b/test/arns/presentation/assign_name_bloc/assign_name_bloc_test.dart index d89ad4a7e..874ec963e 100644 --- a/test/arns/presentation/assign_name_bloc/assign_name_bloc_test.dart +++ b/test/arns/presentation/assign_name_bloc/assign_name_bloc_test.dart @@ -108,6 +108,29 @@ void main() { walletAddress, )).called(1); }); + + test('emits [LoadingNames, LoadingNamesFailed] when LoadNames is added', + () async { + // Arrange + const walletAddress = 'test_wallet_address'; + + when(() => mockAuth.getWalletAddress()) + .thenAnswer((_) async => walletAddress); + when(() => mockArnsRepository.getAntRecordsForWallet(walletAddress, + update: true)).thenThrow(StateError('Test error')); + + // Act + assignNameBloc.add(const LoadNames()); + + // Assert + await expectLater( + assignNameBloc.stream, + emitsInOrder([ + isA(), + isA(), + ]), + ); + }); }); group('SelectName', () { @@ -353,5 +376,76 @@ void main() { }, ); }); + + blocTest( + 'emits [ConfirmingSelection, SelectionFailed] when ConfirmSelection is added', + build: () { + when(() => mockFileDataTableItem.dataTxId) + .thenReturn('test_data_tx_id'); + when(() => mockFileDataTableItem.fileId).thenReturn('test_file_id'); + when(() => mockFileDataTableItem.driveId).thenReturn('test_drive_id'); + final antRecords = [ + ANTRecord(domain: 'test1.ar', processId: 'process1'), + ANTRecord(domain: 'test2.ar', processId: 'process2'), + ]; + + const walletAddress = 'test_wallet_address'; + + when(() => mockAuth.getWalletAddress()) + .thenAnswer((_) async => walletAddress); + when(() => mockArnsRepository.getAntRecordsForWallet( + walletAddress, + )).thenAnswer((_) async => antRecords); + + /// FAILED CALL + when(() => mockArnsRepository.setUndernamesToFile( + undername: any(named: 'undername'), + fileId: any(named: 'fileId'), + driveId: any(named: 'driveId'), + processId: any(named: 'processId'), + )).thenThrow(StateError('Test error')); + when(() => mockArnsRepository.getARNSUndernames(any())).thenAnswer( + (_) async => [ + ARNSUndername( + name: 'undername', + domain: 'domain', + record: ARNSRecord(transactionId: 'test_tx_id', ttlSeconds: 3600), + ) + ], + ); + return assignNameBloc; + }, + act: (bloc) { + bloc.add(const LoadNames()); + bloc.add( + SelectName(ANTRecord(domain: 'domain', processId: 'process_id'))); + bloc.add(const LoadUndernames()); + bloc.add(SelectUndername( + undername: ARNSUndername( + name: 'undername', + domain: 'domain', + record: ARNSRecord(transactionId: 'test_tx_id', ttlSeconds: 3600), + ), + )); + bloc.add(ConfirmSelectionAndUpload()); + }, + expect: () => [ + isA(), + isA(), + isA(), + isA(), + isA(), + isA(), + isA(), + ], + verify: (_) { + verify(() => mockArnsRepository.setUndernamesToFile( + undername: any(named: 'undername'), + fileId: any(named: 'fileId'), + driveId: any(named: 'driveId'), + processId: any(named: 'processId'), + )).called(1); + }, + ); }); }