diff --git a/lib/blocs/drive_create/drive_create_cubit.dart b/lib/blocs/drive_create/drive_create_cubit.dart index 208c10a5da..7050d2e5f7 100644 --- a/lib/blocs/drive_create/drive_create_cubit.dart +++ b/lib/blocs/drive_create/drive_create_cubit.dart @@ -1,5 +1,9 @@ import 'package:ardrive/blocs/blocs.dart'; -import 'package:ardrive/entities/entities.dart'; +import 'package:ardrive/core/arfs/entities/arfs_entities.dart' + show DrivePrivacy; +import 'package:ardrive/entities/constants.dart' as constants; +import 'package:ardrive/entities/drive_entity.dart'; +import 'package:ardrive/entities/folder_entity.dart'; import 'package:ardrive/models/models.dart'; import 'package:ardrive/services/services.dart'; import 'package:ardrive/turbo/services/upload_service.dart'; @@ -15,7 +19,9 @@ part 'drive_create_state.dart'; class DriveCreateCubit extends Cubit { final form = FormGroup({ 'privacy': FormControl( - value: DrivePrivacy.private, validators: [Validators.required]), + value: DrivePrivacy.private.name, + validators: [Validators.required], + ), }); final ArweaveService _arweave; @@ -35,25 +41,32 @@ class DriveCreateCubit extends Cubit { _driveDao = driveDao, _profileCubit = profileCubit, _drivesCubit = drivesCubit, - super(DriveCreateInitial()); + super(const DriveCreateInitial(privacy: DrivePrivacy.private)); + + void onPrivacyChanged() { + final privacy = form.control('privacy').value == DrivePrivacy.private.name + ? DrivePrivacy.private + : DrivePrivacy.public; + emit(state.copyWith(privacy: privacy)); + } Future submit( String driveName, ) async { final profile = _profileCubit.state as ProfileLoggedIn; if (await _profileCubit.logoutIfWalletMismatch()) { - emit(DriveCreateWalletMismatch()); + emit(DriveCreateWalletMismatch(privacy: state.privacy)); return; } final minimumWalletBalance = BigInt.from(10000000); if (profile.walletBalance <= minimumWalletBalance && !_turboUploadService.useTurboUpload) { - emit(DriveCreateZeroBalance()); + emit(DriveCreateZeroBalance(privacy: state.privacy)); return; } - emit(DriveCreateInProgress()); + emit(DriveCreateInProgress(privacy: state.privacy)); try { final String drivePrivacy = form.control('privacy').value; @@ -72,8 +85,8 @@ class DriveCreateCubit extends Cubit { name: driveName, rootFolderId: createRes.rootFolderId, privacy: drivePrivacy, - authMode: drivePrivacy == DrivePrivacy.private - ? DriveAuthMode.password + authMode: drivePrivacy == constants.DrivePrivacy.private + ? constants.DriveAuthMode.password : null, ); @@ -140,12 +153,12 @@ class DriveCreateCubit extends Cubit { addError(err); } - emit(DriveCreateSuccess()); + emit(DriveCreateSuccess(privacy: state.privacy)); } @override void onError(Object error, StackTrace stackTrace) { - emit(DriveCreateFailure()); + emit(DriveCreateFailure(privacy: state.privacy)); super.onError(error, stackTrace); logger.e('Failed to create drive', error, stackTrace); diff --git a/lib/blocs/drive_create/drive_create_state.dart b/lib/blocs/drive_create/drive_create_state.dart index ac3d115a4e..7c07247a49 100644 --- a/lib/blocs/drive_create/drive_create_state.dart +++ b/lib/blocs/drive_create/drive_create_state.dart @@ -2,18 +2,68 @@ part of 'drive_create_cubit.dart'; @immutable abstract class DriveCreateState extends Equatable { + final DrivePrivacy privacy; + + const DriveCreateState({required this.privacy}); + + DriveCreateState copyWith({DrivePrivacy? privacy}) { + throw UnimplementedError(); + } + @override - List get props => []; + List get props => [privacy]; } -class DriveCreateInitial extends DriveCreateState {} +class DriveCreateInitial extends DriveCreateState { + const DriveCreateInitial({required super.privacy}); -class DriveCreateZeroBalance extends DriveCreateState {} + @override + DriveCreateInitial copyWith({DrivePrivacy? privacy}) { + return DriveCreateInitial(privacy: privacy ?? this.privacy); + } +} -class DriveCreateInProgress extends DriveCreateState {} +class DriveCreateZeroBalance extends DriveCreateState { + const DriveCreateZeroBalance({required super.privacy}); -class DriveCreateSuccess extends DriveCreateState {} + @override + DriveCreateZeroBalance copyWith({DrivePrivacy? privacy}) { + return DriveCreateZeroBalance(privacy: privacy ?? this.privacy); + } +} + +class DriveCreateInProgress extends DriveCreateState { + const DriveCreateInProgress({required super.privacy}); + + @override + DriveCreateInProgress copyWith({DrivePrivacy? privacy}) { + return DriveCreateInProgress(privacy: privacy ?? this.privacy); + } +} -class DriveCreateFailure extends DriveCreateState {} +class DriveCreateSuccess extends DriveCreateState { + const DriveCreateSuccess({required super.privacy}); -class DriveCreateWalletMismatch extends DriveCreateState {} + @override + DriveCreateSuccess copyWith({DrivePrivacy? privacy}) { + return DriveCreateSuccess(privacy: privacy ?? this.privacy); + } +} + +class DriveCreateFailure extends DriveCreateState { + const DriveCreateFailure({required super.privacy}); + + @override + DriveCreateFailure copyWith({DrivePrivacy? privacy}) { + return DriveCreateFailure(privacy: privacy ?? this.privacy); + } +} + +class DriveCreateWalletMismatch extends DriveCreateState { + const DriveCreateWalletMismatch({required super.privacy}); + + @override + DriveCreateWalletMismatch copyWith({DrivePrivacy? privacy}) { + return DriveCreateWalletMismatch(privacy: privacy ?? this.privacy); + } +} diff --git a/lib/components/drive_create_form.dart b/lib/components/drive_create_form.dart index 0645db6002..5800b22c93 100644 --- a/lib/components/drive_create_form.dart +++ b/lib/components/drive_create_form.dart @@ -1,4 +1,5 @@ import 'package:ardrive/blocs/blocs.dart'; +import 'package:ardrive/core/arfs/entities/arfs_entities.dart'; import 'package:ardrive/l11n/l11n.dart'; import 'package:ardrive/models/models.dart'; import 'package:ardrive/pages/congestion_warning_wrapper.dart'; @@ -73,6 +74,8 @@ class _DriveCreateFormState extends State { ], ); } else { + final privacy = state.privacy; + return ArDriveStandardModal( title: appLocalizationsOf(context).createDriveEmphasized, content: SizedBox( @@ -108,24 +111,43 @@ class _DriveCreateFormState extends State { ReactiveDropdownField( formControlName: 'privacy', decoration: InputDecoration( - labelText: appLocalizationsOf(context).privacy), + labelText: appLocalizationsOf(context).privacy, + ), showErrors: (control) => control.dirty && control.invalid, validationMessages: kValidationMessages(appLocalizationsOf(context)), items: [ DropdownMenuItem( - value: 'public', + value: DrivePrivacy.public.name, child: Text(appLocalizationsOf(context).public), ), DropdownMenuItem( - value: 'private', + value: DrivePrivacy.private.name, child: Text( appLocalizationsOf(context).private, ), ) ], + onChanged: (_) { + context.read().onPrivacyChanged(); + }, ), + const SizedBox(height: 32), + Row(children: [ + if (privacy == DrivePrivacy.private) + Flexible( + child: Text( + appLocalizationsOf(context) + .drivePrivacyDescriptionPrivate, + )) + else + Flexible( + child: Text( + appLocalizationsOf(context) + .drivePrivacyDescriptionPublic, + )) + ]), ], ), ), diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 0619f9786b..a3e2c9823a 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -519,6 +519,14 @@ "@driveName": { "description": "The name of certain drive" }, + "drivePrivacyDescriptionPrivate": "Private Drives offer state-of-the-art security, you control access.", + "@drivePrivacyDescriptionPrivate": { + "description": "Explains private drives." + }, + "drivePrivacyDescriptionPublic": "Public Drives are discoverable, others can find and view the contents.", + "@drivePrivacyDescriptionPublic": { + "description": "Explains public drives." + }, "driveRoot": "Drive root", "@driveRoot": { "description": "The folder entity that is at the top of the folder hierarchy" diff --git a/test/blocs/drive_create_cubit_test.dart b/test/blocs/drive_create_cubit_test.dart index 97c7694b6b..8ff7e10435 100644 --- a/test/blocs/drive_create_cubit_test.dart +++ b/test/blocs/drive_create_cubit_test.dart @@ -1,12 +1,11 @@ @Tags(['broken']) import 'package:ardrive/blocs/blocs.dart'; -import 'package:ardrive/core/crypto/crypto.dart'; -import 'package:ardrive/entities/entities.dart'; +import 'package:ardrive/core/arfs/entities/arfs_entities.dart'; +import 'package:ardrive/entities/entity.dart'; import 'package:ardrive/models/models.dart'; import 'package:ardrive/services/services.dart'; import 'package:ardrive/turbo/services/upload_service.dart'; -import 'package:ardrive/utils/app_flavors.dart'; import 'package:ardrive/utils/app_platform.dart'; import 'package:arweave/arweave.dart'; import 'package:bloc_test/bloc_test.dart'; @@ -19,109 +18,139 @@ import 'package:test/test.dart'; import '../test_utils/fakes.dart'; import '../test_utils/utils.dart'; +class FakeEntity extends Fake implements Entity {} + void main() { - group('DriveCreateCubit', () { - late Database db; - late DriveDao driveDao; - - late ArweaveService arweave; - late TurboUploadService turboUploadService; - late DrivesCubit drivesCubit; - late ProfileCubit profileCubit; - late DriveCreateCubit driveCreateCubit; - - const validDriveName = 'valid-drive-name'; - - setUp(() async { - registerFallbackValue(DrivesStateFake()); - registerFallbackValue(ProfileStateFake()); - - db = getTestDb(); - driveDao = db.driveDao; - final configService = ConfigService( - appFlavors: AppFlavors(MockEnvFetcher()), - configFetcher: MockConfigFetcher()); - final config = await configService.loadConfig(); - - AppPlatform.setMockPlatform(platform: SystemPlatform.unknown); - arweave = ArweaveService( - Arweave(gatewayUrl: Uri.parse(config.defaultArweaveGatewayUrl!)), - ArDriveCrypto(), + group( + 'DriveCreateCubit', + () { + late Database db; + late DriveDao driveDao; + + late ArweaveService arweave; + late TurboUploadService turboUploadService; + late DrivesCubit drivesCubit; + late ProfileCubit profileCubit; + late DriveCreateCubit driveCreateCubit; + + late Wallet wallet; + + const validDriveName = 'valid-drive-name'; + + setUp(() async { + wallet = getTestWallet(); + + registerFallbackValue(DrivesStateFake()); + registerFallbackValue(ProfileStateFake()); + registerFallbackValue(DataBundle(blob: Uint8List.fromList([]))); + registerFallbackValue(wallet); + registerFallbackValue(FakeEntity()); + + db = getTestDb(); + driveDao = db.driveDao; + AppPlatform.setMockPlatform(platform: SystemPlatform.unknown); + arweave = MockArweaveService(); + turboUploadService = DontUseUploadService(); + drivesCubit = MockDrivesCubit(); + profileCubit = MockProfileCubit(); + + final walletAddress = await wallet.getAddress(); + final walletOwner = await wallet.getAddress(); + + final keyBytes = Uint8List(32); + fillBytesWithSecureRandom(keyBytes); + + when(() => profileCubit.state).thenReturn( + ProfileLoggedIn( + username: 'Test', + password: '123', + wallet: wallet, + walletAddress: walletAddress, + walletBalance: BigInt.from(10000001), + cipherKey: SecretKey(keyBytes), + useTurbo: turboUploadService.useTurboUpload, + ), + ); + + when(() => profileCubit.logoutIfWalletMismatch()).thenAnswer( + (invocation) => Future.value(false), + ); + + when(() => arweave.prepareBundledDataItem(any(), any())).thenAnswer( + (invocation) => Future.value( + DataItem.withBlobData( + owner: walletOwner, + data: Uint8List.fromList([]), + ), + ), + ); + + when(() => arweave.prepareEntityDataItem(any(), any())).thenAnswer( + (invocation) => Future.value( + DataItem.withBlobData( + owner: walletOwner, + data: Uint8List.fromList([]), + ), + ), + ); + + driveCreateCubit = DriveCreateCubit( + arweave: arweave, + turboUploadService: turboUploadService, + driveDao: driveDao, + drivesCubit: drivesCubit, + profileCubit: profileCubit, + ); + }); + + tearDown(() async { + await db.close(); + }); + + blocTest( + 'create public drive', + build: () => driveCreateCubit, + act: (bloc) async { + bloc.form.value = { + 'name': validDriveName, + 'privacy': DrivePrivacy.public.name, + }; + await bloc.submit(''); + }, + expect: () => [ + const DriveCreateInProgress(privacy: DrivePrivacy.public), + const DriveCreateSuccess(privacy: DrivePrivacy.public), + ], + verify: (_) {}, ); - turboUploadService = DontUseUploadService(); - drivesCubit = MockDrivesCubit(); - profileCubit = MockProfileCubit(); - - final wallet = getTestWallet(); - final walletAddress = await wallet.getAddress(); - - final keyBytes = Uint8List(32); - fillBytesWithSecureRandom(keyBytes); - - when(() => profileCubit.state).thenReturn( - ProfileLoggedIn( - username: 'Test', - password: '123', - wallet: wallet, - walletAddress: walletAddress, - walletBalance: BigInt.one, - cipherKey: SecretKey(keyBytes), - useTurbo: turboUploadService.useTurboUpload, - ), + + blocTest( + 'create private drive', + build: () => driveCreateCubit, + act: (bloc) async { + bloc.form.value = { + 'name': validDriveName, + 'privacy': DrivePrivacy.private.name, + }; + + bloc.onPrivacyChanged(); + + await bloc.submit(''); + }, + expect: () => [ + const DriveCreateInProgress(privacy: DrivePrivacy.public), + const DriveCreateInProgress(privacy: DrivePrivacy.private), + const DriveCreateSuccess(privacy: DrivePrivacy.private), + ], + verify: (_) {}, ); - driveCreateCubit = DriveCreateCubit( - arweave: arweave, - turboUploadService: turboUploadService, - driveDao: driveDao, - drivesCubit: drivesCubit, - profileCubit: profileCubit, + blocTest( + 'does nothing when submitted without valid form', + build: () => driveCreateCubit, + act: (bloc) => bloc.submit(''), + expect: () => [], ); - }); - - tearDown(() async { - await db.close(); - }); - - blocTest( - 'create public drive', - build: () => driveCreateCubit, - act: (bloc) async { - bloc.form.value = { - 'name': validDriveName, - 'privacy': DrivePrivacy.public, - }; - await bloc.submit(''); - }, - expect: () => [ - DriveCreateInProgress(), - DriveCreateSuccess(), - ], - verify: (_) {}, - ); - - blocTest( - 'create private drive', - build: () => driveCreateCubit, - act: (bloc) async { - bloc.form.value = { - 'name': validDriveName, - 'privacy': DrivePrivacy.private, - }; - await bloc.submit(''); - }, - expect: () => [ - DriveCreateInProgress(), - DriveCreateSuccess(), - ], - verify: (_) {}, - ); - - blocTest( - 'does nothing when submitted without valid form', - build: () => driveCreateCubit, - act: (bloc) => bloc.submit(''), - expect: () => [], - ); - }, skip: 'Needs to update the tests'); + }, + ); }