diff --git a/lib/features/base/widget/circle_loading_widget.dart b/lib/features/base/widget/circle_loading_widget.dart index 9261b70bd..7d9c72428 100644 --- a/lib/features/base/widget/circle_loading_widget.dart +++ b/lib/features/base/widget/circle_loading_widget.dart @@ -11,32 +11,22 @@ class CircleLoadingWidget extends StatelessWidget { super.key, this.size, this.padding, - this.strokeWidth + this.strokeWidth, }); @override Widget build(BuildContext context) { - if (padding != null) { - return Padding( - padding: padding!, - child: SizedBox( - width: size ?? CircleLoadingWidgetStyles.size, - height: size ?? CircleLoadingWidgetStyles.size, - child: CircularProgressIndicator( - color: CircleLoadingWidgetStyles.progressColor, - strokeWidth: strokeWidth ?? CircleLoadingWidgetStyles.width, - ) - ), - ); - } else { - return SizedBox( - width: size ?? CircleLoadingWidgetStyles.size, - height: size ?? CircleLoadingWidgetStyles.size, - child: CircularProgressIndicator( - color: CircleLoadingWidgetStyles.progressColor, - strokeWidth: strokeWidth ?? CircleLoadingWidgetStyles.width, - ) - ); - } + final loadingSize = size ?? CircleLoadingWidgetStyles.size; + final loadingStrokeWidth = strokeWidth ?? CircleLoadingWidgetStyles.width; + + return Container( + padding: padding, + width: loadingSize, + height: loadingSize, + child: CircularProgressIndicator( + color: CircleLoadingWidgetStyles.progressColor, + strokeWidth: loadingStrokeWidth, + ) + ); } } \ No newline at end of file diff --git a/lib/features/email/domain/state/download_attachment_for_web_state.dart b/lib/features/email/domain/state/download_attachment_for_web_state.dart index 77fcc7c9b..a5209dd38 100644 --- a/lib/features/email/domain/state/download_attachment_for_web_state.dart +++ b/lib/features/email/domain/state/download_attachment_for_web_state.dart @@ -1,6 +1,7 @@ import 'dart:typed_data'; import 'package:core/presentation/state/failure.dart'; import 'package:core/presentation/state/success.dart'; +import 'package:dio/dio.dart'; import 'package:model/download/download_task_id.dart'; import 'package:model/email/attachment.dart'; @@ -10,11 +11,12 @@ class StartDownloadAttachmentForWeb extends UIState { final DownloadTaskId taskId; final Attachment attachment; + final CancelToken? cancelToken; - StartDownloadAttachmentForWeb(this.taskId, this.attachment); + StartDownloadAttachmentForWeb(this.taskId, this.attachment, [this.cancelToken]); @override - List get props => [taskId, attachment]; + List get props => [taskId, attachment, cancelToken]; } class DownloadingAttachmentForWeb extends UIState { @@ -59,13 +61,15 @@ class DownloadAttachmentForWebFailure extends FeatureFailure { final DownloadTaskId? taskId; final Attachment? attachment; + final CancelToken? cancelToken; DownloadAttachmentForWebFailure({ this.attachment, this.taskId, - dynamic exception - }) : super(exception: exception); + this.cancelToken, + super.exception + }); @override - List get props => [attachment, taskId, ...super.props]; + List get props => [attachment, taskId, cancelToken, ...super.props]; } \ No newline at end of file diff --git a/lib/features/email/domain/usecases/download_attachment_for_web_interactor.dart b/lib/features/email/domain/usecases/download_attachment_for_web_interactor.dart index 106d95dc0..4a4d304b2 100644 --- a/lib/features/email/domain/usecases/download_attachment_for_web_interactor.dart +++ b/lib/features/email/domain/usecases/download_attachment_for_web_interactor.dart @@ -38,8 +38,8 @@ class DownloadAttachmentForWebInteractor { {CancelToken? cancelToken} ) async* { try { - yield Right(StartDownloadAttachmentForWeb(taskId, attachment)); - onReceiveController.add(Right(StartDownloadAttachmentForWeb(taskId, attachment))); + yield Right(StartDownloadAttachmentForWeb(taskId, attachment, cancelToken)); + onReceiveController.add(Right(StartDownloadAttachmentForWeb(taskId, attachment, cancelToken))); final currentAccount = await _accountRepository.getCurrentAccount(); AccountRequest? accountRequest; @@ -77,7 +77,8 @@ class DownloadAttachmentForWebInteractor { DownloadAttachmentForWebFailure( attachment: attachment, taskId: taskId, - exception: exception + exception: exception, + cancelToken: cancelToken, ) ); } diff --git a/lib/features/email/presentation/controller/single_email_controller.dart b/lib/features/email/presentation/controller/single_email_controller.dart index e391b7bf1..5692a267f 100644 --- a/lib/features/email/presentation/controller/single_email_controller.dart +++ b/lib/features/email/presentation/controller/single_email_controller.dart @@ -426,9 +426,12 @@ class SingleEmailController extends BaseController with AppLoaderMixin { (success) { if (success is StartDownloadAttachmentForWeb) { emailSupervisorController.mailboxDashBoardController.addDownloadTask( - DownloadTaskState( - taskId: success.taskId, - attachment: success.attachment)); + DownloadTaskState( + taskId: success.taskId, + attachment: success.attachment, + onCancel: () => success.cancelToken?.cancel(), + ), + ); if (currentOverlayContext != null && currentContext != null) { appToast.showToastMessage( @@ -896,12 +899,15 @@ class SingleEmailController extends BaseController with AppLoaderMixin { final baseDownloadUrl = session!.getDownloadUrl( jmapUrl: dynamicUrlInterceptors.jmapUrl, ); + final cancelToken = CancelToken(); consumeState(_downloadAttachmentForWebInteractor.execute( - generateTaskId, - attachment, - accountId!, - baseDownloadUrl, - _downloadProgressStateController)); + generateTaskId, + attachment, + accountId!, + baseDownloadUrl, + _downloadProgressStateController, + cancelToken: cancelToken, + )); } catch (e) { logError('SingleEmailController::downloadAttachmentForWeb(): $e'); consumeState(Stream.value(Left(DownloadAttachmentForWebFailure(attachment: attachment, taskId: generateTaskId, exception: e)))); @@ -944,14 +950,17 @@ class SingleEmailController extends BaseController with AppLoaderMixin { if (failure.attachment != null) { _updateAttachmentsViewState(failure.attachment?.blobId, Left(failure)); } + + if (currentOverlayContext == null || currentContext == null) return; - if (currentOverlayContext != null && currentContext != null) { - appToast.showToastErrorMessage( - currentOverlayContext!, - failure.attachment is EMLAttachment - ? AppLocalizations.of(currentContext!).downloadMessageAsEMLFailed - : AppLocalizations.of(currentContext!).attachment_download_failed); + String message = AppLocalizations.of(currentContext!).attachment_download_failed; + if (failure.attachment is EMLAttachment) { + message = AppLocalizations.of(currentContext!).downloadMessageAsEMLFailed; + } else if (failure.cancelToken?.isCancelled == true) { + message = AppLocalizations.of(currentContext!).downloadAttachmentHasBeenCancelled; } + + appToast.showToastErrorMessage(currentOverlayContext!, message); } void _updateAttachmentsViewState( diff --git a/lib/features/mailbox_dashboard/presentation/controller/download/download_controller.dart b/lib/features/mailbox_dashboard/presentation/controller/download/download_controller.dart index 3455bfc42..76de53d0f 100644 --- a/lib/features/mailbox_dashboard/presentation/controller/download/download_controller.dart +++ b/lib/features/mailbox_dashboard/presentation/controller/download/download_controller.dart @@ -9,12 +9,14 @@ typedef UpdateDownloadTaskStateCallback = DownloadTaskState Function(DownloadTas class DownloadController extends GetxController { final listDownloadTaskState = RxList(); + final hideDownloadTaskbar = RxBool(false); bool get notEmptyListDownloadTask => listDownloadTaskState.isNotEmpty; void addDownloadTask(DownloadTaskState task) { log('DownloadController::addDownloadTask(): ${task.taskId}'); listDownloadTaskState.add(task); + hideDownloadTaskbar.value = false; } void updateDownloadTaskByTaskId( @@ -37,5 +39,8 @@ class DownloadController extends GetxController { listDownloadTaskState.removeAt(matchIndex); listDownloadTaskState.refresh(); } + if (listDownloadTaskState.isEmpty) { + hideDownloadTaskbar.value = true; + } } } \ No newline at end of file diff --git a/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart b/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart index 74a1f5e8d..f731e0d75 100644 --- a/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart +++ b/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart @@ -202,7 +202,7 @@ class MailboxDashBoardView extends BaseMailboxDashBoardView { ? const SearchMailboxView() : const SizedBox.shrink() ), - _buildDownloadTaskStateWidget(), + _buildDownloadTaskStateWidget(AppLocalizations.of(context)), ]), ); } @@ -413,30 +413,50 @@ class MailboxDashBoardView extends BaseMailboxDashBoardView { controller.dispatchAction(FilterMessageAction(FilterMessageOption.all)); } - Widget _buildDownloadTaskStateWidget() { + Widget _buildDownloadTaskStateWidget(AppLocalizations appLocalizations) { return Obx(() { final listDownloadTasks = controller.downloadController.listDownloadTaskState; - if (listDownloadTasks.isNotEmpty) { + final hideDownloadTaskbar = controller.downloadController.hideDownloadTaskbar; + if (listDownloadTasks.isNotEmpty && !hideDownloadTaskbar.value) { return Align( alignment: Alignment.bottomCenter, child: Container( color: AppColor.colorBackgroundSnackBar, height: 60, width: double.infinity, - child: ListView.separated( - scrollDirection: Axis.horizontal, - padding: EdgeInsets.zero, - itemCount: listDownloadTasks.length, - separatorBuilder: (context, index) => - const Padding( - padding: EdgeInsets.symmetric(vertical: 5), - child: VerticalDivider( - color: Colors.grey, - width: 2.5, - thickness: 0.2), + child: Row( + children: [ + Expanded( + child: ListView.separated( + scrollDirection: Axis.horizontal, + padding: EdgeInsets.zero, + itemCount: listDownloadTasks.length, + separatorBuilder: (context, index) => + const Padding( + padding: EdgeInsets.symmetric(vertical: 5), + child: VerticalDivider( + color: Colors.grey, + width: 2.5, + thickness: 0.2), + ), + itemBuilder: (context, index) => + DownloadTaskItemWidget(listDownloadTasks[index]) ), - itemBuilder: (context, index) => - DownloadTaskItemWidget(listDownloadTasks[index]) + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: TMailButtonWidget.fromText( + text: appLocalizations.hide, + backgroundColor: Colors.transparent, + textStyle: const TextStyle( + color: AppColor.colorCancelButton, + ), + onTapActionCallback: () { + controller.downloadController.hideDownloadTaskbar.value = true; + }, + ), + ), + ], ), ), ); diff --git a/lib/features/mailbox_dashboard/presentation/model/download/download_task_state.dart b/lib/features/mailbox_dashboard/presentation/model/download/download_task_state.dart index 4ab2331c5..8e1641de1 100644 --- a/lib/features/mailbox_dashboard/presentation/model/download/download_task_state.dart +++ b/lib/features/mailbox_dashboard/presentation/model/download/download_task_state.dart @@ -1,5 +1,6 @@ import 'package:equatable/equatable.dart'; +import 'package:flutter/widgets.dart'; import 'package:model/download/download_task_id.dart'; import 'package:model/email/attachment.dart'; @@ -9,13 +10,15 @@ class DownloadTaskState with EquatableMixin { final double progress; final int downloaded; final int total; + final VoidCallback? onCancel; DownloadTaskState({ required this.taskId, required this.attachment, this.progress = 0, this.downloaded = 0, - this.total = 0 + this.total = 0, + this.onCancel, }); DownloadTaskState copyWith({ @@ -23,14 +26,16 @@ class DownloadTaskState with EquatableMixin { Attachment? attachment, double? progress, int? downloaded, - int? total + int? total, + VoidCallback? onCancel }) { return DownloadTaskState( taskId: taskId ?? this.taskId, attachment: attachment ?? this.attachment, progress: progress ?? this.progress, downloaded: downloaded ?? this.downloaded, - total: total ?? this.total + total: total ?? this.total, + onCancel: onCancel ?? this.onCancel, ); } @@ -46,5 +51,5 @@ class DownloadTaskState with EquatableMixin { } @override - List get props => [taskId, attachment, progress, downloaded, total]; + List get props => [taskId, attachment, progress, downloaded, total, onCancel]; } \ No newline at end of file diff --git a/lib/features/mailbox_dashboard/presentation/widgets/download/download_task_item_widget.dart b/lib/features/mailbox_dashboard/presentation/widgets/download/download_task_item_widget.dart index 520b8def5..4bbc4571f 100644 --- a/lib/features/mailbox_dashboard/presentation/widgets/download/download_task_item_widget.dart +++ b/lib/features/mailbox_dashboard/presentation/widgets/download/download_task_item_widget.dart @@ -3,10 +3,12 @@ import 'package:byte_converter/byte_converter.dart'; import 'package:core/presentation/extensions/color_extension.dart'; import 'package:core/presentation/resources/image_paths.dart'; import 'package:core/presentation/utils/style_utils.dart'; +import 'package:core/presentation/views/button/tmail_button_widget.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:get/get.dart'; import 'package:percent_indicator/circular_percent_indicator.dart'; +import 'package:tmail_ui_user/features/base/widget/circle_loading_widget.dart'; import 'package:tmail_ui_user/features/email/presentation/extensions/attachment_extension.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/download/download_task_state.dart'; @@ -43,7 +45,7 @@ class DownloadTaskItemWidget extends StatelessWidget { fit: BoxFit.fill), Center( child: taskState.percentDownloading == 0 - ? const CircularProgressIndicator(color: AppColor.primaryColor, strokeWidth: 3) + ? const CircleLoadingWidget(strokeWidth: 3) : CircularPercentIndicator( percent: taskState.percentDownloading, backgroundColor: AppColor.colorBgMailboxSelected, @@ -83,7 +85,13 @@ class DownloadTaskItemWidget extends StatelessWidget { ) ], ), - ) + ), + if (taskState.onCancel != null) + TMailButtonWidget.fromIcon( + icon: imagePaths.icClose, + onTapActionCallback: taskState.onCancel, + backgroundColor: Colors.transparent, + ), ], ), ); diff --git a/lib/l10n/intl_messages.arb b/lib/l10n/intl_messages.arb index c33d6b416..cd88bbc4f 100644 --- a/lib/l10n/intl_messages.arb +++ b/lib/l10n/intl_messages.arb @@ -4211,5 +4211,11 @@ "type": "text", "placeholders_order": [], "placeholders": {} + }, + "downloadAttachmentHasBeenCancelled": "Download attachment has been cancelled", + "@downloadAttachmentHasBeenCancelled": { + "type": "text", + "placeholders_order": [], + "placeholders": {} } } \ No newline at end of file diff --git a/lib/main/localizations/app_localizations.dart b/lib/main/localizations/app_localizations.dart index e0b640068..bbd081bc4 100644 --- a/lib/main/localizations/app_localizations.dart +++ b/lib/main/localizations/app_localizations.dart @@ -4419,4 +4419,11 @@ class AppLocalizations { name: 'thisHtmlAttachmentCannotBePreviewed', ); } + + String get downloadAttachmentHasBeenCancelled { + return Intl.message( + 'Download attachment has been cancelled', + name: 'downloadAttachmentHasBeenCancelled', + ); + } }