diff --git a/android/fastlane/metadata/android/en-US/changelogs/130.txt b/android/fastlane/metadata/android/en-US/changelogs/130.txt new file mode 100644 index 0000000000..5a6f1ea623 --- /dev/null +++ b/android/fastlane/metadata/android/en-US/changelogs/130.txt @@ -0,0 +1,2 @@ +- Improves handling of a few common errors +- Enhances empty drive and folder view \ No newline at end of file diff --git a/lib/blocs/drives/drives_cubit.dart b/lib/blocs/drives/drives_cubit.dart index e2fbd4f689..386487de4c 100644 --- a/lib/blocs/drives/drives_cubit.dart +++ b/lib/blocs/drives/drives_cubit.dart @@ -132,6 +132,10 @@ class DrivesCubit extends Cubit { drivesWithAlerts: const [], canCreateNewDrive: false); + if (isClosed) { + return; + } + emit(state); } diff --git a/lib/components/folder_create_form.dart b/lib/components/folder_create_form.dart index 4daacf7823..ff8f7dd18f 100644 --- a/lib/components/folder_create_form.dart +++ b/lib/components/folder_create_form.dart @@ -76,6 +76,7 @@ class _FolderCreateFormState extends State { showProgressDialog( context, title: appLocalizationsOf(context).creatingFolderEmphasized, + useNewArDriveUI: true, ); } else if (state is FolderCreateSuccess) { Navigator.pop(context); @@ -91,14 +92,15 @@ class _FolderCreateFormState extends State { description: appLocalizationsOf(context).entityAlreadyExists( state.folderName, ), + useNewArDriveUI: true, ); } }, - builder: (context, state) => ArDriveStandardModal( + builder: (context, state) => ArDriveStandardModalNew( title: appLocalizationsOf(context).createFolderEmphasized, content: SizedBox( width: kMediumDialogWidth, - child: ArDriveTextField( + child: ArDriveTextFieldNew( controller: _folderNameController, autofocus: true, onFieldSubmitted: (value) { diff --git a/lib/components/pin_file_dialog.dart b/lib/components/pin_file_dialog.dart index 52c15e1ad3..4d80b7565c 100644 --- a/lib/components/pin_file_dialog.dart +++ b/lib/components/pin_file_dialog.dart @@ -100,14 +100,14 @@ class PinFileDialog extends StatelessWidget { final idValidationError = _getIdValidationError(context, state); final nameValidationError = _getNameValidationError(context, state); - return ArDriveStandardModal( + return ArDriveStandardModalNew( title: appLocalizationsOf(context).newFilePin, content: SizedBox( width: kMediumDialogWidth, child: Column( mainAxisSize: MainAxisSize.min, children: [ - ArDriveTextField( + ArDriveTextFieldNew( isEnabled: state is! PinFileNetworkCheckRunning, label: appLocalizationsOf(context).enterTxIdOrFileId, isFieldRequired: true, @@ -119,7 +119,7 @@ class PinFileDialog extends StatelessWidget { ); }, ), - ArDriveTextField( + ArDriveTextFieldNew( isEnabled: true, label: appLocalizationsOf(context).enterFileName, isFieldRequired: true, @@ -154,10 +154,6 @@ class PinFileDialog extends StatelessWidget { } String? _getIdValidationError(BuildContext context, PinFileState state) { - // if (state is PinFileInitial) { - // return null; - // } - if (state.idValidation == IdValidationResult.invalid) { return appLocalizationsOf(context).theIdProvidedIsNotValid; } else if (state.idValidation == IdValidationResult.required) { @@ -183,10 +179,6 @@ class PinFileDialog extends StatelessWidget { } String? _getNameValidationError(BuildContext context, PinFileState state) { - // if (state is PinFileInitial) { - // return null; - // } - if (state.nameValidation == NameValidationResult.invalid) { return appLocalizationsOf(context).validationInvalid; } else if (state.nameValidation == NameValidationResult.required) { @@ -197,21 +189,22 @@ class PinFileDialog extends StatelessWidget { return null; } - ArDriveStandardModal _errorDialog( + ArDriveStandardModalNew _errorDialog( BuildContext context, { required String errorText, bool doublePop = false, }) => - ArDriveStandardModal( + ArDriveStandardModalNew( width: kMediumDialogWidth, title: appLocalizationsOf(context).failedToCreatePin, content: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - const SizedBox(height: 16), - Text(errorText), - const SizedBox(height: 16), + Padding( + padding: const EdgeInsets.symmetric(vertical: 16), + child: Text(errorText), + ), ], ), actions: [ diff --git a/lib/components/progress_dialog.dart b/lib/components/progress_dialog.dart index 86edf8f94a..84f02512cc 100644 --- a/lib/components/progress_dialog.dart +++ b/lib/components/progress_dialog.dart @@ -8,6 +8,7 @@ Future showProgressDialog( BuildContext context, { required String title, List? actions, + bool useNewArDriveUI = false, }) => showArDriveDialog( context, @@ -15,6 +16,7 @@ Future showProgressDialog( content: ProgressDialog( title: title, actions: actions ?? const [], + useNewArDriveUI: useNewArDriveUI, ), ); diff --git a/lib/core/crypto/crypto.dart b/lib/core/crypto/crypto.dart index 03b1c79340..ccab9c6e9c 100644 --- a/lib/core/crypto/crypto.dart +++ b/lib/core/crypto/crypto.dart @@ -92,7 +92,10 @@ class ArDriveCrypto { final cipherIvTag = transaction.getTag(EntityTag.cipherIv); if (cipher == null || cipherIvTag == null) { - throw MissingCipherTagException(); + throw MissingCipherTagException( + corruptedDataAppVersion: transaction.getTag(EntityTag.appVersion), + corruptedTransactionId: transaction.id, + ); } final cipherIv = utils.decodeBase64ToBytes(cipherIvTag); @@ -128,6 +131,7 @@ class ArDriveCrypto { } else { throw UnknownCipherException( corruptedDataAppVersion: transaction.getTag(EntityTag.appVersion), + corruptedTransactionId: transaction.id, ); } @@ -143,6 +147,7 @@ class ArDriveCrypto { /// Unknow error throw TransactionDecryptionException( corruptedDataAppVersion: transaction.getTag(EntityTag.appVersion), + corruptedTransactionId: transaction.id, ); } } @@ -161,6 +166,7 @@ class ArDriveCrypto { if (cipher == null || cipherIvTag == null) { throw MissingCipherTagException( corruptedDataAppVersion: transaction.getTag(EntityTag.appVersion), + corruptedTransactionId: transaction.id, ); } diff --git a/lib/pages/drive_detail/components/drive_detail_folder_empty_card.dart b/lib/pages/drive_detail/components/drive_detail_folder_empty_card.dart index 7084cc4ff7..2a87e4cc6a 100644 --- a/lib/pages/drive_detail/components/drive_detail_folder_empty_card.dart +++ b/lib/pages/drive_detail/components/drive_detail_folder_empty_card.dart @@ -1,87 +1,653 @@ part of '../drive_detail_page.dart'; -class DriveDetailFolderEmptyCard extends StatelessWidget { +class DriveDetailFolderEmptyCard extends StatefulWidget { final bool promptToAddFiles; final String driveId; final String parentFolderId; + final bool isRootFolder; const DriveDetailFolderEmptyCard({ super.key, this.promptToAddFiles = false, required this.driveId, required this.parentFolderId, + required this.isRootFolder, }); + @override + State createState() => + _DriveDetailFolderEmptyCardState(); +} + +class _DriveDetailFolderEmptyCardState + extends State { @override Widget build(BuildContext context) => buildArDriveCard(context); Widget buildArDriveCard(BuildContext context) { - return Padding( - padding: const EdgeInsets.symmetric(vertical: 20), - child: ArDriveCard( - backgroundColor: - ArDriveTheme.of(context).themeData.tableTheme.backgroundColor, - width: double.infinity, - content: Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Flexible( - child: SizedBox( - height: 45, - ), + ArDriveTypographyNew.of(context); + final colorTokens = ArDriveTheme.of(context).themeData.colorTokens; + return BlocBuilder( + builder: (context, state) { + final isANewUser = (state as DrivesLoadSuccess).userDrives.length == 1; + + return ScreenTypeLayout.builder(mobile: (context) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: Stack( + children: [ + Positioned( + bottom: 0, + right: 0, + child: SvgPicture.asset( + Resources.images.login.bannerLightMode, + fit: BoxFit.cover, + ), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: !widget.isRootFolder + ? _EmptyFolder( + driveId: widget.driveId, + parentFolderId: widget.parentFolderId, + ) + : isANewUser + ? _NewUserEmptyRootFolder( + driveId: widget.driveId, + parentFolderId: widget.parentFolderId, + ) + : _ExistingUserEmptyRootFolder( + driveId: widget.driveId, + parentFolderId: widget.parentFolderId, + ), + ), + ], ), - Text( - appLocalizationsOf(context).noFiles, - style: ArDriveTypography.headline.headline5Regular(), + ); + }, desktop: (context) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 20), + child: SizedBox( + height: MediaQuery.of(context).size.height * 0.8, + child: ArDriveCard( + width: double.infinity, + backgroundColor: colorTokens.containerL1, + content: !widget.isRootFolder + ? _EmptyFolder( + driveId: widget.driveId, + parentFolderId: widget.parentFolderId, + ) + : isANewUser + ? _NewUserEmptyRootFolder( + driveId: widget.driveId, + parentFolderId: widget.parentFolderId, + ) + : _ExistingUserEmptyRootFolder( + driveId: widget.driveId, + parentFolderId: widget.parentFolderId, + ), + ), ), - const Flexible( - child: SizedBox( - height: 45, + ); + }); + }, + ); + } +} + +class _EmptyFolder extends StatelessWidget { + final String driveId; + final String parentFolderId; + + const _EmptyFolder({ + required this.driveId, + required this.parentFolderId, + }); + + @override + Widget build(BuildContext context) { + final typography = ArDriveTypographyNew.of(context); + final colorTokens = ArDriveTheme.of(context).themeData.colorTokens; + final width = MediaQuery.of(context).size.width; + const String headerText = 'Ready to organize your files?'; + const String descriptionText = + 'This folder is empty. You can move existing files into this folder, or upload new content.'; + + return PlausiblePageViewWrapper( + pageView: PlausiblePageView.folderEmptyPage, + child: ScreenTypeLayout.builder( + mobile: (context) => SingleChildScrollView( + child: Column( + children: [ + _buildHeaderText( + context: context, + text: headerText, + style: typography.display(fontWeight: ArFontWeight.bold), + ), + const SizedBox(height: 10), + _buildDescriptionText( + context: context, + text: descriptionText, + style: typography.heading5( + color: colorTokens.textLow, + fontWeight: ArFontWeight.semiBold, + ), + ), + const SizedBox(height: 20), + _ActionCard.uploadFile(context, + driveId: driveId, + parentFolderId: parentFolderId, + page: PlausiblePageView.folderEmptyPage), + const SizedBox(height: 20), + _ActionCard.uploadFolder(context, + driveId: driveId, + parentFolderId: parentFolderId, + page: PlausiblePageView.folderEmptyPage), + const SizedBox(height: 20), + _ActionCard.createPin(context, + page: PlausiblePageView.folderEmptyPage), + ], + ), + ), + desktop: (context) => Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 66), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + ArDriveImage( + image: AssetImage(Resources.images.login.confetti)), + Flexible( + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + _buildHeaderText( + context: context, + text: headerText, + style: + typography.display(fontWeight: ArFontWeight.bold), + ), + const SizedBox(height: 10), + _buildDescriptionText( + context: context, + text: descriptionText, + style: typography.heading5( + color: colorTokens.textLow, + fontWeight: ArFontWeight.semiBold, + ), + ), + ], + ), + ), + ArDriveImage( + image: AssetImage(Resources.images.login.confetti)), + ], ), ), - if (promptToAddFiles) - InkWell( - onTap: () { - promptToUpload( - context, + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + _ActionCard.uploadFile(context, driveId: driveId, parentFolderId: parentFolderId, - isFolderUpload: false, - ); - }, - child: Container( - decoration: BoxDecoration( - border: Border.all( - color: ArDriveTheme.of(context) - .themeData - .colors - .themeFgDefault, - width: 1, - ), - borderRadius: BorderRadius.circular(10), - ), - padding: - const EdgeInsets.symmetric(horizontal: 40, vertical: 40), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - ArDriveIcons.upload(size: 45), - const SizedBox( - height: 10, - ), - Text( - appLocalizationsOf(context).uploadYourFirstFile, - style: ArDriveTypography.headline.headline5Regular(), - textAlign: TextAlign.center, + page: PlausiblePageView.folderEmptyPage), + _ActionCard.uploadFolder(context, + driveId: driveId, + parentFolderId: parentFolderId, + page: PlausiblePageView.folderEmptyPage), + if (width > SMALL_DESKTOP) + _ActionCard.createPin(context, + page: PlausiblePageView.folderEmptyPage), + ], + ) + ], + ), + ), + ); + } + + Widget _buildHeaderText({ + required BuildContext context, + required String text, + required TextStyle style, + }) { + return Text( + text, + style: style, + ); + } + + Widget _buildDescriptionText({ + required BuildContext context, + required String text, + required TextStyle style, + }) { + return Text( + text, + style: style, + textAlign: TextAlign.center, + ); + } +} + +class _NewUserEmptyRootFolder extends StatelessWidget { + final String driveId; + final String parentFolderId; + + const _NewUserEmptyRootFolder({ + required this.driveId, + required this.parentFolderId, + }); + + @override + Widget build(BuildContext context) { + final typography = ArDriveTypographyNew.of(context); + final colorTokens = ArDriveTheme.of(context).themeData.colorTokens; + + const String headerText = 'You\'re on chain!'; + const String descriptionText = + 'You have just made your first blockchain interaction, congratulations! You can now use your new drive to manage, share, and organize just about any multimedia file. Uploads collectively under 100KB are free!'; + + return PlausiblePageViewWrapper( + pageView: PlausiblePageView.newUserDriveEmptyPage, + child: ScreenTypeLayout.builder( + mobile: (context) => SingleChildScrollView( + child: Column( + children: [ + _buildHeaderText( + context: context, + text: headerText, + style: typography.heading4(fontWeight: ArFontWeight.bold), + ), + const SizedBox(height: 10), + _buildDescriptionText( + context: context, + text: descriptionText, + style: typography.paragraphLarge( + color: colorTokens.textLow, + fontWeight: ArFontWeight.semiBold), + ), + const SizedBox(height: 20), + _ActionCard.uploadFile( + context, + driveId: driveId, + parentFolderId: parentFolderId, + page: PlausiblePageView.newUserDriveEmptyPage, + ), + const SizedBox(height: 20), + _ActionCard.createFolder( + context, + driveId: driveId, + parentFolderId: parentFolderId, + page: PlausiblePageView.newUserDriveEmptyPage, + ), + ], + ), + ), + desktop: (context) => Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 66), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + ArDriveImage( + image: AssetImage(Resources.images.login.confetti)), + Flexible( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: Column( + children: [ + _buildHeaderText( + context: context, + text: headerText, + style: typography.display( + fontWeight: ArFontWeight.bold), + ), + const SizedBox(height: 10), + _buildDescriptionText( + context: context, + text: descriptionText, + style: + typography.heading5(color: colorTokens.textLow), + ), + ], ), - ], + ), ), + ArDriveImage( + image: AssetImage(Resources.images.login.confetti)), + ], + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + _ActionCard.uploadFile(context, + driveId: driveId, + parentFolderId: parentFolderId, + page: PlausiblePageView.newUserDriveEmptyPage), + _ActionCard.createFolder(context, + driveId: driveId, + parentFolderId: parentFolderId, + page: PlausiblePageView.newUserDriveEmptyPage), + ], + ) + ], + ), + ), + ); + } + + Widget _buildHeaderText({ + required BuildContext context, + required String text, + required TextStyle style, + }) { + return Text( + text, + style: style, + ); + } + + Widget _buildDescriptionText({ + required BuildContext context, + required String text, + required TextStyle style, + }) { + return Text( + text, + style: style, + textAlign: TextAlign.center, + ); + } +} + +class _ExistingUserEmptyRootFolder extends StatelessWidget { + final String driveId; + final String parentFolderId; + + const _ExistingUserEmptyRootFolder({ + required this.driveId, + required this.parentFolderId, + }); + + @override + Widget build(BuildContext context) { + final typography = ArDriveTypographyNew.of(context); + final colorTokens = ArDriveTheme.of(context).themeData.colorTokens; + final width = MediaQuery.of(context).size.width; + const String headerText = 'Your brand new drive!'; + // add gesture and link to turbo + const String descriptionText = + 'When you are ready to benefit from blazingly fast, unlimited uploading you can try out Turbo. Until then, if your upload is collectively under 100KB, here are some of the awesome FREE things you can do next.'; + + return PlausiblePageViewWrapper( + pageView: PlausiblePageView.existingUserDriveEmptyPage, + child: ScreenTypeLayout.builder( + mobile: (context) => SingleChildScrollView( + child: Column( + children: [ + _buildHeaderText( + context: context, + text: headerText, + style: typography.heading4(fontWeight: ArFontWeight.bold), + ), + const SizedBox(height: 10), + _buildDescriptionText( + context: context, + text: descriptionText, + style: typography.paragraphLarge( + color: colorTokens.textLow, + fontWeight: ArFontWeight.semiBold, ), ), + const SizedBox(height: 20), + _ActionCard.uploadFile( + context, + driveId: driveId, + parentFolderId: parentFolderId, + page: PlausiblePageView.existingUserDriveEmptyPage, + ), + const SizedBox(height: 20), + _ActionCard.uploadFolder(context, + driveId: driveId, + parentFolderId: parentFolderId, + page: PlausiblePageView.existingUserDriveEmptyPage), + const SizedBox(height: 20), + _ActionCard.createFolder(context, + driveId: driveId, + parentFolderId: parentFolderId, + page: PlausiblePageView.existingUserDriveEmptyPage), + const SizedBox(height: 20), + _ActionCard.createPin(context, + page: PlausiblePageView.existingUserDriveEmptyPage), + const SizedBox(height: 20), + ], + ), + ), + desktop: (context) => Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 66), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + ArDriveImage( + image: AssetImage(Resources.images.login.confetti)), + Flexible( + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + _buildHeaderText( + context: context, + text: headerText, + style: + typography.display(fontWeight: ArFontWeight.bold), + ), + const SizedBox(height: 10), + _buildDescriptionText( + context: context, + text: descriptionText, + style: typography.heading5( + color: colorTokens.textLow, + fontWeight: ArFontWeight.semiBold, + ), + ), + ], + ), + ), + ArDriveImage( + image: AssetImage(Resources.images.login.confetti)), + ], + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + _ActionCard.uploadFile(context, + driveId: driveId, + parentFolderId: parentFolderId, + page: PlausiblePageView.existingUserDriveEmptyPage), + _ActionCard.uploadFolder(context, + driveId: driveId, + parentFolderId: parentFolderId, + page: PlausiblePageView.existingUserDriveEmptyPage), + if (width > SMALL_DESKTOP) + _ActionCard.createFolder(context, + driveId: driveId, + parentFolderId: parentFolderId, + page: PlausiblePageView.existingUserDriveEmptyPage), + if (width > LARGE_DESKTOP) + _ActionCard.createPin(context, + page: PlausiblePageView.existingUserDriveEmptyPage), + ], + ) ], ), ), ); } + + Widget _buildHeaderText({ + required BuildContext context, + required String text, + required TextStyle style, + }) { + return Text( + text, + style: style, + ); + } + + Widget _buildDescriptionText({ + required BuildContext context, + required String text, + required TextStyle style, + }) { + return Text( + text, + style: style, + textAlign: TextAlign.center, + ); + } +} + +class _ActionCard { + static Widget uploadFile( + BuildContext context, { + required String driveId, + required String parentFolderId, + required PlausiblePageView page, + }) { + return _buildActionCard( + context: context, + title: 'Upload Files', + description: + 'Upload a file, or a selection of files into your new drive.', + buttonText: 'Upload', + onPressed: () { + PlausibleEventTracker.trackClickUploadFileEmptyState(page); + + promptToUpload( + context, + driveId: driveId, + parentFolderId: parentFolderId, + isFolderUpload: false, + ); + }, + icon: ArDriveIcons.upload(), + ); + } + + static Widget createFolder( + BuildContext context, { + required String driveId, + required String parentFolderId, + required PlausiblePageView page, + }) { + return _buildActionCard( + context: context, + title: 'Create a Folder', + description: 'Create folders to keep your drive organized.', + buttonText: 'Create Folder', + onPressed: () { + PlausibleEventTracker.trackClickCreateFolderEmptyState(page); + + promptToCreateFolder(context, + driveId: driveId, parentFolderId: parentFolderId); + }, + icon: ArDriveIcons.iconNewFolder1(), + ); + } + + static Widget uploadFolder(BuildContext context, + {required String driveId, + required String parentFolderId, + required PlausiblePageView page}) { + return _buildActionCard( + context: context, + title: 'Upload a Folder', + description: + 'Upload existing folders from your computer, other file storage apps, or an external HD.', + buttonText: 'Upload Folder', + onPressed: () { + PlausibleEventTracker.trackClickUploadFolderEmptyState(page); + + promptToUpload( + context, + driveId: driveId, + parentFolderId: parentFolderId, + isFolderUpload: true, + ); + }, + icon: ArDriveIcons.iconUploadFolder1(), + ); + } + + static Widget createPin(BuildContext context, + {required PlausiblePageView page}) { + return _buildActionCard( + context: context, + title: 'Create a Pin', + description: + 'Pin any permaweb file to your drive, to create inspiration boards, recipe collections, or NFT galleries.', + buttonText: 'Create Pin', + onPressed: () { + PlausibleEventTracker.trackClickCreatePinEmptyState(page); + + showPinFileDialog(context: context); + }, + icon: ArDriveIcons.pinWithCircle(), + ); + } + + static Widget _buildActionCard({ + required BuildContext context, + required String title, + required String description, + required String buttonText, + required Function() onPressed, + required ArDriveIcon icon, + }) { + final typography = ArDriveTypographyNew.of(context); + final colorTokens = ArDriveTheme.of(context).themeData.colorTokens; + + return ArDriveCard( + width: 283, + height: 283, + backgroundColor: colorTokens.containerL2, + contentPadding: const EdgeInsets.all(31), + content: Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + icon.copyWith(size: 25), + Text( + title, + style: + typography.paragraphXLarge(fontWeight: ArFontWeight.semiBold), + ), + const SizedBox(height: 10), + Text( + description, + style: typography.paragraphNormal( + color: colorTokens.textMid, fontWeight: ArFontWeight.semiBold), + textAlign: TextAlign.center, + ), + const SizedBox(height: 20), + ArDriveButtonNew( + text: buttonText, + typography: typography, + onPressed: onPressed, + variant: ButtonVariant.secondary, + ), + ], + ), + ); + } } diff --git a/lib/pages/drive_detail/drive_detail_page.dart b/lib/pages/drive_detail/drive_detail_page.dart index 9af301be25..6c9294d152 100644 --- a/lib/pages/drive_detail/drive_detail_page.dart +++ b/lib/pages/drive_detail/drive_detail_page.dart @@ -3,6 +3,7 @@ import 'dart:math'; import 'package:ardrive/app_shell.dart'; import 'package:ardrive/authentication/ardrive_auth.dart'; +import 'package:ardrive/authentication/components/breakpoint_layout_builder.dart'; import 'package:ardrive/blocs/blocs.dart'; import 'package:ardrive/blocs/fs_entry_preview/fs_entry_preview_cubit.dart'; import 'package:ardrive/blocs/prompt_to_snapshot/prompt_to_snapshot_bloc.dart'; @@ -17,12 +18,14 @@ import 'package:ardrive/components/drive_detach_dialog.dart'; import 'package:ardrive/components/drive_rename_form.dart'; import 'package:ardrive/components/fs_entry_license_form.dart'; import 'package:ardrive/components/new_button/new_button.dart'; +import 'package:ardrive/components/pin_file_dialog.dart'; import 'package:ardrive/components/prompt_to_snapshot_dialog.dart'; import 'package:ardrive/components/side_bar.dart'; import 'package:ardrive/core/activity_tracker.dart'; import 'package:ardrive/download/multiple_file_download_modal.dart'; import 'package:ardrive/entities/entities.dart' as entities; import 'package:ardrive/l11n/l11n.dart'; +import 'package:ardrive/misc/resources.dart'; import 'package:ardrive/models/license.dart'; import 'package:ardrive/models/models.dart'; import 'package:ardrive/pages/congestion_warning_wrapper.dart'; @@ -35,6 +38,7 @@ import 'package:ardrive/pages/drive_detail/components/unpreviewable_content.dart import 'package:ardrive/search/search_modal.dart'; import 'package:ardrive/search/search_text_field.dart'; import 'package:ardrive/services/services.dart'; +import 'package:ardrive/shared/components/plausible_page_view_wrapper.dart'; import 'package:ardrive/sharing/sharing_file_listener.dart'; import 'package:ardrive/sync/domain/cubit/sync_cubit.dart'; import 'package:ardrive/theme/theme.dart'; @@ -55,6 +59,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:flutter_svg/svg.dart'; import 'package:just_audio/just_audio.dart'; import 'package:responsive_builder/responsive_builder.dart'; import 'package:synchronized/synchronized.dart'; @@ -561,6 +566,11 @@ class _DriveDetailPageState extends State { .folderInView.folder.id, promptToAddFiles: driveDetailState .hasWritePermissions, + isRootFolder: driveDetailState + .folderInView + .folder + .parentFolderId == + null, ), ), ], @@ -578,6 +588,10 @@ class _DriveDetailPageState extends State { driveDetailState.folderInView.folder.id, promptToAddFiles: driveDetailState.hasWritePermissions, + isRootFolder: driveDetailState.folderInView + .folder.parentFolderId == + null && + !hasSubfolders, ), ), ], @@ -801,32 +815,32 @@ class _DriveDetailPageState extends State { ), ), Expanded( - child: Padding( - padding: const EdgeInsets.symmetric( - vertical: 8, - horizontal: 16, - ), - child: (hasSubfolders || hasFiles) - ? ListView.separated( - controller: _scrollController, - separatorBuilder: (context, index) => const SizedBox( - height: 5, - ), - itemCount: filteredItems.length, - itemBuilder: (context, index) { - return ArDriveItemListTile( - key: ObjectKey([filteredItems[index]]), - drive: state.currentDrive, - item: filteredItems[index], - ); - }, - ) - : DriveDetailFolderEmptyCard( - promptToAddFiles: state.hasWritePermissions, - driveId: state.currentDrive.id, - parentFolderId: state.folderInView.folder.id, + child: (hasSubfolders || hasFiles) + ? ListView.separated( + padding: const EdgeInsets.symmetric( + vertical: 8, + horizontal: 16, ), - ), + controller: _scrollController, + separatorBuilder: (context, index) => const SizedBox( + height: 5, + ), + itemCount: filteredItems.length, + itemBuilder: (context, index) { + return ArDriveItemListTile( + key: ObjectKey([filteredItems[index]]), + drive: state.currentDrive, + item: filteredItems[index], + ); + }, + ) + : DriveDetailFolderEmptyCard( + promptToAddFiles: state.hasWritePermissions, + driveId: state.currentDrive.id, + parentFolderId: state.folderInView.folder.id, + isRootFolder: + state.folderInView.folder.parentFolderId == null, + ), ), ], ); diff --git a/lib/shared/components/plausible_page_view_wrapper.dart b/lib/shared/components/plausible_page_view_wrapper.dart new file mode 100644 index 0000000000..a64056d548 --- /dev/null +++ b/lib/shared/components/plausible_page_view_wrapper.dart @@ -0,0 +1,37 @@ +import 'package:ardrive/utils/plausible_event_tracker/plausible_event_tracker.dart'; +import 'package:flutter/material.dart'; + +/// A wrapper widget that tracks a page view event when the widget is built. +/// +/// This widget should be used to wrap the root widget of a page to track a page view event. +class PlausiblePageViewWrapper extends StatefulWidget { + const PlausiblePageViewWrapper({ + super.key, + required this.pageView, + required this.child, + this.props, + }); + + final PlausiblePageView pageView; + final Widget child; + final Map? props; + + @override + State createState() => + _PlausiblePageViewWrapperState(); +} + +class _PlausiblePageViewWrapperState extends State { + @override + void initState() { + PlausibleEventTracker.trackPageview( + page: widget.pageView, props: widget.props); + + super.initState(); + } + + @override + Widget build(BuildContext context) { + return widget.child; + } +} diff --git a/lib/utils/logger.dart b/lib/utils/logger.dart index c7ee475868..a232514bd4 100644 --- a/lib/utils/logger.dart +++ b/lib/utils/logger.dart @@ -13,9 +13,10 @@ final Set _knownTransactionDecryptionBugVersions = { '2.30.0', '2.30.1', '2.30.2', + '2.32.0', '2.36.0', '2.37.0', - '2.37.1' + '2.37.1', }; ShouldLogErrorCallback shouldLogErrorCallback = (error) { diff --git a/lib/utils/plausible_event_tracker/plausible_custom_events.dart b/lib/utils/plausible_event_tracker/plausible_custom_events.dart index 94ce09ecac..e51f9d854f 100644 --- a/lib/utils/plausible_event_tracker/plausible_custom_events.dart +++ b/lib/utils/plausible_event_tracker/plausible_custom_events.dart @@ -65,6 +65,12 @@ enum PlausibleCustomEvent { /// Create Drive on Empty State clickCreatePrivateDriveButtonEmptyState, clickCreatePublicDriveButtonEmptyState, + + /// Drive Empty State + clickUploadFileEmptyState, + clickCreateFolderEmptyState, + clickUploadFolderEmptyState, + clickCreatePinEmptyState, } extension PlausibleCustomEventNames on PlausibleCustomEvent { @@ -184,6 +190,16 @@ extension PlausibleCustomEventNames on PlausibleCustomEvent { return 'clickCreatePrivateDriveButtonEmptyState'; case PlausibleCustomEvent.clickCreatePublicDriveButtonEmptyState: return 'clickCreatePublicDriveButtonEmptyState'; + + /// Drive Empty State + case PlausibleCustomEvent.clickUploadFileEmptyState: + return 'clickUploadFileEmptyState'; + case PlausibleCustomEvent.clickCreateFolderEmptyState: + return 'clickCreateFolderEmptyState'; + case PlausibleCustomEvent.clickUploadFolderEmptyState: + return 'clickUploadFolderEmptyState'; + case PlausibleCustomEvent.clickCreatePinEmptyState: + return 'clickCreatePinEmptyState'; } } } diff --git a/lib/utils/plausible_event_tracker/plausible_event_tracker.dart b/lib/utils/plausible_event_tracker/plausible_event_tracker.dart index 9c45fc4508..66e8ff7c80 100644 --- a/lib/utils/plausible_event_tracker/plausible_event_tracker.dart +++ b/lib/utils/plausible_event_tracker/plausible_event_tracker.dart @@ -481,6 +481,46 @@ abstract class PlausibleEventTracker { ); } + // clickUploadFileEmptyState, + static Future trackClickUploadFileEmptyState( + PlausiblePageView page, + ) { + return _trackCustomEvent( + page: page, + event: PlausibleCustomEvent.clickUploadFileEmptyState, + ); + } + + // clickCreateFolderEmptyState + static Future trackClickCreateFolderEmptyState( + PlausiblePageView page, + ) { + return _trackCustomEvent( + page: page, + event: PlausibleCustomEvent.clickCreateFolderEmptyState, + ); + } + + // clickUploadFolderEmptyState + static Future trackClickUploadFolderEmptyState( + PlausiblePageView page, + ) { + return _trackCustomEvent( + page: page, + event: PlausibleCustomEvent.clickUploadFolderEmptyState, + ); + } + + // clickCreatePinEmptyState + static Future trackClickCreatePinEmptyState( + PlausiblePageView page, + ) { + return _trackCustomEvent( + page: page, + event: PlausibleCustomEvent.clickCreatePinEmptyState, + ); + } + static Future _track({ required PlausibleApiData data, required PlausibleEventData eventData, diff --git a/lib/utils/plausible_event_tracker/plausible_page_view_events.dart b/lib/utils/plausible_event_tracker/plausible_page_view_events.dart index 9ecec909dd..8efb2444ae 100644 --- a/lib/utils/plausible_event_tracker/plausible_page_view_events.dart +++ b/lib/utils/plausible_event_tracker/plausible_page_view_events.dart @@ -39,4 +39,9 @@ enum PlausiblePageView { /// Search searchPage, + + /// Empty State + folderEmptyPage, + existingUserDriveEmptyPage, + newUserDriveEmptyPage, } diff --git a/packages/ardrive_crypto/lib/src/exceptions.dart b/packages/ardrive_crypto/lib/src/exceptions.dart index dbe5039878..a8048dfc9d 100644 --- a/packages/ardrive_crypto/lib/src/exceptions.dart +++ b/packages/ardrive_crypto/lib/src/exceptions.dart @@ -1,37 +1,50 @@ class ArDriveDecryptionException implements Exception { final String? corruptedDataAppVersion; + final String? corruptedTransactionId; - ArDriveDecryptionException({this.corruptedDataAppVersion}); + ArDriveDecryptionException({ + this.corruptedDataAppVersion, + this.corruptedTransactionId, + }); @override String toString() { - return 'ARFSDecryptionException: corruptedDataAppVersion: $corruptedDataAppVersion'; + return 'ArDriveDecryptionException: corruptedDataAppVersion: $corruptedDataAppVersion and corruptedTransactionId: $corruptedTransactionId'; } } class TransactionDecryptionException extends ArDriveDecryptionException { - TransactionDecryptionException({super.corruptedDataAppVersion}); + TransactionDecryptionException({ + super.corruptedDataAppVersion, + super.corruptedTransactionId, + }); @override String toString() { - return 'TransactionDecryptionException: corruptedDataAppVersion: $corruptedDataAppVersion'; + return 'TransactionDecryptionException: corruptedDataAppVersion: $corruptedDataAppVersion and corruptedTransactionId: $corruptedTransactionId'; } } class MissingCipherTagException extends ArDriveDecryptionException { - MissingCipherTagException({super.corruptedDataAppVersion}); + MissingCipherTagException({ + super.corruptedDataAppVersion, + super.corruptedTransactionId, + }); @override String toString() { - return 'MissingCipherTagException: corruptedDataAppVersion: $corruptedDataAppVersion'; + return 'MissingCipherTagException: corruptedDataAppVersion: $corruptedDataAppVersion and corruptedTransactionId: $corruptedTransactionId'; } } class UnknownCipherException extends ArDriveDecryptionException { - UnknownCipherException({super.corruptedDataAppVersion}); + UnknownCipherException({ + super.corruptedDataAppVersion, + super.corruptedTransactionId, + }); @override String toString() { - return 'UnknowCipherException: corruptedDataAppVersion: $corruptedDataAppVersion'; + return 'UnknowCipherException: corruptedDataAppVersion: $corruptedDataAppVersion and corruptedTransactionId: $corruptedTransactionId'; } } diff --git a/packages/ardrive_ui/lib/src/components/modal.dart b/packages/ardrive_ui/lib/src/components/modal.dart index b32fd93740..9fec76a30f 100644 --- a/packages/ardrive_ui/lib/src/components/modal.dart +++ b/packages/ardrive_ui/lib/src/components/modal.dart @@ -651,15 +651,22 @@ Future showStandardDialog( required String description, List? actions, bool barrierDismissible = true, + bool useNewArDriveUI = false, }) { return showAnimatedDialog( context, barrierDismissible: barrierDismissible, - content: ArDriveStandardModal( - description: description, - title: title, - actions: actions, - ), + content: useNewArDriveUI + ? ArDriveStandardModalNew( + description: description, + title: title, + actions: actions, + ) + : ArDriveStandardModal( + description: description, + title: title, + actions: actions, + ), ); } diff --git a/pubspec.yaml b/pubspec.yaml index 94efce16a6..5105700f0f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -3,7 +3,7 @@ description: Secure, permanent storage publish_to: 'none' -version: 2.46.0 +version: 2.47.0 environment: sdk: '>=3.2.0 <4.0.0' diff --git a/test/utils/logger_test.dart b/test/utils/logger_test.dart index 4b813f956a..a1a4fdebb8 100644 --- a/test/utils/logger_test.dart +++ b/test/utils/logger_test.dart @@ -18,6 +18,7 @@ void main() { '2.30.0', '2.30.1', '2.30.2', + '2.32.0', '2.36.0', '2.37.0', '2.37.1'