TF-3719 Add handle export log to device if need
Signed-off-by: dab246 <tdvu@linagora.com>
This commit is contained in:
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
Reference in New Issue
Block a user