From 4a1e34445e9975ee6abadb8b665863fe7298fd55 Mon Sep 17 00:00:00 2001 From: Mati Date: Fri, 15 Sep 2023 13:07:17 -0300 Subject: [PATCH 01/24] feat(pin file bloc): adds a gql tag for public pinned files PE-4545 --- lib/blocs/pin_file/pin_file_bloc.dart | 27 +++++++++++++++++++++-- lib/services/arweave/arweave_service.dart | 5 ++++- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/lib/blocs/pin_file/pin_file_bloc.dart b/lib/blocs/pin_file/pin_file_bloc.dart index b1ffddb6d7..b2bbc29adf 100644 --- a/lib/blocs/pin_file/pin_file_bloc.dart +++ b/lib/blocs/pin_file/pin_file_bloc.dart @@ -279,6 +279,7 @@ class PinFileBloc extends Bloc { ) async { final stateAsPinFileFieldsValid = state as PinFileFieldsValid; final profileState = _profileCubit.state as ProfileLoggedIn; + final wallet = profileState.wallet; emit(PinFileCreating( id: stateAsPinFileFieldsValid.id, @@ -314,10 +315,21 @@ class PinFileBloc extends Bloc { if (_turboUploadService.useTurboUpload) { final fileDataItem = await _arweave.prepareEntityDataItem( newFileEntity, - profileState.wallet, + wallet, key: fileKey, + skipSignature: true, ); + if (fileKey == null) { + // If file is public + fileDataItem.addTag( + 'Pinned-Data-Owner', + stateAsPinFileFieldsValid.pinnedDataOwnerAddress, + ); + } + + fileDataItem.sign(wallet); + await _turboUploadService.postDataItem( dataItem: fileDataItem, wallet: profileState.wallet, @@ -326,10 +338,21 @@ class PinFileBloc extends Bloc { } else { final fileDataItem = await _arweave.prepareEntityTx( newFileEntity, - profileState.wallet, + wallet, fileKey, + skipSignature: true, ); + if (fileKey == null) { + // If file is public + fileDataItem.addTag( + 'Pinned-Data-Owner', + stateAsPinFileFieldsValid.pinnedDataOwnerAddress, + ); + } + + fileDataItem.sign(wallet); + await _arweave.postTx(fileDataItem); newFileEntity.txId = fileDataItem.id; } diff --git a/lib/services/arweave/arweave_service.dart b/lib/services/arweave/arweave_service.dart index 06ad23c6f2..4afa342aaf 100644 --- a/lib/services/arweave/arweave_service.dart +++ b/lib/services/arweave/arweave_service.dart @@ -1068,11 +1068,14 @@ class ArweaveService { Entity entity, Wallet wallet, { SecretKey? key, + bool skipSignature = false, }) async { final item = await entity.asDataItem(key); item.setOwner(await wallet.getOwner()); - await item.sign(wallet); + if (!skipSignature) { + await item.sign(wallet); + } return item; } From e0785f1da200377ee403c23686e4fb3686cb3007 Mon Sep 17 00:00:00 2001 From: Mati Date: Fri, 15 Sep 2023 15:29:57 -0300 Subject: [PATCH 02/24] feat(pin file bloc): sets ArFS-Pin tag for all pins, and Pinned-Data-Tx for public ones PE-4545 --- lib/blocs/pin_file/pin_file_bloc.dart | 19 +++++++++++++------ lib/entities/constants.dart | 3 +++ 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/lib/blocs/pin_file/pin_file_bloc.dart b/lib/blocs/pin_file/pin_file_bloc.dart index b2bbc29adf..8d017cf330 100644 --- a/lib/blocs/pin_file/pin_file_bloc.dart +++ b/lib/blocs/pin_file/pin_file_bloc.dart @@ -1,9 +1,8 @@ import 'dart:async'; import 'package:ardrive/blocs/blocs.dart'; -import 'package:ardrive/core/arfs/entities/arfs_entities.dart'; import 'package:ardrive/core/crypto/crypto.dart'; -import 'package:ardrive/entities/file_entity.dart'; +import 'package:ardrive/entities/entities.dart'; import 'package:ardrive/entities/string_types.dart'; import 'package:ardrive/misc/misc.dart'; import 'package:ardrive/models/models.dart'; @@ -320,11 +319,15 @@ class PinFileBloc extends Bloc { skipSignature: true, ); + fileDataItem.addTag( + EntityTag.arFsPin, + 'true', + ); if (fileKey == null) { // If file is public fileDataItem.addTag( - 'Pinned-Data-Owner', - stateAsPinFileFieldsValid.pinnedDataOwnerAddress, + EntityTag.pinnedDataTx, + newFileEntity.dataTxId!, ); } @@ -343,11 +346,15 @@ class PinFileBloc extends Bloc { skipSignature: true, ); + fileDataItem.addTag( + EntityTag.arFsPin, + 'true', + ); if (fileKey == null) { // If file is public fileDataItem.addTag( - 'Pinned-Data-Owner', - stateAsPinFileFieldsValid.pinnedDataOwnerAddress, + EntityTag.pinnedDataTx, + newFileEntity.dataTxId!, ); } diff --git a/lib/entities/constants.dart b/lib/entities/constants.dart index 45089943d8..8d904258c7 100644 --- a/lib/entities/constants.dart +++ b/lib/entities/constants.dart @@ -30,6 +30,9 @@ class EntityTag { static const blockEnd = 'Block-End'; static const dataStart = 'Data-Start'; static const dataEnd = 'Data-End'; + + static const pinnedDataTx = 'Pinned-Data-Tx'; + static const arFsPin = 'ArFS-Pin'; } class ContentType { From 2d8de9234a9e09fe00cf6e33350a927ad07580d0 Mon Sep 17 00:00:00 2001 From: Mati Date: Fri, 15 Sep 2023 16:23:27 -0300 Subject: [PATCH 03/24] feat(pin file bloc): corrects imports PE-4545 --- lib/blocs/pin_file/pin_file_bloc.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/blocs/pin_file/pin_file_bloc.dart b/lib/blocs/pin_file/pin_file_bloc.dart index 8d017cf330..9d1888eb63 100644 --- a/lib/blocs/pin_file/pin_file_bloc.dart +++ b/lib/blocs/pin_file/pin_file_bloc.dart @@ -1,8 +1,9 @@ import 'dart:async'; import 'package:ardrive/blocs/blocs.dart'; +import 'package:ardrive/core/arfs/entities/arfs_entities.dart'; import 'package:ardrive/core/crypto/crypto.dart'; -import 'package:ardrive/entities/entities.dart'; +import 'package:ardrive/entities/entities.dart' show EntityTag, FileEntity; import 'package:ardrive/entities/string_types.dart'; import 'package:ardrive/misc/misc.dart'; import 'package:ardrive/models/models.dart'; From 6980f203a2000d9b0cba8c7c1ea1d9a2a133d9d2 Mon Sep 17 00:00:00 2001 From: Mati Date: Fri, 15 Sep 2023 18:41:55 -0300 Subject: [PATCH 04/24] feat(create drive form): adds a description of the selected drive privacy PE-4597 --- .../drive_create/drive_create_cubit.dart | 23 +++++++++++---- .../drive_create/drive_create_state.dart | 13 ++++++++- lib/components/drive_create_form.dart | 29 +++++++++++++++++-- lib/l10n/app_en.arb | 8 +++++ 4 files changed, 64 insertions(+), 9 deletions(-) diff --git a/lib/blocs/drive_create/drive_create_cubit.dart b/lib/blocs/drive_create/drive_create_cubit.dart index 208c10a5da..0ecf864ca3 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,7 +41,14 @@ class DriveCreateCubit extends Cubit { _driveDao = driveDao, _profileCubit = profileCubit, _drivesCubit = drivesCubit, - super(DriveCreateInitial()); + super(DriveCreateInitial(privacy: DrivePrivacy.private)); + + void onPrivacyChanged() { + final privacy = form.control('privacy').value == DrivePrivacy.private.name + ? DrivePrivacy.private + : DrivePrivacy.public; + emit((state as DriveCreateInitial).copyWith(privacy: privacy)); + } Future submit( String driveName, @@ -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, ); diff --git a/lib/blocs/drive_create/drive_create_state.dart b/lib/blocs/drive_create/drive_create_state.dart index ac3d115a4e..4b0a6048e9 100644 --- a/lib/blocs/drive_create/drive_create_state.dart +++ b/lib/blocs/drive_create/drive_create_state.dart @@ -6,7 +6,18 @@ abstract class DriveCreateState extends Equatable { List get props => []; } -class DriveCreateInitial extends DriveCreateState {} +class DriveCreateInitial extends DriveCreateState { + final DrivePrivacy privacy; + + DriveCreateInitial({required this.privacy}); + + DriveCreateInitial copyWith({DrivePrivacy? privacy}) { + return DriveCreateInitial(privacy: privacy ?? this.privacy); + } + + @override + List get props => [privacy]; +} class DriveCreateZeroBalance extends DriveCreateState {} diff --git a/lib/components/drive_create_form.dart b/lib/components/drive_create_form.dart index 0645db6002..0aa1efe782 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,9 @@ class _DriveCreateFormState extends State { ], ); } else { + final stateAsInitial = state as DriveCreateInitial; + final privacy = stateAsInitial.privacy; + return ArDriveStandardModal( title: appLocalizationsOf(context).createDriveEmphasized, content: SizedBox( @@ -108,24 +112,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" From 2d53864341c9788db7fa0a07be91d1f1328f7044 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Batista?= Date: Mon, 18 Sep 2023 12:14:26 -0300 Subject: [PATCH 05/24] [Localizely] Translations update --- lib/l10n/app_en.arb | 16 +++++- lib/l10n/app_es.arb | 116 ++++++++++++++++++++++++++++++++++++++--- lib/l10n/app_hi.arb | 110 ++++++++++++++++++++++++++++++++++++-- lib/l10n/app_ja.arb | 110 ++++++++++++++++++++++++++++++++++++-- lib/l10n/app_zh-HK.arb | 112 +++++++++++++++++++++++++++++++++++++-- lib/l10n/app_zh.arb | 110 ++++++++++++++++++++++++++++++++++++-- 6 files changed, 553 insertions(+), 21 deletions(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 0619f9786b..e00e186472 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -310,6 +310,10 @@ "@costUpload": { "description": "Cost" }, + "couldNotLoadFile": "Could not load file", + "@couldNotLoadFile": { + "description": "Message shown when user can not load audio or media files for playback" + }, "country": "Country", "@country": {}, "create": "Create", @@ -519,6 +523,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" @@ -1216,7 +1228,7 @@ "@movingItemsEmphasized": { "description": "Moving Items dialog title" }, - "multiDownloadCompleteWithSkippedFiles": "Download Complete with {numSkippedFiles} skipped file(s)", + "multiDownloadCompleteWithSkippedFiles": "Download complete with {numSkippedFiles} skipped file(s)", "@multiDownloadCompleteWithSkippedFiles": { "description": "Title to modal shown when user completes multi-file download and has skipped files. " }, @@ -2062,4 +2074,4 @@ "@zippingYourFiles": { "description": "Download failure message when a file is too big" } -} +} \ No newline at end of file diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 978f46529f..5353eae112 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -22,6 +22,10 @@ "@addSomeFiles": { "description": "Empty folder" }, + "advanced": "Avanzadas", + "@advanced": { + "description": "The advanced options" + }, "aggreeToTerms_body": "Acepto los términos de servicio y la política de privacidad de ArDrive.", "@aggreeToTerms_body": { "description": "Checkbox for agreeing to the terms of service and privacy policy" @@ -40,6 +44,8 @@ }, "amount": "Cantidad", "@amount": {}, + "anErrorOccuredWhileDownloadingYourFiles": "Se ha producido un error al descargar los archivos. Por favor, inténtalo de nuevo.", + "@anErrorOccuredWhileDownloadingYourFiles": {}, "anErrorOccuredWhileDownloadingYourKeyfile": "Se ha producido un error al descargar el archivo de claves de su monedero. Inténtalo de nuevo.", "@anErrorOccuredWhileDownloadingYourKeyfile": {}, "anyFilesWillOutliveYou": "¡Los archivos que cargues aquí existirán para siempre!", @@ -330,7 +336,7 @@ "@createHereEmphasized": { "description": "Create the manifest in the current selected folder" }, - "createManifest": "Crear manifiesto", + "createManifest": "Nuevo manifiesto", "@createManifest": { "description": "To create a manifest" }, @@ -346,7 +352,7 @@ "@createNewSnapshot": { "description": "The action of snapshotting a drive" }, - "createSnapshot": "Crear instantánea", + "createSnapshot": "Nueva captura", "@createSnapshot": { "description": "The action of snapshotting a drive" }, @@ -685,6 +691,14 @@ "@failedToCreateManifestEmphasized": { "description": "The manifest could not be created" }, + "failedToCreatePin": "No se ha podido crear el anclaje. Vuelve a intentarlo más tarde.", + "@failedToCreatePin": { + "description": "The pin could not be created" + }, + "failedToRetrieveFileInfromation": "No se ha podido recuperar la información del archivo", + "@failedToRetrieveFileInfromation": { + "description": "Explains that there was an error while retrieving the file infromation" + }, "failedToSyncDrive": "No se pudo sincronizar el contenido de la unidad.", "@failedToSyncDrive": { "description": "The app wasn't able to sync the drive" @@ -710,6 +724,10 @@ } } }, + "fileDoesExistButIsInvalid": "El archivo proporcionado está dañado y no se puede anclar.", + "@fileDoesExistButIsInvalid": { + "description": "Explains that the file does exist, but some of its properties make it invalid" + }, "fileDownloadFailed": "La descarga del archivo ha fallado", "@fileDownloadFailed": { "description": "Could not export the CSV data" @@ -734,6 +752,10 @@ "@fileID": { "description": "The Entity-ID tag of files" }, + "fileIsNotPublic": "La ID proporcionada es para un archivo privado y no puede anclarse.", + "@fileIsNotPublic": { + "description": "Warning explaining that the file is not public" + }, "fileName": "Nombre del archivo", "@fileName": { "description": "The name of certain file" @@ -795,6 +817,10 @@ "@fileSystem": { "description": "Button label to select a file from file system" }, + "fileType": "Tipo de archivo", + "@fileType": { + "description": "The MIME type of the file" + }, "fileWasCreatedWithName": "El archivo fue creado con el nombre: \"{fileName}\".", "@fileWasCreatedWithName": { "description": "File activity (journal): created", @@ -813,6 +839,10 @@ "@fileWasMoved": { "description": "File activity (journal): moved" }, + "fileWasPinnedToTheDrive": "Archivo anclado a la unidad.", + "@fileWasPinnedToTheDrive": { + "description": "File activity (journal): was pinned" + }, "fileWasRenamed": "El archivo fue renombrado a: \"{fileName}\".", "@fileWasRenamed": { "description": "File activity (journal): renamed", @@ -955,6 +985,8 @@ "@help": { "description": "Link to a form for collecting feedback from the user" }, + "helpCenter": "Centro de ayuda", + "@helpCenter": {}, "howAreConversionsDetermined": "¿Cómo se calculan las conversiones?", "@howAreConversionsDetermined": {}, "howDoesKeyfileLoginWork": "¿Cómo funcionan el archivo de claves y la frase de recuperación de inicio de sesión?", @@ -1148,7 +1180,7 @@ "@monthlyCharges": { "description": "Payment philosophy" }, - "moreInfo": "Más información", + "moreInfo": "Más Información", "@moreInfo": { "description": "More info" }, @@ -1184,6 +1216,18 @@ "@movingItemsEmphasized": { "description": "Moving Items dialog title" }, + "multiDownloadCompleteWithSkippedFiles": "Descarga completa con {numSkippedFiles} archivo(s) omitido(s)", + "@multiDownloadCompleteWithSkippedFiles": { + "description": "Title to modal shown when user completes multi-file download and has skipped files. " + }, + "multiDownloadDownloadingFilesProgress": "Descargando archivo(s)... {currentFileNum} de {totalNumFiles}", + "@multiDownloadDownloadingFilesProgress": { + "description": "Title to modal shown when user is downloading multiple file. " + }, + "multiDownloadErrorTryAgain": "Ha habido un error al descargar tu(s) archivo(s). Inténtalo de nuevo.", + "@multiDownloadErrorTryAgain": { + "description": "Error message to show user when an error has occurred downloading a file within a set of multi-file downloads." + }, "name": "Nombre", "@name": { "description": "Name of certain entity" @@ -1298,6 +1342,8 @@ "@orContinueWith": { "description": "text to continue with ar connect" }, + "ourChannels": "Nuestros canales:", + "@ourChannels": {}, "password": "Contraseña", "@password": { "description": "Label for the 'password' input" @@ -1336,6 +1382,10 @@ "@personalDrivesEmphasized": { "description": "Sub-header for the personal drives list. Emphasized with upper case" }, + "pinFailedToUpload": "No se ha podido cargar el anclaje", + "@pinFailedToUpload": { + "description": "Explains that pin creation has failed" + }, "pleaseAcceptTheTermsToContinue": "Acepta los términos y condiciones para continuar.", "@pleaseAcceptTheTermsToContinue": { "description": "terms and conditions cta" @@ -1412,6 +1462,16 @@ "@publicDrives": { "description": "Public drives accordion title" }, + "quoteUpdatesIn": "El presupuesto se actualiza en {timer}", + "@quoteUpdatesIn": { + "description": "Indicates the time left for the next update of the quote", + "placeholders": { + "timer": { + "type": "String", + "example": "10:00" + } + } + }, "recreateFolderEmphasized": "RECREAR CARPETA", "@recreateFolderEmphasized": { "description": "Recreate a folder that failed to be minted" @@ -1430,7 +1490,7 @@ "@rename": { "description": "The action of renaming; imperative form" }, - "renameDrive": "Renombrar unidad", + "renameDrive": "Renombrar Unidad", "@renameDrive": { "description": "The action of renaming a drive" }, @@ -1496,6 +1556,8 @@ "@selectWalletEmphasized": { "description": "Users can log in with a crypto wallet, so this prompts them to choose one for their login. Emphasized with upper case" }, + "share": "Compartir", + "@share": {}, "sharedDrives": "Discos compartidos", "@sharedDrives": { "description": "Sub-header for the shared drives list" @@ -1528,11 +1590,25 @@ "@shareFileWithOthers": { "description": "To share a file link" }, + "shareLogsDescription": "Puedes compartir tus registros con el equipo de ArDrive para ayudarnos a mejorar la aplicación. Puedes descargar los registros en tu dispositivo y compartirlos con nosotros por correo electrónico, Discord o añadirlos a un tique de asistencia.", + "@shareLogsDescription": {}, + "shareLogsEmailBody": "Se adjuntan los registros de usuario de ArDrive.", + "@shareLogsEmailBody": {}, + "shareLogsEmailSubject": "Registros de ArDrive", + "@shareLogsEmailSubject": {}, + "shareLogsNativeShareSubject": "Registros de ArDrive", + "@shareLogsNativeShareSubject": {}, + "shareLogsNativeShareText": "Se adjuntan los registros de usuario de ArDrive.", + "@shareLogsNativeShareText": {}, + "shareLogsText": "Compartir registros", + "@shareLogsText": {}, + "shareLogsWithEmailText": "Enviar correo electrónico", + "@shareLogsWithEmailText": {}, "sharePendingFile": "El archivo que intentas compartir sigue pendiente y no puede ser compartido en este momento.", "@sharePendingFile": { "description": "Pending file share dialog text" }, - "showMenu": "Mostrar menú", + "showMenu": "Mostrar Menú", "@showMenu": { "description": "show menu text" }, @@ -1692,6 +1768,20 @@ "@turboAddCreditsBlurb": { "description": "text placeholder for turbo balance when no user" }, + "turboCustomAmount": "Importe personalizado (mín. {min} - máx. {max})", + "@turboCustomAmount": { + "description": "Describes the range of credits the user can choose", + "placeholders": { + "max": { + "type": "String", + "example": "$10,000" + }, + "min": { + "type": "String", + "example": "$5" + } + } + }, "turboErrorMessageEstimationInformationFailed": "Error al cargar la información. Inténtalo de nuevo.", "@turboErrorMessageEstimationInformationFailed": {}, "turboErrorMessageFetchPaymentIntentFailed": "El procesador de pagos no está disponible. Inténtalo de nuevo más tarde.", @@ -1704,6 +1794,20 @@ "@turboErrorMessageSessionExpired": {}, "turboErrorMessageUnknown": "No se ha podido realizar el pago. Comprueba la información de su tarjeta e inténtalo de nuevo.", "@turboErrorMessageUnknown": {}, + "turboPleaseEnterAmountBetween": "Introduce un importe entre {min} y {max}", + "@turboPleaseEnterAmountBetween": { + "description": "Error message for when the given amount is not in range", + "placeholders": { + "max": { + "type": "String", + "example": "$10,000" + }, + "min": { + "type": "String", + "example": "$5" + } + } + }, "unableToFetchEstimateAtThisTime": "No se puede obtener la estimación en este momento.", "@unableToFetchEstimateAtThisTime": {}, "unableToUpdateQuote": "No se ha podido actualizar el presupuesto. Inténtalo de nuevo.", @@ -1958,4 +2062,4 @@ "@zippingYourFiles": { "description": "Download failure message when a file is too big" } -} +} \ No newline at end of file diff --git a/lib/l10n/app_hi.arb b/lib/l10n/app_hi.arb index 67d8fce371..4a8bd9d9d6 100644 --- a/lib/l10n/app_hi.arb +++ b/lib/l10n/app_hi.arb @@ -22,6 +22,10 @@ "@addSomeFiles": { "description": "Empty folder" }, + "advanced": "एडवांस ", + "@advanced": { + "description": "The advanced options" + }, "aggreeToTerms_body": "मैं ArDrive की सेवा की शर्तें और गोपनीयता नीति से सहमत हूं।", "@aggreeToTerms_body": { "description": "Checkbox for agreeing to the terms of service and privacy policy" @@ -40,6 +44,8 @@ }, "amount": "अमाउंट ", "@amount": {}, + "anErrorOccuredWhileDownloadingYourFiles": "आपकी फ़ाइल डाउनलोड करते समय एक त्रुटि हुई। कृपया फिर से कोशिश करें।", + "@anErrorOccuredWhileDownloadingYourFiles": {}, "anErrorOccuredWhileDownloadingYourKeyfile": "आपका वॉलेट कीफाइल डाउनलोड करने में एरर आ गया है. कृप्या पुनः ट्राई करें. ", "@anErrorOccuredWhileDownloadingYourKeyfile": {}, "anyFilesWillOutliveYou": "आपके ज़रिए यहां अपलोड की जाने वाली कोई भी फ़ाइल आपकी उम्र से भी ज़्यादा समय तक टिकी रहेगी!", @@ -330,7 +336,7 @@ "@createHereEmphasized": { "description": "Create the manifest in the current selected folder" }, - "createManifest": "मैनिफ़ेस्ट बनाएं", + "createManifest": "नया मैनिफेस्ट ", "@createManifest": { "description": "To create a manifest" }, @@ -346,7 +352,7 @@ "@createNewSnapshot": { "description": "The action of snapshotting a drive" }, - "createSnapshot": "स्नैपशॉट बनाएं", + "createSnapshot": "नया स्नैपशॉट ", "@createSnapshot": { "description": "The action of snapshotting a drive" }, @@ -685,6 +691,14 @@ "@failedToCreateManifestEmphasized": { "description": "The manifest could not be created" }, + "failedToCreatePin": "पिन क्रिएट नही हो पाया. कृप्या पुनः प्रयास करें. ", + "@failedToCreatePin": { + "description": "The pin could not be created" + }, + "failedToRetrieveFileInfromation": "फाइल इनफॉर्मेशन रिट्रीव नहीं हो पाया ", + "@failedToRetrieveFileInfromation": { + "description": "Explains that there was an error while retrieving the file infromation" + }, "failedToSyncDrive": "ड्राइव के कॉन्टेंट को सिंक करने में असफल रहा।", "@failedToSyncDrive": { "description": "The app wasn't able to sync the drive" @@ -710,6 +724,10 @@ } } }, + "fileDoesExistButIsInvalid": "प्रोवाइड किया गया फाइल करप्टेड है और पिन नही किया जा सकता.", + "@fileDoesExistButIsInvalid": { + "description": "Explains that the file does exist, but some of its properties make it invalid" + }, "fileDownloadFailed": "फ़ाइल डाउनलोड असफल रहा", "@fileDownloadFailed": { "description": "Could not export the CSV data" @@ -734,6 +752,10 @@ "@fileID": { "description": "The Entity-ID tag of files" }, + "fileIsNotPublic": "प्रोवाइड किया गया ID एक प्राइवेट फाइल के लिए है और इसे पिन नहीं किया जा सकता.", + "@fileIsNotPublic": { + "description": "Warning explaining that the file is not public" + }, "fileName": "फ़ाइल का नाम", "@fileName": { "description": "The name of certain file" @@ -795,6 +817,10 @@ "@fileSystem": { "description": "Button label to select a file from file system" }, + "fileType": "फाइल टाइप ", + "@fileType": { + "description": "The MIME type of the file" + }, "fileWasCreatedWithName": "यह फ़ाइल {fileName} नाम से बनाई गई थी।", "@fileWasCreatedWithName": { "description": "File activity (journal): created", @@ -813,6 +839,10 @@ "@fileWasMoved": { "description": "File activity (journal): moved" }, + "fileWasPinnedToTheDrive": "ड्राइव से पिन किया गया फाइल ", + "@fileWasPinnedToTheDrive": { + "description": "File activity (journal): was pinned" + }, "fileWasRenamed": "इस फ़ाइल का नाम बदलकर {fileName} कर दिया गया था।", "@fileWasRenamed": { "description": "File activity (journal): renamed", @@ -955,6 +985,8 @@ "@help": { "description": "Link to a form for collecting feedback from the user" }, + "helpCenter": "हेल्प सेंटर ", + "@helpCenter": {}, "howAreConversionsDetermined": "कन्वर्जंस कैसे निर्धारित किया जाता है?", "@howAreConversionsDetermined": {}, "howDoesKeyfileLoginWork": "कीफाइल और सीड फ्रेज लॉगिन कैसे काम करते हैं?", @@ -1184,6 +1216,18 @@ "@movingItemsEmphasized": { "description": "Moving Items dialog title" }, + "multiDownloadCompleteWithSkippedFiles": "{numSkippedFiles} स्किप्ड फाइल(फाइल्स) के साथ डाउनलोड कंपलीट हो गया", + "@multiDownloadCompleteWithSkippedFiles": { + "description": "Title to modal shown when user completes multi-file download and has skipped files. " + }, + "multiDownloadDownloadingFilesProgress": "फाइल(फाइल्स) डाउनलोड हो रहें हैं...\n{totalNumFiles} में से {currentFileNum}", + "@multiDownloadDownloadingFilesProgress": { + "description": "Title to modal shown when user is downloading multiple file. " + }, + "multiDownloadErrorTryAgain": "आपके फाइल(फाइल्स) डाउनलोड करने में एक एरर आ गया है. कृप्या पुनः प्रयास करें.", + "@multiDownloadErrorTryAgain": { + "description": "Error message to show user when an error has occurred downloading a file within a set of multi-file downloads." + }, "name": "नाम", "@name": { "description": "Name of certain entity" @@ -1298,6 +1342,8 @@ "@orContinueWith": { "description": "text to continue with ar connect" }, + "ourChannels": "हमारे चैनल्स:", + "@ourChannels": {}, "password": "पासवर्ड", "@password": { "description": "Label for the 'password' input" @@ -1336,6 +1382,10 @@ "@personalDrivesEmphasized": { "description": "Sub-header for the personal drives list. Emphasized with upper case" }, + "pinFailedToUpload": "आपका पिन अपलोड नहीं हो पाया", + "@pinFailedToUpload": { + "description": "Explains that pin creation has failed" + }, "pleaseAcceptTheTermsToContinue": "जारी रखने के लिए कृपया नियम और शर्तें स्वीकार करें. ", "@pleaseAcceptTheTermsToContinue": { "description": "terms and conditions cta" @@ -1412,6 +1462,16 @@ "@publicDrives": { "description": "Public drives accordion title" }, + "quoteUpdatesIn": "क्वाेट {timer} में अपडेट हो रहा है ", + "@quoteUpdatesIn": { + "description": "Indicates the time left for the next update of the quote", + "placeholders": { + "timer": { + "type": "String", + "example": "10:00" + } + } + }, "recreateFolderEmphasized": "फ़ोल्डर फिर से बनाएं", "@recreateFolderEmphasized": { "description": "Recreate a folder that failed to be minted" @@ -1496,6 +1556,8 @@ "@selectWalletEmphasized": { "description": "Users can log in with a crypto wallet, so this prompts them to choose one for their login. Emphasized with upper case" }, + "share": "शेयर करें ", + "@share": {}, "sharedDrives": "ड्राइवों को शेयर किया गया है", "@sharedDrives": { "description": "Sub-header for the shared drives list" @@ -1528,6 +1590,20 @@ "@shareFileWithOthers": { "description": "To share a file link" }, + "shareLogsDescription": "आप अपने लॉग्स ArDrive टीम के साथ शेयर कर एप्प इंप्रूव करने में हमारी मदद कर सकते हैं. लॉग्स आपके डिवाइस पे डाउनलोड हो सकतें हैं और हमारे साथ इमेल, डिस्कॉर्ड के द्वारा शेयर हो सकते या सपोर्ट टिकट में एड हो सकतें हैं.", + "@shareLogsDescription": {}, + "shareLogsEmailBody": "ArDrive यूज़र लॉग अटैच हो गए हैं.", + "@shareLogsEmailBody": {}, + "shareLogsEmailSubject": "ArDrive लॉग्स ", + "@shareLogsEmailSubject": {}, + "shareLogsNativeShareSubject": "एआरड्राइव लॉग्स", + "@shareLogsNativeShareSubject": {}, + "shareLogsNativeShareText": "ARDrive उपयोगकर्ता लॉग संलग्न हैं।", + "@shareLogsNativeShareText": {}, + "shareLogsText": "लॉग्स शेयर करें ", + "@shareLogsText": {}, + "shareLogsWithEmailText": "इमेल भेजें ", + "@shareLogsWithEmailText": {}, "sharePendingFile": "आप जिस फ़ाइल को शेयर करने की कोशिश कर रहे हैं वह फ़िलहाल पेंडिंग है और इस समय शेयर नहीं की जा सकती।", "@sharePendingFile": { "description": "Pending file share dialog text" @@ -1692,6 +1768,20 @@ "@turboAddCreditsBlurb": { "description": "text placeholder for turbo balance when no user" }, + "turboCustomAmount": "कस्टम अमाउंट (सबसे कम {min} - सबसे ज्यादा {max})", + "@turboCustomAmount": { + "description": "Describes the range of credits the user can choose", + "placeholders": { + "max": { + "type": "String", + "example": "$10,000" + }, + "min": { + "type": "String", + "example": "$5" + } + } + }, "turboErrorMessageEstimationInformationFailed": "इन्फॉर्मेशन लोड करने में एरर. कृप्या पुनः प्रयास करें.", "@turboErrorMessageEstimationInformationFailed": {}, "turboErrorMessageFetchPaymentIntentFailed": "पेमेंट प्रोसेसर अभी उपलब्ध नहीं है, कृप्या कुछ देर बाद पुनः प्रयास करें.", @@ -1704,6 +1794,20 @@ "@turboErrorMessageSessionExpired": {}, "turboErrorMessageUnknown": "पेमेंट सफल नहीं रहा. कृप्या अपना कार्ड इन्फॉर्मेशन चेक करें और पुनः प्रयास करें.", "@turboErrorMessageUnknown": {}, + "turboPleaseEnterAmountBetween": "कृप्या {min} - {max} के बीच का अमाउंट एंटर करें ", + "@turboPleaseEnterAmountBetween": { + "description": "Error message for when the given amount is not in range", + "placeholders": { + "max": { + "type": "String", + "example": "$10,000" + }, + "min": { + "type": "String", + "example": "$5" + } + } + }, "unableToFetchEstimateAtThisTime": "इस समय पर एस्टीमेट प्राप्त करने में असमर्थ हैं.", "@unableToFetchEstimateAtThisTime": {}, "unableToUpdateQuote": "क्वोट अपडेट करने में असमर्थ हैं. कृप्या पुनः प्रयास करें.", @@ -1958,4 +2062,4 @@ "@zippingYourFiles": { "description": "Download failure message when a file is too big" } -} +} \ No newline at end of file diff --git a/lib/l10n/app_ja.arb b/lib/l10n/app_ja.arb index 6d1bb16094..cfcaaff697 100644 --- a/lib/l10n/app_ja.arb +++ b/lib/l10n/app_ja.arb @@ -22,6 +22,10 @@ "@addSomeFiles": { "description": "Empty folder" }, + "advanced": "詳細", + "@advanced": { + "description": "The advanced options" + }, "aggreeToTerms_body": "ArDriveのサービス利用規約とプライバシーポリシー同意する", "@aggreeToTerms_body": { "description": "Checkbox for agreeing to the terms of service and privacy policy" @@ -40,6 +44,8 @@ }, "amount": "金額", "@amount": {}, + "anErrorOccuredWhileDownloadingYourFiles": "ファイルのダウンロード中にエラーが発生しました。もう一度試してください。", + "@anErrorOccuredWhileDownloadingYourFiles": {}, "anErrorOccuredWhileDownloadingYourKeyfile": "ウォレットキーファイルのダウンロード中にエラーが発生しました。もう一度お試しください。", "@anErrorOccuredWhileDownloadingYourKeyfile": {}, "anyFilesWillOutliveYou": "ここにアップロードされたファイルはすべて、あなたの死後も存在し続けますす!", @@ -330,7 +336,7 @@ "@createHereEmphasized": { "description": "Create the manifest in the current selected folder" }, - "createManifest": "マニフェストを作成する", + "createManifest": "新規マニフェスト", "@createManifest": { "description": "To create a manifest" }, @@ -346,7 +352,7 @@ "@createNewSnapshot": { "description": "The action of snapshotting a drive" }, - "createSnapshot": "スナップショットを作成する", + "createSnapshot": "新規スナップショット", "@createSnapshot": { "description": "The action of snapshotting a drive" }, @@ -685,6 +691,14 @@ "@failedToCreateManifestEmphasized": { "description": "The manifest could not be created" }, + "failedToCreatePin": "ピンの作成に失敗しました。後で再試行してください。", + "@failedToCreatePin": { + "description": "The pin could not be created" + }, + "failedToRetrieveFileInfromation": "ファイル情報の取得に失敗しました", + "@failedToRetrieveFileInfromation": { + "description": "Explains that there was an error while retrieving the file infromation" + }, "failedToSyncDrive": "ドライブのコンテンツの同期に失敗しました。", "@failedToSyncDrive": { "description": "The app wasn't able to sync the drive" @@ -710,6 +724,10 @@ } } }, + "fileDoesExistButIsInvalid": "提供されたファイルは破損しており、ピン留めできません。", + "@fileDoesExistButIsInvalid": { + "description": "Explains that the file does exist, but some of its properties make it invalid" + }, "fileDownloadFailed": "ファイルのダウンロードが失敗しました", "@fileDownloadFailed": { "description": "Could not export the CSV data" @@ -734,6 +752,10 @@ "@fileID": { "description": "The Entity-ID tag of files" }, + "fileIsNotPublic": "提供されたIDはプライベートファイル用であり、ピン留めできません。", + "@fileIsNotPublic": { + "description": "Warning explaining that the file is not public" + }, "fileName": "ファイル名", "@fileName": { "description": "The name of certain file" @@ -795,6 +817,10 @@ "@fileSystem": { "description": "Button label to select a file from file system" }, + "fileType": "ファイルタイプ", + "@fileType": { + "description": "The MIME type of the file" + }, "fileWasCreatedWithName": "このファイルは{fileName}という名前で作成されました。", "@fileWasCreatedWithName": { "description": "File activity (journal): created", @@ -813,6 +839,10 @@ "@fileWasMoved": { "description": "File activity (journal): moved" }, + "fileWasPinnedToTheDrive": "ファイルがドライブにピン留めされました。", + "@fileWasPinnedToTheDrive": { + "description": "File activity (journal): was pinned" + }, "fileWasRenamed": "このファイルは{fileName}という名前に変更されました。", "@fileWasRenamed": { "description": "File activity (journal): renamed", @@ -955,6 +985,8 @@ "@help": { "description": "Link to a form for collecting feedback from the user" }, + "helpCenter": "ヘルプセンター", + "@helpCenter": {}, "howAreConversionsDetermined": "コンバージョンはどのようにして決定されるのですか?", "@howAreConversionsDetermined": {}, "howDoesKeyfileLoginWork": "キーファイルとシードフレーズによるログインはどのように行われるのですか?", @@ -1184,6 +1216,18 @@ "@movingItemsEmphasized": { "description": "Moving Items dialog title" }, + "multiDownloadCompleteWithSkippedFiles": "{numSkippedFiles}のファイルをスキップしてダウンロード完了", + "@multiDownloadCompleteWithSkippedFiles": { + "description": "Title to modal shown when user completes multi-file download and has skipped files. " + }, + "multiDownloadDownloadingFilesProgress": "{totalNumFiles}のうち{currentFileNum} のファイルをダウンロード中…", + "@multiDownloadDownloadingFilesProgress": { + "description": "Title to modal shown when user is downloading multiple file. " + }, + "multiDownloadErrorTryAgain": "ファイルのダウンロード中にエラーが発生しました。再試行してください。", + "@multiDownloadErrorTryAgain": { + "description": "Error message to show user when an error has occurred downloading a file within a set of multi-file downloads." + }, "name": "名前", "@name": { "description": "Name of certain entity" @@ -1298,6 +1342,8 @@ "@orContinueWith": { "description": "text to continue with ar connect" }, + "ourChannels": "私たちのチャンネル:", + "@ourChannels": {}, "password": "パスワード", "@password": { "description": "Label for the 'password' input" @@ -1336,6 +1382,10 @@ "@personalDrivesEmphasized": { "description": "Sub-header for the personal drives list. Emphasized with upper case" }, + "pinFailedToUpload": "ピンのアップロードに失敗しました", + "@pinFailedToUpload": { + "description": "Explains that pin creation has failed" + }, "pleaseAcceptTheTermsToContinue": "続行するには利用規約に同意してください。", "@pleaseAcceptTheTermsToContinue": { "description": "terms and conditions cta" @@ -1412,6 +1462,16 @@ "@publicDrives": { "description": "Public drives accordion title" }, + "quoteUpdatesIn": "{timer}で見積もり更新", + "@quoteUpdatesIn": { + "description": "Indicates the time left for the next update of the quote", + "placeholders": { + "timer": { + "type": "String", + "example": "10:00" + } + } + }, "recreateFolderEmphasized": "フォルダの再作成", "@recreateFolderEmphasized": { "description": "Recreate a folder that failed to be minted" @@ -1496,6 +1556,8 @@ "@selectWalletEmphasized": { "description": "Users can log in with a crypto wallet, so this prompts them to choose one for their login. Emphasized with upper case" }, + "share": "共有する", + "@share": {}, "sharedDrives": "共有ドライブ", "@sharedDrives": { "description": "Sub-header for the shared drives list" @@ -1528,6 +1590,20 @@ "@shareFileWithOthers": { "description": "To share a file link" }, + "shareLogsDescription": "ArDriveチームとあなたのログを共有して、アプリの改善にご協力ください。ログはお使いのデバイスにダウンロードして、電子メール、Discordで共有したり、サポートチケットに追加したりできます。", + "@shareLogsDescription": {}, + "shareLogsEmailBody": "ArDriveのユーザーログが添付されています。", + "@shareLogsEmailBody": {}, + "shareLogsEmailSubject": "ArDriveログ", + "@shareLogsEmailSubject": {}, + "shareLogsNativeShareSubject": "ARドライブログ", + "@shareLogsNativeShareSubject": {}, + "shareLogsNativeShareText": "ARDriveのユーザーログが添付されています。", + "@shareLogsNativeShareText": {}, + "shareLogsText": "共有ログ", + "@shareLogsText": {}, + "shareLogsWithEmailText": "電子メールを送信する", + "@shareLogsWithEmailText": {}, "sharePendingFile": "共有しようとしているファイルは現在保留中であり、現時点では共有できません。", "@sharePendingFile": { "description": "Pending file share dialog text" @@ -1692,6 +1768,20 @@ "@turboAddCreditsBlurb": { "description": "text placeholder for turbo balance when no user" }, + "turboCustomAmount": "カスタム量(最小 {min} ~ 最大 {max})", + "@turboCustomAmount": { + "description": "Describes the range of credits the user can choose", + "placeholders": { + "max": { + "type": "String", + "example": "$10,000" + }, + "min": { + "type": "String", + "example": "$5" + } + } + }, "turboErrorMessageEstimationInformationFailed": "情報の読み込みに失敗しました。もう一度お試しください。", "@turboErrorMessageEstimationInformationFailed": {}, "turboErrorMessageFetchPaymentIntentFailed": "支払い処理が現在利用できません。後でもう一度お試しください。", @@ -1704,6 +1794,20 @@ "@turboErrorMessageSessionExpired": {}, "turboErrorMessageUnknown": "決済に失敗しました。カード情報を確認して、もう一度やり直してください。", "@turboErrorMessageUnknown": {}, + "turboPleaseEnterAmountBetween": "{min} ~ {max} の間の量を入力してください", + "@turboPleaseEnterAmountBetween": { + "description": "Error message for when the given amount is not in range", + "placeholders": { + "max": { + "type": "String", + "example": "$10,000" + }, + "min": { + "type": "String", + "example": "$5" + } + } + }, "unableToFetchEstimateAtThisTime": "現在、見積もりを取ることができません。", "@unableToFetchEstimateAtThisTime": {}, "unableToUpdateQuote": "見積もりを更新できません。もう一度お試しください。", @@ -1958,4 +2062,4 @@ "@zippingYourFiles": { "description": "Download failure message when a file is too big" } -} +} \ No newline at end of file diff --git a/lib/l10n/app_zh-HK.arb b/lib/l10n/app_zh-HK.arb index 5c889baffa..bca716f571 100644 --- a/lib/l10n/app_zh-HK.arb +++ b/lib/l10n/app_zh-HK.arb @@ -1,5 +1,5 @@ { - "@@locale": "zh_HK", + "@@locale": "zh-HK", "accept": "接受", "@accept": {}, "addButtonTurbo": "添加", @@ -22,6 +22,10 @@ "@addSomeFiles": { "description": "Empty folder" }, + "advanced": "進階", + "@advanced": { + "description": "The advanced options" + }, "aggreeToTerms_body": "我同意 ArDrive 的使用及私隱條例", "@aggreeToTerms_body": { "description": "Checkbox for agreeing to the terms of service and privacy policy" @@ -40,6 +44,8 @@ }, "amount": "金額", "@amount": {}, + "anErrorOccuredWhileDownloadingYourFiles": "下載檔案時發生錯誤。請再試一次。", + "@anErrorOccuredWhileDownloadingYourFiles": {}, "anErrorOccuredWhileDownloadingYourKeyfile": "下載你的銀包密鑰文件時發生錯誤。請再試一次。", "@anErrorOccuredWhileDownloadingYourKeyfile": {}, "anyFilesWillOutliveYou": "所有上載到這裏的檔案, 都會比你於這地球上存在得更耐 !", @@ -330,7 +336,7 @@ "@createHereEmphasized": { "description": "Create the manifest in the current selected folder" }, - "createManifest": "建立資訊清單", + "createManifest": "開新清單", "@createManifest": { "description": "To create a manifest" }, @@ -346,7 +352,7 @@ "@createNewSnapshot": { "description": "The action of snapshotting a drive" }, - "createSnapshot": "建立快照", + "createSnapshot": "開新快照", "@createSnapshot": { "description": "The action of snapshotting a drive" }, @@ -685,6 +691,14 @@ "@failedToCreateManifestEmphasized": { "description": "The manifest could not be created" }, + "failedToCreatePin": "無法創建萬字夾。請稍後再試。", + "@failedToCreatePin": { + "description": "The pin could not be created" + }, + "failedToRetrieveFileInfromation": "無法擷取檔案資訊", + "@failedToRetrieveFileInfromation": { + "description": "Explains that there was an error while retrieving the file infromation" + }, "failedToSyncDrive": "未能同步檔案盤的內容。", "@failedToSyncDrive": { "description": "The app wasn't able to sync the drive" @@ -710,6 +724,10 @@ } } }, + "fileDoesExistButIsInvalid": "該檔案已損壞,無法置頂。", + "@fileDoesExistButIsInvalid": { + "description": "Explains that the file does exist, but some of its properties make it invalid" + }, "fileDownloadFailed": "檔案下載失敗", "@fileDownloadFailed": { "description": "Could not export the CSV data" @@ -734,6 +752,10 @@ "@fileID": { "description": "The Entity-ID tag of files" }, + "fileIsNotPublic": "所提供的ID是私人檔案的 ID,無法置頂。", + "@fileIsNotPublic": { + "description": "Warning explaining that the file is not public" + }, "fileName": "檔案用名", "@fileName": { "description": "The name of certain file" @@ -795,6 +817,10 @@ "@fileSystem": { "description": "Button label to select a file from file system" }, + "fileType": "檔案類型", + "@fileType": { + "description": "The MIME type of the file" + }, "fileWasCreatedWithName": "此檔案與 {fileName} 的用名重覆。", "@fileWasCreatedWithName": { "description": "File activity (journal): created", @@ -813,6 +839,10 @@ "@fileWasMoved": { "description": "File activity (journal): moved" }, + "fileWasPinnedToTheDrive": "檔案已置頂到硬碟。", + "@fileWasPinnedToTheDrive": { + "description": "File activity (journal): was pinned" + }, "fileWasRenamed": "此檔案已被重新命名為 {fileName} 。", "@fileWasRenamed": { "description": "File activity (journal): renamed", @@ -955,6 +985,8 @@ "@help": { "description": "Link to a form for collecting feedback from the user" }, + "helpCenter": "幫助中心", + "@helpCenter": {}, "howAreConversionsDetermined": "轉換率是如何決定的?", "@howAreConversionsDetermined": {}, "howDoesKeyfileLoginWork": "密鑰文件和助記詞登入如何運作?\n", @@ -1184,6 +1216,18 @@ "@movingItemsEmphasized": { "description": "Moving Items dialog title" }, + "multiDownloadCompleteWithSkippedFiles": "下載完成,有 {numSkippedFiles} 個檔案未下載", + "@multiDownloadCompleteWithSkippedFiles": { + "description": "Title to modal shown when user completes multi-file download and has skipped files. " + }, + "multiDownloadDownloadingFilesProgress": "正在下載檔案...共 {totalNumFiles} 個檔案中的第 {currentFileNum} 個", + "@multiDownloadDownloadingFilesProgress": { + "description": "Title to modal shown when user is downloading multiple file. " + }, + "multiDownloadErrorTryAgain": "下載您的檔案時發生錯誤。請重試。", + "@multiDownloadErrorTryAgain": { + "description": "Error message to show user when an error has occurred downloading a file within a set of multi-file downloads." + }, "name": "名字", "@name": { "description": "Name of certain entity" @@ -1298,6 +1342,8 @@ "@orContinueWith": { "description": "text to continue with ar connect" }, + "ourChannels": "我們的聯絡渠道:", + "@ourChannels": {}, "password": "密碼", "@password": { "description": "Label for the 'password' input" @@ -1336,6 +1382,10 @@ "@personalDrivesEmphasized": { "description": "Sub-header for the personal drives list. Emphasized with upper case" }, + "pinFailedToUpload": "上傳萬字夾失敗", + "@pinFailedToUpload": { + "description": "Explains that pin creation has failed" + }, "pleaseAcceptTheTermsToContinue": "請接受合約條款以繼續。", "@pleaseAcceptTheTermsToContinue": { "description": "terms and conditions cta" @@ -1412,6 +1462,16 @@ "@publicDrives": { "description": "Public drives accordion title" }, + "quoteUpdatesIn": "報價在{timer}後更新", + "@quoteUpdatesIn": { + "description": "Indicates the time left for the next update of the quote", + "placeholders": { + "timer": { + "type": "String", + "example": "10:00" + } + } + }, "recreateFolderEmphasized": "重新建立資料夾", "@recreateFolderEmphasized": { "description": "Recreate a folder that failed to be minted" @@ -1496,6 +1556,8 @@ "@selectWalletEmphasized": { "description": "Users can log in with a crypto wallet, so this prompts them to choose one for their login. Emphasized with upper case" }, + "share": "分享", + "@share": {}, "sharedDrives": "共享的硬碟", "@sharedDrives": { "description": "Sub-header for the shared drives list" @@ -1528,6 +1590,20 @@ "@shareFileWithOthers": { "description": "To share a file link" }, + "shareLogsDescription": "您可以與 ArDrive 團隊分享你的日誌,以幫助我們改進應用程式。日誌可以下載到您的裝置並透過電子郵件、Discord 或添加到支援查詢與我們分享。", + "@shareLogsDescription": {}, + "shareLogsEmailBody": "ArDrive 使用者日誌已附上。", + "@shareLogsEmailBody": {}, + "shareLogsEmailSubject": "ArDrive 日誌", + "@shareLogsEmailSubject": {}, + "shareLogsNativeShareSubject": "日誌", + "@shareLogsNativeShareSubject": {}, + "shareLogsNativeShareText": "已附加 ARDrive 使用者記錄檔。", + "@shareLogsNativeShareText": {}, + "shareLogsText": "分享日誌", + "@shareLogsText": {}, + "shareLogsWithEmailText": "發送電子郵件", + "@shareLogsWithEmailText": {}, "sharePendingFile": "您想分享的檔案仍待處理,目前尙無法分享。", "@sharePendingFile": { "description": "Pending file share dialog text" @@ -1692,6 +1768,20 @@ "@turboAddCreditsBlurb": { "description": "text placeholder for turbo balance when no user" }, + "turboCustomAmount": "自訂金額(最小{min} - 最大{max})", + "@turboCustomAmount": { + "description": "Describes the range of credits the user can choose", + "placeholders": { + "max": { + "type": "String", + "example": "$10,000" + }, + "min": { + "type": "String", + "example": "$5" + } + } + }, "turboErrorMessageEstimationInformationFailed": "加載信息時出錯。請再試一次。", "@turboErrorMessageEstimationInformationFailed": {}, "turboErrorMessageFetchPaymentIntentFailed": "付款處理器現無法使用,請稍後再試", @@ -1704,6 +1794,20 @@ "@turboErrorMessageSessionExpired": {}, "turboErrorMessageUnknown": "未能成功付款。請檢查你的信用卡資料並重試。", "@turboErrorMessageUnknown": {}, + "turboPleaseEnterAmountBetween": "請輸入介於{min}至{max}之間的金額", + "@turboPleaseEnterAmountBetween": { + "description": "Error message for when the given amount is not in range", + "placeholders": { + "max": { + "type": "String", + "example": "$10,000" + }, + "min": { + "type": "String", + "example": "$5" + } + } + }, "unableToFetchEstimateAtThisTime": "目前無法獲取估算值。", "@unableToFetchEstimateAtThisTime": {}, "unableToUpdateQuote": "無法更新報價。請再試一次。", @@ -1958,4 +2062,4 @@ "@zippingYourFiles": { "description": "Download failure message when a file is too big" } -} +} \ No newline at end of file diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index a08e86705f..c5ed2f6ba7 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -22,6 +22,10 @@ "@addSomeFiles": { "description": "Empty folder" }, + "advanced": "高级", + "@advanced": { + "description": "The advanced options" + }, "aggreeToTerms_body": "我同意ArDrive的服务条款与隐私政策.", "@aggreeToTerms_body": { "description": "Checkbox for agreeing to the terms of service and privacy policy" @@ -40,6 +44,8 @@ }, "amount": "数额", "@amount": {}, + "anErrorOccuredWhileDownloadingYourFiles": "下载您的文件时出错。请重试。", + "@anErrorOccuredWhileDownloadingYourFiles": {}, "anErrorOccuredWhileDownloadingYourKeyfile": "下载你的钱包密钥文件时出现了一个错误。请重试。", "@anErrorOccuredWhileDownloadingYourKeyfile": {}, "anyFilesWillOutliveYou": "任何你上传的文件都会永久存储!", @@ -330,7 +336,7 @@ "@createHereEmphasized": { "description": "Create the manifest in the current selected folder" }, - "createManifest": "创建manifest", + "createManifest": "新建清单", "@createManifest": { "description": "To create a manifest" }, @@ -346,7 +352,7 @@ "@createNewSnapshot": { "description": "The action of snapshotting a drive" }, - "createSnapshot": "创建快照", + "createSnapshot": "新建快照", "@createSnapshot": { "description": "The action of snapshotting a drive" }, @@ -685,6 +691,14 @@ "@failedToCreateManifestEmphasized": { "description": "The manifest could not be created" }, + "failedToCreatePin": "无法创建锁定。请稍后再试。", + "@failedToCreatePin": { + "description": "The pin could not be created" + }, + "failedToRetrieveFileInfromation": "无法检索文件信息", + "@failedToRetrieveFileInfromation": { + "description": "Explains that there was an error while retrieving the file infromation" + }, "failedToSyncDrive": "无法同步网盘内容.", "@failedToSyncDrive": { "description": "The app wasn't able to sync the drive" @@ -710,6 +724,10 @@ } } }, + "fileDoesExistButIsInvalid": "提供的文件已损坏,无法锁定。", + "@fileDoesExistButIsInvalid": { + "description": "Explains that the file does exist, but some of its properties make it invalid" + }, "fileDownloadFailed": "文件下载失败", "@fileDownloadFailed": { "description": "Could not export the CSV data" @@ -734,6 +752,10 @@ "@fileID": { "description": "The Entity-ID tag of files" }, + "fileIsNotPublic": "提供的ID用于专用文件,无法锁定。", + "@fileIsNotPublic": { + "description": "Warning explaining that the file is not public" + }, "fileName": "文件名称", "@fileName": { "description": "The name of certain file" @@ -795,6 +817,10 @@ "@fileSystem": { "description": "Button label to select a file from file system" }, + "fileType": "文件类型", + "@fileType": { + "description": "The MIME type of the file" + }, "fileWasCreatedWithName": "这个文件创建时名称为{fileName}.", "@fileWasCreatedWithName": { "description": "File activity (journal): created", @@ -813,6 +839,10 @@ "@fileWasMoved": { "description": "File activity (journal): moved" }, + "fileWasPinnedToTheDrive": "文件已锁定到驱动器。", + "@fileWasPinnedToTheDrive": { + "description": "File activity (journal): was pinned" + }, "fileWasRenamed": "这个文件被重命名为{fileName}.", "@fileWasRenamed": { "description": "File activity (journal): renamed", @@ -955,6 +985,8 @@ "@help": { "description": "Link to a form for collecting feedback from the user" }, + "helpCenter": "帮助中心", + "@helpCenter": {}, "howAreConversionsDetermined": "转换是如何确定的?", "@howAreConversionsDetermined": {}, "howDoesKeyfileLoginWork": "密钥文件和助记词登录是如何工作的?", @@ -1184,6 +1216,18 @@ "@movingItemsEmphasized": { "description": "Moving Items dialog title" }, + "multiDownloadCompleteWithSkippedFiles": "下载完成,跳过了{numSkippedFiles}个文件", + "@multiDownloadCompleteWithSkippedFiles": { + "description": "Title to modal shown when user completes multi-file download and has skipped files. " + }, + "multiDownloadDownloadingFilesProgress": "正在下载文件... {totalNumFiles}的{currentFileNum}", + "@multiDownloadDownloadingFilesProgress": { + "description": "Title to modal shown when user is downloading multiple file. " + }, + "multiDownloadErrorTryAgain": "下载文件时出错。请重试。", + "@multiDownloadErrorTryAgain": { + "description": "Error message to show user when an error has occurred downloading a file within a set of multi-file downloads." + }, "name": "名称", "@name": { "description": "Name of certain entity" @@ -1298,6 +1342,8 @@ "@orContinueWith": { "description": "text to continue with ar connect" }, + "ourChannels": "我们的渠道:", + "@ourChannels": {}, "password": "密码", "@password": { "description": "Label for the 'password' input" @@ -1336,6 +1382,10 @@ "@personalDrivesEmphasized": { "description": "Sub-header for the personal drives list. Emphasized with upper case" }, + "pinFailedToUpload": "您的锁定上传失败", + "@pinFailedToUpload": { + "description": "Explains that pin creation has failed" + }, "pleaseAcceptTheTermsToContinue": "请接受条款和条件以继续。", "@pleaseAcceptTheTermsToContinue": { "description": "terms and conditions cta" @@ -1412,6 +1462,16 @@ "@publicDrives": { "description": "Public drives accordion title" }, + "quoteUpdatesIn": "{timer}中的报价更新", + "@quoteUpdatesIn": { + "description": "Indicates the time left for the next update of the quote", + "placeholders": { + "timer": { + "type": "String", + "example": "10:00" + } + } + }, "recreateFolderEmphasized": "重新创建文件夹", "@recreateFolderEmphasized": { "description": "Recreate a folder that failed to be minted" @@ -1496,6 +1556,8 @@ "@selectWalletEmphasized": { "description": "Users can log in with a crypto wallet, so this prompts them to choose one for their login. Emphasized with upper case" }, + "share": "分享", + "@share": {}, "sharedDrives": "共享驱动器", "@sharedDrives": { "description": "Sub-header for the shared drives list" @@ -1528,6 +1590,20 @@ "@shareFileWithOthers": { "description": "To share a file link" }, + "shareLogsDescription": "您可以与ArDrive团队分享您的日志,帮助我们改进应用程序。日志可以下载到您的设备上,并通过电子邮件、Discord或添加到支持票证中与我们共享。", + "@shareLogsDescription": {}, + "shareLogsEmailBody": "ArDrive用户日志附后。", + "@shareLogsEmailBody": {}, + "shareLogsEmailSubject": "ArDrive日志", + "@shareLogsEmailSubject": {}, + "shareLogsNativeShareSubject": "arDrive 日志", + "@shareLogsNativeShareSubject": {}, + "shareLogsNativeShareText": "已附上 arDrive 用户日志。", + "@shareLogsNativeShareText": {}, + "shareLogsText": "分享日志", + "@shareLogsText": {}, + "shareLogsWithEmailText": "发送电子邮件", + "@shareLogsWithEmailText": {}, "sharePendingFile": "你所要分享的文件目前未被网络确认,暂时无 法被分享。请稍后重试。", "@sharePendingFile": { "description": "Pending file share dialog text" @@ -1692,6 +1768,20 @@ "@turboAddCreditsBlurb": { "description": "text placeholder for turbo balance when no user" }, + "turboCustomAmount": "自定义金额(最小值 {min} - 最大值 {max})", + "@turboCustomAmount": { + "description": "Describes the range of credits the user can choose", + "placeholders": { + "max": { + "type": "String", + "example": "$10,000" + }, + "min": { + "type": "String", + "example": "$5" + } + } + }, "turboErrorMessageEstimationInformationFailed": "加载信息时出现错误。请重试。", "@turboErrorMessageEstimationInformationFailed": {}, "turboErrorMessageFetchPaymentIntentFailed": "支付处理程序目前不可用,请稍后重试", @@ -1704,6 +1794,20 @@ "@turboErrorMessageSessionExpired": {}, "turboErrorMessageUnknown": "支付不成功。请检查你的卡信息然后重试。", "@turboErrorMessageUnknown": {}, + "turboPleaseEnterAmountBetween": "请输入介于{min} - {max}之间的金额", + "@turboPleaseEnterAmountBetween": { + "description": "Error message for when the given amount is not in range", + "placeholders": { + "max": { + "type": "String", + "example": "$10,000" + }, + "min": { + "type": "String", + "example": "$5" + } + } + }, "unableToFetchEstimateAtThisTime": "此时无法获取估计值。", "@unableToFetchEstimateAtThisTime": {}, "unableToUpdateQuote": "无法更新报价。请重试。", @@ -1958,4 +2062,4 @@ "@zippingYourFiles": { "description": "Download failure message when a file is too big" } -} +} \ No newline at end of file From dac42f869825d8909ced27a2be150a07801c679b Mon Sep 17 00:00:00 2001 From: Mati Date: Mon, 18 Sep 2023 13:05:39 -0300 Subject: [PATCH 06/24] feat(deps): updates ardrive-io dep PE-4554 --- pubspec.lock | 4 ++-- pubspec.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index aeb6483b82..c976001b5c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -79,8 +79,8 @@ packages: dependency: "direct main" description: path: "." - ref: "v1.9.1" - resolved-ref: c240bab3c61efec613832ce5636226aff2a18d63 + ref: PE-4554_detach_icon + resolved-ref: "6ab769ae53b6696a469a0e4a93bed99e4cc6ac0e" url: "https://github.com/ar-io/ardrive_ui.git" source: git version: "1.9.1" diff --git a/pubspec.yaml b/pubspec.yaml index a075f39234..cd54026d82 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -39,7 +39,7 @@ dependencies: ardrive_ui: git: url: https://github.com/ar-io/ardrive_ui.git - ref: v1.9.1 + ref: PE-4554_detach_icon artemis: ^7.0.0-beta.13 arweave: git: From 357bc085d0bc0591b34ed35b7e9cffa17098a77a Mon Sep 17 00:00:00 2001 From: Mati Date: Mon, 18 Sep 2023 13:06:10 -0300 Subject: [PATCH 07/24] feat(drive detail page): updates the detach drive icon PE-4554 --- lib/pages/drive_detail/drive_detail_page.dart | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/pages/drive_detail/drive_detail_page.dart b/lib/pages/drive_detail/drive_detail_page.dart index 06fb51832d..1aa1e3b1d7 100644 --- a/lib/pages/drive_detail/drive_detail_page.dart +++ b/lib/pages/drive_detail/drive_detail_page.dart @@ -379,9 +379,10 @@ class _DriveDetailPageState extends State { ); }, content: _buildItem( - appLocalizationsOf(context) - .detachDrive, - ArDriveIcons.triangle()), + appLocalizationsOf(context) + .detachDrive, + ArDriveIcons.detach(), + ), ), ], child: HoverWidget( From 791649ddfe53ab22c599f9b394bd64fb2d9f1e48 Mon Sep 17 00:00:00 2001 From: Mati Date: Mon, 18 Sep 2023 13:37:30 -0300 Subject: [PATCH 08/24] feat(pin file bloc): tag public pinned files only PE-4545 --- lib/blocs/pin_file/pin_file_bloc.dart | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/blocs/pin_file/pin_file_bloc.dart b/lib/blocs/pin_file/pin_file_bloc.dart index 9d1888eb63..332636d99e 100644 --- a/lib/blocs/pin_file/pin_file_bloc.dart +++ b/lib/blocs/pin_file/pin_file_bloc.dart @@ -312,6 +312,8 @@ class PinFileBloc extends Bloc { .folderById(driveId: _driveId, folderId: _parentFolderId) .getSingle(); + final isAPublicPin = fileKey == null; + if (_turboUploadService.useTurboUpload) { final fileDataItem = await _arweave.prepareEntityDataItem( newFileEntity, @@ -320,12 +322,11 @@ class PinFileBloc extends Bloc { skipSignature: true, ); - fileDataItem.addTag( - EntityTag.arFsPin, - 'true', - ); - if (fileKey == null) { - // If file is public + if (isAPublicPin) { + fileDataItem.addTag( + EntityTag.arFsPin, + 'true', + ); fileDataItem.addTag( EntityTag.pinnedDataTx, newFileEntity.dataTxId!, @@ -347,12 +348,11 @@ class PinFileBloc extends Bloc { skipSignature: true, ); - fileDataItem.addTag( - EntityTag.arFsPin, - 'true', - ); - if (fileKey == null) { - // If file is public + if (isAPublicPin) { + fileDataItem.addTag( + EntityTag.arFsPin, + 'true', + ); fileDataItem.addTag( EntityTag.pinnedDataTx, newFileEntity.dataTxId!, From d3b774b4122cd9114644cd2e8b10ec007ced729b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Batista?= Date: Mon, 18 Sep 2023 18:34:15 -0300 Subject: [PATCH 09/24] feat(localization es): corrects some spanish nits PE-4616 --- lib/l10n/app_es.arb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 5353eae112..a0c4e94fe7 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -352,7 +352,7 @@ "@createNewSnapshot": { "description": "The action of snapshotting a drive" }, - "createSnapshot": "Nueva captura", + "createSnapshot": "Nueva instantánea", "@createSnapshot": { "description": "The action of snapshotting a drive" }, @@ -1180,7 +1180,7 @@ "@monthlyCharges": { "description": "Payment philosophy" }, - "moreInfo": "Más Información", + "moreInfo": "Más información", "@moreInfo": { "description": "More info" }, @@ -1608,7 +1608,7 @@ "@sharePendingFile": { "description": "Pending file share dialog text" }, - "showMenu": "Mostrar Menú", + "showMenu": "Mostrar menú", "@showMenu": { "description": "show menu text" }, @@ -1768,7 +1768,7 @@ "@turboAddCreditsBlurb": { "description": "text placeholder for turbo balance when no user" }, - "turboCustomAmount": "Importe personalizado (mín. {min} - máx. {max})", + "turboCustomAmount": "Importe personalizado (mín. {min} - máx. {max})", "@turboCustomAmount": { "description": "Describes the range of credits the user can choose", "placeholders": { From 07d2c9a60c5a6fd9e633f630465bdf99ad747fd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Batista?= Date: Mon, 18 Sep 2023 18:36:37 -0300 Subject: [PATCH 10/24] feat(localization es): corrects some spanish nits PE-4616 --- lib/l10n/app_es.arb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index a0c4e94fe7..93981d562e 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -1490,7 +1490,7 @@ "@rename": { "description": "The action of renaming; imperative form" }, - "renameDrive": "Renombrar Unidad", + "renameDrive": "Renombrar unidad", "@renameDrive": { "description": "The action of renaming a drive" }, From cc06f73f4f5216553d6820a0e19708177b0eff3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Batista?= Date: Mon, 18 Sep 2023 18:37:18 -0300 Subject: [PATCH 11/24] feat(localization zh-hk): reverts unnecessary change PE-4616 --- lib/l10n/app_zh-HK.arb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/l10n/app_zh-HK.arb b/lib/l10n/app_zh-HK.arb index bca716f571..0c0aa6628d 100644 --- a/lib/l10n/app_zh-HK.arb +++ b/lib/l10n/app_zh-HK.arb @@ -1,5 +1,5 @@ { - "@@locale": "zh-HK", + "@@locale": "zh_HK", "accept": "接受", "@accept": {}, "addButtonTurbo": "添加", From 68a11d010a271eb5888cf95099b50750d1aaffd8 Mon Sep 17 00:00:00 2001 From: Mati Date: Tue, 19 Sep 2023 12:47:08 -0300 Subject: [PATCH 12/24] feat(create drive cubit): fixes bad casting PE-4597 --- .../drive_create/drive_create_cubit.dart | 14 ++--- .../drive_create/drive_create_state.dart | 59 +++++++++++++++---- lib/components/drive_create_form.dart | 3 +- 3 files changed, 57 insertions(+), 19 deletions(-) diff --git a/lib/blocs/drive_create/drive_create_cubit.dart b/lib/blocs/drive_create/drive_create_cubit.dart index 0ecf864ca3..7050d2e5f7 100644 --- a/lib/blocs/drive_create/drive_create_cubit.dart +++ b/lib/blocs/drive_create/drive_create_cubit.dart @@ -41,13 +41,13 @@ class DriveCreateCubit extends Cubit { _driveDao = driveDao, _profileCubit = profileCubit, _drivesCubit = drivesCubit, - super(DriveCreateInitial(privacy: DrivePrivacy.private)); + super(const DriveCreateInitial(privacy: DrivePrivacy.private)); void onPrivacyChanged() { final privacy = form.control('privacy').value == DrivePrivacy.private.name ? DrivePrivacy.private : DrivePrivacy.public; - emit((state as DriveCreateInitial).copyWith(privacy: privacy)); + emit(state.copyWith(privacy: privacy)); } Future submit( @@ -55,18 +55,18 @@ class DriveCreateCubit extends Cubit { ) 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; @@ -153,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 4b0a6048e9..7c07247a49 100644 --- a/lib/blocs/drive_create/drive_create_state.dart +++ b/lib/blocs/drive_create/drive_create_state.dart @@ -2,29 +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 { - final DrivePrivacy privacy; - - DriveCreateInitial({required this.privacy}); + const DriveCreateInitial({required super.privacy}); + @override DriveCreateInitial copyWith({DrivePrivacy? privacy}) { return DriveCreateInitial(privacy: privacy ?? this.privacy); } +} + +class DriveCreateZeroBalance extends DriveCreateState { + const DriveCreateZeroBalance({required super.privacy}); @override - List get props => [privacy]; + 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 DriveCreateZeroBalance extends DriveCreateState {} +class DriveCreateSuccess extends DriveCreateState { + const DriveCreateSuccess({required super.privacy}); + + @override + DriveCreateSuccess copyWith({DrivePrivacy? privacy}) { + return DriveCreateSuccess(privacy: privacy ?? this.privacy); + } +} -class DriveCreateInProgress extends DriveCreateState {} +class DriveCreateFailure extends DriveCreateState { + const DriveCreateFailure({required super.privacy}); -class DriveCreateSuccess extends DriveCreateState {} + @override + DriveCreateFailure copyWith({DrivePrivacy? privacy}) { + return DriveCreateFailure(privacy: privacy ?? this.privacy); + } +} -class DriveCreateFailure extends DriveCreateState {} +class DriveCreateWalletMismatch extends DriveCreateState { + const DriveCreateWalletMismatch({required super.privacy}); -class DriveCreateWalletMismatch extends DriveCreateState {} + @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 0aa1efe782..5800b22c93 100644 --- a/lib/components/drive_create_form.dart +++ b/lib/components/drive_create_form.dart @@ -74,8 +74,7 @@ class _DriveCreateFormState extends State { ], ); } else { - final stateAsInitial = state as DriveCreateInitial; - final privacy = stateAsInitial.privacy; + final privacy = state.privacy; return ArDriveStandardModal( title: appLocalizationsOf(context).createDriveEmphasized, From c90af0424e7545ac000a5702e7ec33e0be94fbcf Mon Sep 17 00:00:00 2001 From: Mati Date: Tue, 19 Sep 2023 12:47:36 -0300 Subject: [PATCH 13/24] test(create drive cubit): attempts to fix skipped test PE-4597 --- test/blocs/drive_create_cubit_test.dart | 235 +++++++++++++----------- 1 file changed, 132 insertions(+), 103 deletions(-) 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'); + }, + ); } From b1033afb4734dd76bc356fab4dfdaf60d58e85c8 Mon Sep 17 00:00:00 2001 From: Mati Date: Tue, 19 Sep 2023 12:53:08 -0300 Subject: [PATCH 14/24] Revert "feat(create drive cubit): fixes bad casting PE-4597" This reverts commit 68a11d010a271eb5888cf95099b50750d1aaffd8. --- .../drive_create/drive_create_cubit.dart | 14 ++--- .../drive_create/drive_create_state.dart | 59 ++++--------------- lib/components/drive_create_form.dart | 3 +- 3 files changed, 19 insertions(+), 57 deletions(-) diff --git a/lib/blocs/drive_create/drive_create_cubit.dart b/lib/blocs/drive_create/drive_create_cubit.dart index 7050d2e5f7..0ecf864ca3 100644 --- a/lib/blocs/drive_create/drive_create_cubit.dart +++ b/lib/blocs/drive_create/drive_create_cubit.dart @@ -41,13 +41,13 @@ class DriveCreateCubit extends Cubit { _driveDao = driveDao, _profileCubit = profileCubit, _drivesCubit = drivesCubit, - super(const DriveCreateInitial(privacy: DrivePrivacy.private)); + super(DriveCreateInitial(privacy: DrivePrivacy.private)); void onPrivacyChanged() { final privacy = form.control('privacy').value == DrivePrivacy.private.name ? DrivePrivacy.private : DrivePrivacy.public; - emit(state.copyWith(privacy: privacy)); + emit((state as DriveCreateInitial).copyWith(privacy: privacy)); } Future submit( @@ -55,18 +55,18 @@ class DriveCreateCubit extends Cubit { ) async { final profile = _profileCubit.state as ProfileLoggedIn; if (await _profileCubit.logoutIfWalletMismatch()) { - emit(DriveCreateWalletMismatch(privacy: state.privacy)); + emit(DriveCreateWalletMismatch()); return; } final minimumWalletBalance = BigInt.from(10000000); if (profile.walletBalance <= minimumWalletBalance && !_turboUploadService.useTurboUpload) { - emit(DriveCreateZeroBalance(privacy: state.privacy)); + emit(DriveCreateZeroBalance()); return; } - emit(DriveCreateInProgress(privacy: state.privacy)); + emit(DriveCreateInProgress()); try { final String drivePrivacy = form.control('privacy').value; @@ -153,12 +153,12 @@ class DriveCreateCubit extends Cubit { addError(err); } - emit(DriveCreateSuccess(privacy: state.privacy)); + emit(DriveCreateSuccess()); } @override void onError(Object error, StackTrace stackTrace) { - emit(DriveCreateFailure(privacy: state.privacy)); + emit(DriveCreateFailure()); 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 7c07247a49..4b0a6048e9 100644 --- a/lib/blocs/drive_create/drive_create_state.dart +++ b/lib/blocs/drive_create/drive_create_state.dart @@ -2,68 +2,29 @@ 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 => [privacy]; + List get props => []; } class DriveCreateInitial extends DriveCreateState { - const DriveCreateInitial({required super.privacy}); + final DrivePrivacy privacy; + + DriveCreateInitial({required this.privacy}); - @override DriveCreateInitial copyWith({DrivePrivacy? privacy}) { return DriveCreateInitial(privacy: privacy ?? this.privacy); } -} - -class DriveCreateZeroBalance extends DriveCreateState { - const DriveCreateZeroBalance({required super.privacy}); - - @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); - } + List get props => [privacy]; } -class DriveCreateSuccess extends DriveCreateState { - const DriveCreateSuccess({required super.privacy}); - - @override - DriveCreateSuccess copyWith({DrivePrivacy? privacy}) { - return DriveCreateSuccess(privacy: privacy ?? this.privacy); - } -} +class DriveCreateZeroBalance extends DriveCreateState {} -class DriveCreateFailure extends DriveCreateState { - const DriveCreateFailure({required super.privacy}); +class DriveCreateInProgress extends DriveCreateState {} - @override - DriveCreateFailure copyWith({DrivePrivacy? privacy}) { - return DriveCreateFailure(privacy: privacy ?? this.privacy); - } -} +class DriveCreateSuccess extends DriveCreateState {} -class DriveCreateWalletMismatch extends DriveCreateState { - const DriveCreateWalletMismatch({required super.privacy}); +class DriveCreateFailure extends DriveCreateState {} - @override - DriveCreateWalletMismatch copyWith({DrivePrivacy? privacy}) { - return DriveCreateWalletMismatch(privacy: privacy ?? this.privacy); - } -} +class DriveCreateWalletMismatch extends DriveCreateState {} diff --git a/lib/components/drive_create_form.dart b/lib/components/drive_create_form.dart index 5800b22c93..0aa1efe782 100644 --- a/lib/components/drive_create_form.dart +++ b/lib/components/drive_create_form.dart @@ -74,7 +74,8 @@ class _DriveCreateFormState extends State { ], ); } else { - final privacy = state.privacy; + final stateAsInitial = state as DriveCreateInitial; + final privacy = stateAsInitial.privacy; return ArDriveStandardModal( title: appLocalizationsOf(context).createDriveEmphasized, From dea7f44b843d151600ae4056da8d3313a9ff23ab Mon Sep 17 00:00:00 2001 From: Mati Date: Tue, 19 Sep 2023 12:53:21 -0300 Subject: [PATCH 15/24] Revert "test(create drive cubit): attempts to fix skipped test PE-4597" This reverts commit c90af0424e7545ac000a5702e7ec33e0be94fbcf. --- test/blocs/drive_create_cubit_test.dart | 235 +++++++++++------------- 1 file changed, 103 insertions(+), 132 deletions(-) diff --git a/test/blocs/drive_create_cubit_test.dart b/test/blocs/drive_create_cubit_test.dart index 8ff7e10435..97c7694b6b 100644 --- a/test/blocs/drive_create_cubit_test.dart +++ b/test/blocs/drive_create_cubit_test.dart @@ -1,11 +1,12 @@ @Tags(['broken']) import 'package:ardrive/blocs/blocs.dart'; -import 'package:ardrive/core/arfs/entities/arfs_entities.dart'; -import 'package:ardrive/entities/entity.dart'; +import 'package:ardrive/core/crypto/crypto.dart'; +import 'package:ardrive/entities/entities.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'; @@ -18,139 +19,109 @@ 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; - - 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: (_) {}, + 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(), ); - - 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: (_) {}, + 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( - 'does nothing when submitted without valid form', - build: () => driveCreateCubit, - act: (bloc) => bloc.submit(''), - expect: () => [], + 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, + }; + 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'); } From f6abb4efa35fd77527017b3a8dd69e5f7de72335 Mon Sep 17 00:00:00 2001 From: Mati Date: Tue, 19 Sep 2023 12:58:18 -0300 Subject: [PATCH 16/24] Revert "Revert "feat(create drive cubit): fixes bad casting PE-4597"" This reverts commit b1033afb4734dd76bc356fab4dfdaf60d58e85c8. --- .../drive_create/drive_create_cubit.dart | 14 ++--- .../drive_create/drive_create_state.dart | 59 +++++++++++++++---- lib/components/drive_create_form.dart | 3 +- 3 files changed, 57 insertions(+), 19 deletions(-) diff --git a/lib/blocs/drive_create/drive_create_cubit.dart b/lib/blocs/drive_create/drive_create_cubit.dart index 0ecf864ca3..7050d2e5f7 100644 --- a/lib/blocs/drive_create/drive_create_cubit.dart +++ b/lib/blocs/drive_create/drive_create_cubit.dart @@ -41,13 +41,13 @@ class DriveCreateCubit extends Cubit { _driveDao = driveDao, _profileCubit = profileCubit, _drivesCubit = drivesCubit, - super(DriveCreateInitial(privacy: DrivePrivacy.private)); + super(const DriveCreateInitial(privacy: DrivePrivacy.private)); void onPrivacyChanged() { final privacy = form.control('privacy').value == DrivePrivacy.private.name ? DrivePrivacy.private : DrivePrivacy.public; - emit((state as DriveCreateInitial).copyWith(privacy: privacy)); + emit(state.copyWith(privacy: privacy)); } Future submit( @@ -55,18 +55,18 @@ class DriveCreateCubit extends Cubit { ) 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; @@ -153,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 4b0a6048e9..7c07247a49 100644 --- a/lib/blocs/drive_create/drive_create_state.dart +++ b/lib/blocs/drive_create/drive_create_state.dart @@ -2,29 +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 { - final DrivePrivacy privacy; - - DriveCreateInitial({required this.privacy}); + const DriveCreateInitial({required super.privacy}); + @override DriveCreateInitial copyWith({DrivePrivacy? privacy}) { return DriveCreateInitial(privacy: privacy ?? this.privacy); } +} + +class DriveCreateZeroBalance extends DriveCreateState { + const DriveCreateZeroBalance({required super.privacy}); @override - List get props => [privacy]; + 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 DriveCreateZeroBalance extends DriveCreateState {} +class DriveCreateSuccess extends DriveCreateState { + const DriveCreateSuccess({required super.privacy}); + + @override + DriveCreateSuccess copyWith({DrivePrivacy? privacy}) { + return DriveCreateSuccess(privacy: privacy ?? this.privacy); + } +} -class DriveCreateInProgress extends DriveCreateState {} +class DriveCreateFailure extends DriveCreateState { + const DriveCreateFailure({required super.privacy}); -class DriveCreateSuccess extends DriveCreateState {} + @override + DriveCreateFailure copyWith({DrivePrivacy? privacy}) { + return DriveCreateFailure(privacy: privacy ?? this.privacy); + } +} -class DriveCreateFailure extends DriveCreateState {} +class DriveCreateWalletMismatch extends DriveCreateState { + const DriveCreateWalletMismatch({required super.privacy}); -class DriveCreateWalletMismatch extends DriveCreateState {} + @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 0aa1efe782..5800b22c93 100644 --- a/lib/components/drive_create_form.dart +++ b/lib/components/drive_create_form.dart @@ -74,8 +74,7 @@ class _DriveCreateFormState extends State { ], ); } else { - final stateAsInitial = state as DriveCreateInitial; - final privacy = stateAsInitial.privacy; + final privacy = state.privacy; return ArDriveStandardModal( title: appLocalizationsOf(context).createDriveEmphasized, From 019b3f016351aaee02df1c72335dbc8050578550 Mon Sep 17 00:00:00 2001 From: Mati Date: Tue, 19 Sep 2023 12:58:22 -0300 Subject: [PATCH 17/24] Revert "Revert "test(create drive cubit): attempts to fix skipped test PE-4597"" This reverts commit dea7f44b843d151600ae4056da8d3313a9ff23ab. --- test/blocs/drive_create_cubit_test.dart | 235 +++++++++++++----------- 1 file changed, 132 insertions(+), 103 deletions(-) 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'); + }, + ); } From 8b338ee149c2b2a689152009b195fe9c84caa86e Mon Sep 17 00:00:00 2001 From: Mati Date: Wed, 20 Sep 2023 12:46:04 -0300 Subject: [PATCH 18/24] feat(pin file bloc): awaits for the signature of the transaction PE-4645 --- lib/blocs/pin_file/pin_file_bloc.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/blocs/pin_file/pin_file_bloc.dart b/lib/blocs/pin_file/pin_file_bloc.dart index 332636d99e..257fdffb0a 100644 --- a/lib/blocs/pin_file/pin_file_bloc.dart +++ b/lib/blocs/pin_file/pin_file_bloc.dart @@ -333,7 +333,7 @@ class PinFileBloc extends Bloc { ); } - fileDataItem.sign(wallet); + await fileDataItem.sign(wallet); await _turboUploadService.postDataItem( dataItem: fileDataItem, @@ -359,7 +359,7 @@ class PinFileBloc extends Bloc { ); } - fileDataItem.sign(wallet); + await fileDataItem.sign(wallet); await _arweave.postTx(fileDataItem); newFileEntity.txId = fileDataItem.id; From ea2ccf261a5065756c99b5adad7ef3fc8d85ca34 Mon Sep 17 00:00:00 2001 From: Mati Date: Wed, 20 Sep 2023 14:28:18 -0300 Subject: [PATCH 19/24] feat(drive create form): makes focus on privacy dropdown field not to change the color PE-4647 --- lib/components/drive_create_form.dart | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/lib/components/drive_create_form.dart b/lib/components/drive_create_form.dart index 0645db6002..992bf13dab 100644 --- a/lib/components/drive_create_form.dart +++ b/lib/components/drive_create_form.dart @@ -108,7 +108,22 @@ class _DriveCreateFormState extends State { ReactiveDropdownField( formControlName: 'privacy', decoration: InputDecoration( - labelText: appLocalizationsOf(context).privacy), + label: Text( + appLocalizationsOf(context).privacy, + style: ArDriveTheme.of(context) + .themeData + .textFieldTheme + .inputTextStyle + .copyWith( + color: ArDriveTheme.of(context) + .themeData + .colors + .themeFgDisabled, + fontSize: 16, + ), + ), + focusedBorder: InputBorder.none, + ), showErrors: (control) => control.dirty && control.invalid, validationMessages: From 6b6f441b210f6d9ef471844eaea6f93b8e329548 Mon Sep 17 00:00:00 2001 From: Mati Date: Wed, 20 Sep 2023 17:30:09 -0300 Subject: [PATCH 20/24] feat(pubspec): bumps the version of ardrive-ui PE-4554 --- pubspec.lock | 6 +++--- pubspec.yaml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index c976001b5c..8216f3c13f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -79,11 +79,11 @@ packages: dependency: "direct main" description: path: "." - ref: PE-4554_detach_icon - resolved-ref: "6ab769ae53b6696a469a0e4a93bed99e4cc6ac0e" + ref: "v1.9.2" + resolved-ref: f8fb1bf502541e46caeb6e7e6270e2c488d7e126 url: "https://github.com/ar-io/ardrive_ui.git" source: git - version: "1.9.1" + version: "1.9.2" args: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index cd54026d82..7186a61c46 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -39,7 +39,7 @@ dependencies: ardrive_ui: git: url: https://github.com/ar-io/ardrive_ui.git - ref: PE-4554_detach_icon + ref: v1.9.2 artemis: ^7.0.0-beta.13 arweave: git: From a8bcfefa99a2917049124d959900d24adfe33ead Mon Sep 17 00:00:00 2001 From: Mati Date: Wed, 20 Sep 2023 17:57:36 -0300 Subject: [PATCH 21/24] feat(drive detail page): replaces the duplicate more info button with detach drive button for the mobile view --- lib/pages/drive_detail/drive_detail_page.dart | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/lib/pages/drive_detail/drive_detail_page.dart b/lib/pages/drive_detail/drive_detail_page.dart index 1aa1e3b1d7..f74c977cc7 100644 --- a/lib/pages/drive_detail/drive_detail_page.dart +++ b/lib/pages/drive_detail/drive_detail_page.dart @@ -861,22 +861,15 @@ class MobileFolderNavigation extends StatelessWidget { context.read().state is ProfileLoggedIn) ArDriveDropdownItem( onClick: () { - final bloc = context.read(); - - bloc.selectDataItem( - DriveDataTableItemMapper.fromDrive( - state.currentDrive, - (_) => null, - 0, - isOwner, - ), + showDetachDriveDialog( + context: context, + driveID: state.currentDrive.id, + driveName: state.currentDrive.name, ); }, content: _buildItem( - appLocalizationsOf(context).moreInfo, - ArDriveIcons.info( - size: defaultIconSize, - ), + appLocalizationsOf(context).detachDrive, + ArDriveIcons.detach(), ), ), ], From 7c8b9398986d9debea4e7ff505ca4129117245c4 Mon Sep 17 00:00:00 2001 From: Mati Date: Thu, 21 Sep 2023 11:59:52 -0300 Subject: [PATCH 22/24] feat(before release preps): before-release version bump and android release notes PE-4648 --- android/fastlane/metadata/android/en-US/changelogs/62.txt | 3 +++ pubspec.yaml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 android/fastlane/metadata/android/en-US/changelogs/62.txt diff --git a/android/fastlane/metadata/android/en-US/changelogs/62.txt b/android/fastlane/metadata/android/en-US/changelogs/62.txt new file mode 100644 index 0000000000..7ce29efd10 --- /dev/null +++ b/android/fastlane/metadata/android/en-US/changelogs/62.txt @@ -0,0 +1,3 @@ +- Adds an explanation for the privacy of drives on the Create Drive Modal +- Starts tagging public pins with new GQL Tags: ArFS-Pin and Pinned-Data-Tx +- Updates the icon for the Detach Drive option diff --git a/pubspec.yaml b/pubspec.yaml index 7186a61c46..8f6d51a681 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -3,7 +3,7 @@ description: Secure, permanent storage publish_to: 'none' -version: 2.13.0 +version: 2.14.0 environment: sdk: '>=2.18.5 <3.0.0' From ad9518b1924de7408028ba5f34379772255ab279 Mon Sep 17 00:00:00 2001 From: Mati Date: Thu, 21 Sep 2023 16:18:05 -0300 Subject: [PATCH 23/24] feat(deps): bumps ardrive-io dep version PE-4653 --- pubspec.lock | 6 +++--- pubspec.yaml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index 8216f3c13f..f04de53e91 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -79,11 +79,11 @@ packages: dependency: "direct main" description: path: "." - ref: "v1.9.2" - resolved-ref: f8fb1bf502541e46caeb6e7e6270e2c488d7e126 + ref: "v1.10.0" + resolved-ref: "72cd21de7cbd52067924cefac579cdb9d7ef39b7" url: "https://github.com/ar-io/ardrive_ui.git" source: git - version: "1.9.2" + version: "1.10.0" args: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 8f6d51a681..1da3240c66 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -39,7 +39,7 @@ dependencies: ardrive_ui: git: url: https://github.com/ar-io/ardrive_ui.git - ref: v1.9.2 + ref: v1.10.0 artemis: ^7.0.0-beta.13 arweave: git: From 1203e7f167168b76ee171e4ab2501878fe703d24 Mon Sep 17 00:00:00 2001 From: Mati Date: Thu, 21 Sep 2023 16:55:50 -0300 Subject: [PATCH 24/24] feat(before release preps): before-release version bump and android release notes PE-4655 --- android/fastlane/metadata/android/en-US/changelogs/63.txt | 4 ++++ pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 android/fastlane/metadata/android/en-US/changelogs/63.txt diff --git a/android/fastlane/metadata/android/en-US/changelogs/63.txt b/android/fastlane/metadata/android/en-US/changelogs/63.txt new file mode 100644 index 0000000000..f0ce5d8d3e --- /dev/null +++ b/android/fastlane/metadata/android/en-US/changelogs/63.txt @@ -0,0 +1,4 @@ +- Integrates new translations. +- Corrects buttons being overflowed on the modals. +- Makes the Pricacy field of the “Create New Drive” modal not change color when focused. +- Replaces the duplicate “More Info” button with Detach Drive on Mobile. diff --git a/pubspec.yaml b/pubspec.yaml index 8f6d51a681..abf5f44079 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -3,7 +3,7 @@ description: Secure, permanent storage publish_to: 'none' -version: 2.14.0 +version: 2.15.0 environment: sdk: '>=2.18.5 <3.0.0'