TF-3719 Add handle export log to device if need

Signed-off-by: dab246 <tdvu@linagora.com>
This commit is contained in:
dab246
2025-05-16 20:16:11 +07:00
committed by Dat H. Pham
parent a891e07887
commit 0ca3ca170d
20 changed files with 541 additions and 6 deletions
+1
View File
@@ -99,6 +99,7 @@ export 'presentation/views/avatar/gradient_circle_avatar_icon.dart';
export 'presentation/views/loading/cupertino_loading_widget.dart';
export 'presentation/views/image/image_loader_mixin.dart';
export 'presentation/views/pull_to_refresh/pull_to_refresh_widget.dart';
export 'presentation/views/button/multi_click_widget.dart';
// Resources
export 'presentation/resources/assets_paths.dart';
@@ -0,0 +1,21 @@
import 'package:equatable/equatable.dart';
abstract class FileException with EquatableMixin implements Exception {
final String message;
FileException(this.message);
@override
String toString() => message;
@override
List<Object> get props => [message];
}
class NotFoundFileInFolderException extends FileException {
NotFoundFileInFolderException() : super('No files found in the folder');
}
class UserCancelShareFileException extends FileException {
UserCancelShareFileException() : super('User cancel share file');
}
@@ -0,0 +1,51 @@
import 'package:flutter/material.dart';
import '../../../utils/app_logger.dart';
class MultiClickWidget extends StatefulWidget {
final Widget child;
final int requiredClicks;
final Duration interval;
final VoidCallback onMultiTap;
const MultiClickWidget({
Key? key,
required this.child,
required this.onMultiTap,
this.requiredClicks = 7,
this.interval = const Duration(seconds: 2),
}) : super(key: key);
@override
State<MultiClickWidget> createState() => _MultiClickWidgetState();
}
class _MultiClickWidgetState extends State<MultiClickWidget> {
int _clickCount = 0;
DateTime? _lastClickTime;
void _handleTap() {
final now = DateTime.now();
if (_lastClickTime == null ||
now.difference(_lastClickTime!) > widget.interval) {
_clickCount = 1;
} else {
_clickCount++;
}
_lastClickTime = now;
log('_MultiClickWidgetState::_handleTap: ClickCount: $_clickCount');
if (_clickCount >= widget.requiredClicks) {
_clickCount = 0;
widget.onMultiTap();
}
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: _handleTap,
child: widget.child,
);
}
}
+78 -1
View File
@@ -3,7 +3,10 @@ import 'dart:collection';
import 'dart:io';
import 'package:core/domain/exceptions/download_file_exception.dart';
import 'package:core/domain/exceptions/file_exception.dart';
import 'package:core/utils/logger/trace_log.dart';
import 'package:core/utils/platform_info.dart';
import 'package:flutter/foundation.dart';
import 'package:intl/intl.dart';
import 'package:path_provider/path_provider.dart';
@@ -147,4 +150,78 @@ class LogTracking {
return await file.writeAsString(content, mode: fileMode);
}
}
Future<TraceLog> getTraceLog() async {
final folderPath = await _getInternalStorageDirPath(folderPath: logFolder);
final directory = Directory(folderPath);
if (directory.existsSync()) {
final directoryInfo = await getDirInfo(directory);
return TraceLog(
path: folderPath,
size: directoryInfo.$1,
listFilePaths: directoryInfo.$2,
);
} else {
throw Exception('Trace folder not exist');
}
}
Future<(int, List<String>)> getDirInfo(Directory dir) async {
var files = await dir.list(recursive: true).toList();
var dirSize = files.fold(0, (int sum, file) => sum + file.statSync().size);
var listPath = files.map((file) => file.path).toList();
return (dirSize, listPath);
}
static Future<String> getExternalDocumentPath({String? folderPath}) async {
Directory directory = Directory('');
if (Platform.isAndroid) {
if (folderPath?.isNotEmpty == true) {
directory = Directory('/storage/emulated/0/Download/$folderPath');
} else {
directory = Directory('/storage/emulated/0/Download');
}
} else {
directory = await getApplicationDocumentsDirectory();
if (folderPath?.isNotEmpty == true) {
directory = Directory('${directory.absolute.path}/$folderPath');
}
}
final exPath = directory.path;
await Directory(exPath).create(recursive: true);
return exPath;
}
static Future<String> copyInternalFilesToDownloadExternal(List<String> listFilePaths) async {
final externalPath = await getExternalDocumentPath();
List<String> externalListPaths = [];
for (var filePath in listFilePaths) {
final file = File(filePath);
final fileName = filePath.substring(filePath.lastIndexOf('/') + 1);
final externalFile = File('$externalPath/$fileName');
await externalFile.writeAsBytes(file.readAsBytesSync());
externalListPaths.add(externalFile.path);
}
if (externalListPaths.isNotEmpty) {
return externalPath;
} else {
throw NotFoundFileInFolderException();
}
}
Future<String> exportTraceLog(TraceLog traceLog) async {
if (PlatformInfo.isIOS) {
final savePath = getExternalDocumentPath(folderPath: logFolder);
return savePath;
} else {
final savePath = await compute(
copyInternalFilesToDownloadExternal,
traceLog.listFilePaths,
);
return savePath;
}
}
}
+16
View File
@@ -0,0 +1,16 @@
import 'package:equatable/equatable.dart';
class TraceLog with EquatableMixin {
final String path;
final int size;
final List<String> listFilePaths;
TraceLog({
required this.path,
required this.size,
required this.listFilePaths
});
@override
List<Object?> get props => [path, size, listFilePaths];
}
@@ -0,0 +1,4 @@
abstract class TraceLogDataSource {
Future<String> exportTraceLog();
}
@@ -0,0 +1,59 @@
import 'package:core/data/utils/device_manager.dart';
import 'package:core/domain/exceptions/file_exception.dart';
import 'package:core/utils/logger/log_tracking.dart';
import 'package:core/utils/platform_info.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:share_plus/share_plus.dart';
import 'package:tmail_ui_user/features/manage_account/data/datasource/trace_log_datasource.dart';
import 'package:tmail_ui_user/main/exceptions/exception_thrower.dart';
import 'package:tmail_ui_user/main/exceptions/permission_exception.dart';
import 'package:tmail_ui_user/main/permissions/permission_service.dart';
class TraceLogDataSourceImpl extends TraceLogDataSource {
final LogTracking _logTracking;
final DeviceManager _deviceManager;
final PermissionService _permissionService;
final ExceptionThrower _exceptionThrower;
TraceLogDataSourceImpl(
this._logTracking,
this._deviceManager,
this._permissionService,
this._exceptionThrower,
);
@override
Future<String> exportTraceLog() {
return Future.sync(() async {
if (PlatformInfo.isAndroid) {
final permissionGranted = await _validateStoragePermissionOnAndroid();
if (permissionGranted) {
final traceLog = await _logTracking.getTraceLog();
return await _logTracking.exportTraceLog(traceLog);
} else {
throw const NotGrantedPermissionStorageException();
}
} else {
final traceLog = await _logTracking.getTraceLog();
final savePath = await _logTracking.exportTraceLog(traceLog);
final result = await Share.shareXFiles([XFile(savePath)]);
if (result.status == ShareResultStatus.success) {
return savePath;
}
throw UserCancelShareFileException();
}
}).catchError(_exceptionThrower.throwException);
}
Future<bool> _validateStoragePermissionOnAndroid() async {
final needRequestPermission = await _deviceManager.isNeedRequestStoragePermissionOnAndroid();
if (needRequestPermission) {
final isGranted = await _permissionService.isGranted(Permission.storage);
return isGranted;
} else {
return true;
}
}
}
@@ -0,0 +1,4 @@
abstract class TraceLogRepository {
Future<String> exportTraceLog();
}
@@ -0,0 +1,13 @@
import 'package:tmail_ui_user/features/manage_account/data/datasource/trace_log_datasource.dart';
import 'package:tmail_ui_user/features/manage_account/data/repository/trace_log_repository.dart';
class TraceLogRepositoryImpl extends TraceLogRepository {
final TraceLogDataSource _traceLogDataSource;
TraceLogRepositoryImpl(this._traceLogDataSource);
@override
Future<String> exportTraceLog() {
return _traceLogDataSource.exportTraceLog();
}
}
@@ -0,0 +1,19 @@
import 'package:core/presentation/state/failure.dart';
import 'package:core/presentation/state/success.dart';
class ExportTraceLogLoading extends LoadingState {}
class ExportTraceLogSuccess extends UIState {
String savePath;
ExportTraceLogSuccess(this.savePath);
@override
List<Object?> get props => [savePath];
}
class ExportTraceLogFailure extends FeatureFailure {
ExportTraceLogFailure(dynamic exception) : super(exception: exception);
}
@@ -0,0 +1,21 @@
import 'package:core/presentation/state/failure.dart';
import 'package:core/presentation/state/success.dart';
import 'package:dartz/dartz.dart';
import 'package:tmail_ui_user/features/manage_account/data/repository/trace_log_repository.dart';
import 'package:tmail_ui_user/features/manage_account/domain/state/export_trace_log_state.dart';
class ExportTraceLogInteractor {
final TraceLogRepository _traceLogRepository;
ExportTraceLogInteractor(this._traceLogRepository);
Stream<Either<Failure, Success>> execute() async* {
try {
yield Right<Failure, Success>(ExportTraceLogLoading());
final savePath = await _traceLogRepository.exportTraceLog();
yield Right<Failure, Success>(ExportTraceLogSuccess(savePath));
} catch (exception) {
yield Left<Failure, Success>(ExportTraceLogFailure(exception));
}
}
}
@@ -0,0 +1,48 @@
import 'package:flutter/material.dart';
import 'package:tmail_ui_user/features/manage_account/domain/state/export_trace_log_state.dart';
import 'package:tmail_ui_user/features/manage_account/domain/usecases/export_trace_log_interactor.dart';
import 'package:tmail_ui_user/features/manage_account/presentation/manage_account_dashboard_controller.dart';
import 'package:tmail_ui_user/features/manage_account/presentation/menu/trace_log_interactor_bindings.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
extension ExportTraceLogExtension on ManageAccountDashBoardController {
void injectTraceLogDependencies() {
TraceLogInteractorBindings().dependencies();
}
void disposeTraceLogDependencies() {
TraceLogInteractorBindings().dispose();
}
Future<void> showExportTraceLogConfirmDialog(BuildContext context) async {
await showConfirmDialogAction(
context,
AppLocalizations.of(context).messageExportTraceLogDialog,
AppLocalizations.of(context).yes,
title: AppLocalizations.of(context).exportTraceLog,
alignCenter: true,
outsideDismissible: false,
useIconAsBasicLogo: false,
cancelTitle: AppLocalizations.of(context).no,
onConfirmAction: _exportTraceLog,
);
}
void _exportTraceLog() {
final exportTraceLogInteractor = getBinding<ExportTraceLogInteractor>();
if (exportTraceLogInteractor != null) {
consumeState(exportTraceLogInteractor.execute());
}
}
void handleExportTraceLogSuccess(ExportTraceLogSuccess success) {
toastManager.showMessageSuccess(success);
}
void handleExportTraceLogFailure(ExportTraceLogFailure failure) {
toastManager.showMessageFailure(failure);
}
}
@@ -16,12 +16,14 @@ import 'package:server_settings/server_settings/capability_server_settings.dart'
import 'package:tmail_ui_user/features/base/action/ui_action.dart';
import 'package:tmail_ui_user/features/base/reloadable/reloadable_controller.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/mixin/user_setting_popup_menu_mixin.dart';
import 'package:tmail_ui_user/features/manage_account/domain/state/export_trace_log_state.dart';
import 'package:tmail_ui_user/features/manage_account/domain/state/get_all_vacation_state.dart';
import 'package:tmail_ui_user/features/manage_account/domain/state/update_vacation_state.dart';
import 'package:tmail_ui_user/features/manage_account/domain/usecases/get_all_vacation_interactor.dart';
import 'package:tmail_ui_user/features/manage_account/domain/usecases/update_vacation_interactor.dart';
import 'package:tmail_ui_user/features/manage_account/presentation/action/dashboard_setting_action.dart';
import 'package:tmail_ui_user/features/manage_account/presentation/email_rules/bindings/email_rules_bindings.dart';
import 'package:tmail_ui_user/features/manage_account/presentation/extensions/export_trace_log_extension.dart';
import 'package:tmail_ui_user/features/manage_account/presentation/extensions/vacation_response_extension.dart';
import 'package:tmail_ui_user/features/manage_account/presentation/forward/bindings/forward_bindings.dart';
import 'package:tmail_ui_user/features/manage_account/presentation/language_and_region/language_and_region_bindings.dart';
@@ -60,6 +62,9 @@ class ManageAccountDashBoardController extends ReloadableController with UserSet
void onInit() {
BackButtonInterceptor.add(_onBackButtonInterceptor, name: AppRoutes.settings);
super.onInit();
if (AppConfig.isApiLoggingEnabled) {
injectTraceLogDependencies();
}
}
@override
@@ -77,11 +82,22 @@ class ManageAccountDashBoardController extends ReloadableController with UserSet
}
} else if (success is UpdateVacationSuccess) {
_handleUpdateVacationSuccess(success);
} else if (success is ExportTraceLogSuccess) {
handleExportTraceLogSuccess(success);
} else {
super.handleSuccessViewState(success);
}
}
@override
void handleFailureViewState(Failure failure) {
if (failure is ExportTraceLogFailure) {
handleExportTraceLogFailure(failure);
} else {
super.handleFailureViewState(failure);
}
}
@override
void handleReloaded(Session session) {
log('ManageAccountDashBoardController::handleReloaded:');
@@ -400,6 +416,9 @@ class ManageAccountDashBoardController extends ReloadableController with UserSet
@override
void onClose() {
BackButtonInterceptor.removeByName(AppRoutes.settings);
if (AppConfig.isApiLoggingEnabled) {
disposeTraceLogDependencies();
}
super.onClose();
}
}
@@ -1,11 +1,13 @@
import 'package:core/presentation/extensions/color_extension.dart';
import 'package:core/presentation/utils/style_utils.dart';
import 'package:core/presentation/views/button/icon_button_web.dart';
import 'package:core/presentation/views/button/multi_click_widget.dart';
import 'package:core/utils/direction_utils.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:get/get.dart';
import 'package:tmail_ui_user/features/manage_account/presentation/email_rules/email_rules_view.dart';
import 'package:tmail_ui_user/features/manage_account/presentation/extensions/export_trace_log_extension.dart';
import 'package:tmail_ui_user/features/manage_account/presentation/extensions/vacation_response_extension.dart';
import 'package:tmail_ui_user/features/manage_account/presentation/forward/forward_view.dart';
import 'package:tmail_ui_user/features/manage_account/presentation/language_and_region/language_and_region_view.dart';
@@ -21,6 +23,7 @@ import 'package:tmail_ui_user/features/manage_account/presentation/profiles/prof
import 'package:tmail_ui_user/features/manage_account/presentation/vacation/vacation_view.dart';
import 'package:tmail_ui_user/features/manage_account/presentation/vacation/widgets/vacation_notification_message_widget.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
import 'package:tmail_ui_user/main/utils/app_config.dart';
typedef CloseSettingsViewAction = void Function();
@@ -84,7 +87,7 @@ class SettingsView extends GetWidget<SettingsController> {
}
Widget _buildUniversalSettingAppBar(BuildContext context) {
return Stack(
final appBarWidget = Stack(
alignment: Alignment.center,
children: [
Positioned(left: 0,child: _buildCloseSettingButton(context)),
@@ -98,10 +101,23 @@ class SettingsView extends GetWidget<SettingsController> {
style: const TextStyle(
fontSize: 20,
color: AppColor.colorNameEmail,
fontWeight: FontWeight.w700))
)
]
fontWeight: FontWeight.w700,
),
),
),
],
);
if (AppConfig.isApiLoggingEnabled) {
return MultiClickWidget(
onMultiTap: () => controller
.manageAccountDashboardController
.showExportTraceLogConfirmDialog(context),
child: appBarWidget,
);
} else {
return appBarWidget;
}
}
Widget _buildSettingLevel1AppBar(BuildContext context) {
@@ -0,0 +1,53 @@
import 'package:core/data/utils/device_manager.dart';
import 'package:core/utils/logger/log_tracking.dart';
import 'package:get/get.dart';
import 'package:tmail_ui_user/features/base/interactors_bindings.dart';
import 'package:tmail_ui_user/features/manage_account/data/datasource/trace_log_datasource.dart';
import 'package:tmail_ui_user/features/manage_account/data/datasource_impl/trace_log_datasource_impl.dart';
import 'package:tmail_ui_user/features/manage_account/data/repository/trace_log_repository.dart';
import 'package:tmail_ui_user/features/manage_account/domain/repository/trace_log_repository_impl.dart';
import 'package:tmail_ui_user/features/manage_account/domain/usecases/export_trace_log_interactor.dart';
import 'package:tmail_ui_user/main/exceptions/cache_exception_thrower.dart';
import 'package:tmail_ui_user/main/permissions/permission_service.dart';
class TraceLogInteractorBindings extends InteractorsBindings {
@override
void bindingsDataSource() {
Get.lazyPut<TraceLogDataSource>(() => Get.find<TraceLogDataSourceImpl>());
}
@override
void bindingsDataSourceImpl() {
Get.lazyPut(() => LogTracking());
Get.lazyPut(() => TraceLogDataSourceImpl(
Get.find<LogTracking>(),
Get.find<DeviceManager>(),
Get.find<PermissionService>(),
Get.find<CacheExceptionThrower>(),
));
}
@override
void bindingsInteractor() {
Get.lazyPut(() => ExportTraceLogInteractor(Get.find<TraceLogRepository>()));
}
@override
void bindingsRepository() {
Get.lazyPut<TraceLogRepository>(() => Get.find<TraceLogRepositoryImpl>());
}
@override
void bindingsRepositoryImpl() {
Get.lazyPut(() => TraceLogRepositoryImpl(Get.find<TraceLogDataSource>()));
}
void dispose() {
Get.delete<ExportTraceLogInteractor>();
Get.delete<TraceLogRepository>();
Get.delete<TraceLogRepositoryImpl>();
Get.delete<TraceLogDataSource>();
Get.delete<TraceLogDataSourceImpl>();
Get.delete<LogTracking>();
}
}
+41 -1
View File
@@ -1,5 +1,5 @@
{
"@@last_modified": "2025-04-14T04:16:15.237055",
"@@last_modified": "2025-05-16T20:14:59.453269",
"initializing_data": "Initializing data...",
"@initializing_data": {
"type": "text",
@@ -4469,5 +4469,45 @@
"placeholders": {
"description": {}
}
},
"exportTraceLog": "Export trace log",
"@exportTraceLog": {
"type": "text",
"placeholders_order": [],
"placeholders": {}
},
"messageExportTraceLogDialog": "The mobile API error message log has been tracked. Do you want to export it?",
"@messageExportTraceLogDialog": {
"type": "text",
"placeholders_order": [],
"placeholders": {}
},
"youNeedToGrantFilesPermissionToExportFile": "You need to grant files permission to export files",
"@youNeedToGrantFilesPermissionToExportFile": {
"type": "text",
"placeholders_order": [],
"placeholders": {}
},
"exportTraceLogSuccess": "Export successful tracking logs at \"{path}\"",
"@exportTraceLogSuccess": {
"type": "text",
"placeholders_order": [
"path"
],
"placeholders": {
"path": {}
}
},
"noLogsHaveBeenRecordedYet": "No logs have been recorded yet.",
"@noLogsHaveBeenRecordedYet": {
"type": "text",
"placeholders_order": [],
"placeholders": {}
},
"exportTraceLogFailed": "Export trace log failed",
"@exportTraceLogFailed": {
"type": "text",
"placeholders_order": [],
"placeholders": {}
}
}
@@ -17,6 +17,7 @@ import 'package:tmail_ui_user/features/base/before_reconnect_manager.dart';
import 'package:tmail_ui_user/features/caching/utils/local_storage_manager.dart';
import 'package:tmail_ui_user/features/caching/utils/session_storage_manager.dart';
import 'package:tmail_ui_user/features/sending_queue/presentation/utils/sending_queue_isolate_manager.dart';
import 'package:tmail_ui_user/main/permissions/permission_service.dart';
import 'package:tmail_ui_user/main/utils/app_config.dart';
import 'package:tmail_ui_user/main/utils/email_receive_manager.dart';
import 'package:tmail_ui_user/main/utils/ios_notification_manager.dart';
@@ -59,6 +60,7 @@ class CoreBindings extends Bindings {
void _bindingDeviceManager() {
Get.put(DeviceInfoPlugin());
Get.put(DeviceManager(Get.find<DeviceInfoPlugin>()));
Get.put(PermissionService());
}
void _bindingReceivingSharingStream() {
@@ -0,0 +1,15 @@
import 'package:equatable/equatable.dart';
abstract class PermissionException with EquatableMixin implements Exception {
final String? message;
const PermissionException({this.message});
}
class NotGrantedPermissionStorageException extends PermissionException {
const NotGrantedPermissionStorageException() : super(message: 'Permission Storage has not been granted access');
@override
List<Object?> get props => [super.message];
}
@@ -4702,4 +4702,47 @@ class AppLocalizations {
args: [description],
);
}
String get exportTraceLog {
return Intl.message(
'Export trace log',
name: 'exportTraceLog',
);
}
String get messageExportTraceLogDialog {
return Intl.message(
'The mobile API error message log has been tracked. Do you want to export it?',
name: 'messageExportTraceLogDialog',
);
}
String get youNeedToGrantFilesPermissionToExportFile {
return Intl.message(
'You need to grant files permission to export files',
name: 'youNeedToGrantFilesPermissionToExportFile',
);
}
String exportTraceLogSuccess(String path) {
return Intl.message(
'Export successful tracking logs at "$path"',
name: 'exportTraceLogSuccess',
args: [path],
);
}
String get noLogsHaveBeenRecordedYet {
return Intl.message(
'No logs have been recorded yet.',
name: 'noLogsHaveBeenRecordedYet',
);
}
String get exportTraceLogFailed {
return Intl.message(
'Export trace log failed',
name: 'exportTraceLogFailed',
);
}
}
+13
View File
@@ -1,5 +1,6 @@
import 'dart:io';
import 'package:core/domain/exceptions/file_exception.dart';
import 'package:core/domain/exceptions/web_session_exception.dart';
import 'package:core/presentation/state/failure.dart';
import 'package:core/presentation/state/success.dart';
@@ -19,12 +20,14 @@ import 'package:tmail_ui_user/features/home/domain/state/get_session_state.dart'
import 'package:tmail_ui_user/features/login/data/network/oidc_error.dart';
import 'package:tmail_ui_user/features/login/domain/exceptions/authentication_exception.dart';
import 'package:tmail_ui_user/features/mailbox/domain/state/clear_mailbox_state.dart';
import 'package:tmail_ui_user/features/manage_account/domain/state/export_trace_log_state.dart';
import 'package:tmail_ui_user/features/starting_page/domain/state/sign_in_twake_workplace_state.dart';
import 'package:tmail_ui_user/features/starting_page/domain/state/sign_up_twake_workplace_state.dart';
import 'package:tmail_ui_user/features/thread/domain/state/empty_spam_folder_state.dart';
import 'package:tmail_ui_user/features/thread/domain/state/empty_trash_folder_state.dart';
import 'package:tmail_ui_user/features/thread/domain/state/move_multiple_email_to_mailbox_state.dart';
import 'package:tmail_ui_user/main/error/error_method_response_exception_extension.dart';
import 'package:tmail_ui_user/main/exceptions/permission_exception.dart';
import 'package:tmail_ui_user/main/exceptions/remote_exception.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
@@ -132,6 +135,14 @@ class ToastManager {
} else {
message = AppLocalizations.of(currentContext!).previewEmailFromEMLFileFailed;
}
} else if (failure is ExportTraceLogFailure) {
if (failure.exception is NotGrantedPermissionStorageException) {
message = AppLocalizations.of(currentContext!).youNeedToGrantFilesPermissionToExportFile;
} else if (failure.exception is NotFoundFileInFolderException) {
message = AppLocalizations.of(currentContext!).noLogsHaveBeenRecordedYet;
} else {
message = AppLocalizations.of(currentContext!).exportTraceLogFailed;
}
}
if (message?.isNotEmpty == true) {
@@ -151,6 +162,8 @@ class ToastManager {
success is EmptySpamFolderSuccess ||
success is EmptyTrashFolderSuccess) {
message = AppLocalizations.of(currentContext!).toast_message_empty_trash_folder_success;
} else if (success is ExportTraceLogSuccess) {
message = AppLocalizations.of(currentContext!).exportTraceLogSuccess(success.savePath);
}
if (message?.isNotEmpty == true) {