update file: add show hidden, add remember last path, add breadcrumb action
This commit is contained in:
parent
281acf7474
commit
27b80f034c
@ -4,6 +4,12 @@ import 'package:flutter_easyloading/flutter_easyloading.dart';
|
||||
|
||||
final globalKey = GlobalKey<NavigatorState>();
|
||||
|
||||
var isAndroid = false;
|
||||
var isIOS = false;
|
||||
var isWeb = false;
|
||||
var isDesktop = false;
|
||||
var version = "";
|
||||
|
||||
typedef F = String Function(String);
|
||||
typedef FMethod = String Function(String, dynamic);
|
||||
|
||||
@ -172,8 +178,18 @@ Color str2color(String str, [alpha = 0xFF]) {
|
||||
return Color((hash & 0xFF7FFF) | (alpha << 24));
|
||||
}
|
||||
|
||||
bool isAndroid = false;
|
||||
bool isIOS = false;
|
||||
bool isWeb = false;
|
||||
bool isDesktop = false;
|
||||
var version = "";
|
||||
const K = 1024;
|
||||
const M = K * K;
|
||||
const G = M * K;
|
||||
|
||||
String readableFileSize(double size) {
|
||||
if (size < K) {
|
||||
return size.toString() + " B";
|
||||
} else if (size < M) {
|
||||
return (size / K).toStringAsFixed(2) + " KB";
|
||||
} else if (size < G) {
|
||||
return (size / M).toStringAsFixed(2) + " MB";
|
||||
} else {
|
||||
return (size / G).toStringAsFixed(2) + " GB";
|
||||
}
|
||||
}
|
||||
|
@ -19,15 +19,13 @@ enum SortBy { name, type, date, size }
|
||||
// FileLink = 5,
|
||||
// }
|
||||
|
||||
class RemoveCompleter {}
|
||||
|
||||
typedef OnJobStateChange = void Function(JobState state, JobProgress jp);
|
||||
|
||||
class FileModel extends ChangeNotifier {
|
||||
// TODO 添加 dispose 退出页面的时候清理数据以及尚未完成的任务和对话框
|
||||
var _isLocal = false;
|
||||
var _selectMode = false;
|
||||
|
||||
var _localOption = DirectoryOption();
|
||||
var _remoteOption = DirectoryOption();
|
||||
|
||||
/// 每一个选择的文件或文件夹占用一个 _jobId,file_num是文件夹中的单独文件id
|
||||
/// 如
|
||||
/// 发送单独一个文件 file_num = 0;
|
||||
@ -58,6 +56,28 @@ class FileModel extends ChangeNotifier {
|
||||
|
||||
FileDirectory get currentDir => _isLocal ? currentLocalDir : currentRemoteDir;
|
||||
|
||||
String get currentHome => _isLocal ? _localOption.home : _remoteOption.home;
|
||||
|
||||
String get currentShortPath {
|
||||
if(currentDir.path.startsWith(currentHome)){
|
||||
var path = currentDir.path.replaceFirst(currentHome, "");
|
||||
if(path.length ==0 ) return "";
|
||||
if(path[0] == "/" || path[0] == "\\") {
|
||||
// remove more '/' or '\'
|
||||
path = path.replaceFirst(path[0], "");
|
||||
}
|
||||
return path;
|
||||
}else{
|
||||
return currentDir.path.replaceFirst(currentHome, "");
|
||||
}
|
||||
}
|
||||
|
||||
bool get currentShowHidden =>
|
||||
_isLocal ? _localOption.showHidden : _remoteOption.showHidden;
|
||||
|
||||
bool get currentIsWindows =>
|
||||
_isLocal ? _localOption.isWindows : _remoteOption.isWindows;
|
||||
|
||||
final _fileFetcher = FileFetcher();
|
||||
|
||||
final _jobResultListener = JobResultListener<Map<String, dynamic>>();
|
||||
@ -72,6 +92,16 @@ class FileModel extends ChangeNotifier {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
toggleShowHidden({bool? showHidden, bool? local}) {
|
||||
final isLocal = local ?? _isLocal;
|
||||
if (isLocal) {
|
||||
_localOption.showHidden = showHidden ?? !_localOption.showHidden;
|
||||
} else {
|
||||
_remoteOption.showHidden = showHidden ?? !_remoteOption.showHidden;
|
||||
}
|
||||
refresh();
|
||||
}
|
||||
|
||||
tryUpdateJobProgress(Map<String, dynamic> evt) {
|
||||
try {
|
||||
int id = int.parse(evt['id']);
|
||||
@ -79,7 +109,6 @@ class FileModel extends ChangeNotifier {
|
||||
_jobProgress.fileNum = int.parse(evt['file_num']);
|
||||
_jobProgress.speed = double.parse(evt['speed']);
|
||||
_jobProgress.finishedSize = int.parse(evt['finished_size']);
|
||||
debugPrint("_jobProgress update:${_jobProgress.toString()}");
|
||||
notifyListeners();
|
||||
} catch (e) {
|
||||
debugPrint("Failed to tryUpdateJobProgress,evt:${evt.toString()}");
|
||||
@ -107,7 +136,7 @@ class FileModel extends ChangeNotifier {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO
|
||||
debugPrint("jobError $evt");
|
||||
_selectMode = false;
|
||||
_jobProgress.clear();
|
||||
_jobProgress.state = JobState.error;
|
||||
@ -120,19 +149,69 @@ class FileModel extends ChangeNotifier {
|
||||
}
|
||||
|
||||
onReady() {
|
||||
openDirectory(FFI.getByName("get_home_dir"), isLocal: true);
|
||||
openDirectory(FFI.ffiModel.pi.homeDir, isLocal: false);
|
||||
_localOption = DirectoryOption(
|
||||
home: FFI.getByName("get_home_dir"),
|
||||
showHidden: FFI.getByName("peer_option", "local_show_hidden") != "");
|
||||
_remoteOption = DirectoryOption(
|
||||
home: FFI.ffiModel.pi.homeDir,
|
||||
showHidden: FFI.getByName("peer_option", "remote_show_hidden") != "",
|
||||
isWindows: FFI.ffiModel.pi.platform == "Windows");
|
||||
|
||||
debugPrint("remote platform: ${FFI.ffiModel.pi.platform}");
|
||||
|
||||
final local = FFI.getByName("peer_option", "local_dir");
|
||||
final remote = FFI.getByName("peer_option", "remote_dir");
|
||||
openDirectory(local.isEmpty ? _localOption.home : local, isLocal: true);
|
||||
openDirectory(remote.isEmpty ? _remoteOption.home : remote, isLocal: false);
|
||||
Timer(Duration(seconds: 2), () {
|
||||
if (_currentLocalDir.path.isEmpty) {
|
||||
openDirectory(_localOption.home, isLocal: true);
|
||||
}
|
||||
if (_currentRemoteDir.path.isEmpty) {
|
||||
openDirectory(_remoteOption.home, isLocal: false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onClose() {
|
||||
DialogManager.reset();
|
||||
EasyLoading.dismiss();
|
||||
|
||||
// save config
|
||||
Map<String, String> msg = Map();
|
||||
|
||||
msg["name"] = "local_dir";
|
||||
msg["value"] = _currentLocalDir.path;
|
||||
FFI.setByName('peer_option', jsonEncode(msg));
|
||||
|
||||
msg["name"] = "local_show_hidden";
|
||||
msg["value"] = _localOption.showHidden ? "Y" : "";
|
||||
FFI.setByName('peer_option', jsonEncode(msg));
|
||||
|
||||
msg["name"] = "remote_dir";
|
||||
msg["value"] = _currentRemoteDir.path;
|
||||
FFI.setByName('peer_option', jsonEncode(msg));
|
||||
|
||||
msg["name"] = "remote_show_hidden";
|
||||
msg["value"] = _remoteOption.showHidden ? "Y" : "";
|
||||
FFI.setByName('peer_option', jsonEncode(msg));
|
||||
_currentLocalDir.clear();
|
||||
_currentRemoteDir.clear();
|
||||
}
|
||||
|
||||
refresh() {
|
||||
openDirectory(_isLocal ? _currentLocalDir.path : _currentRemoteDir.path);
|
||||
openDirectory(currentDir.path);
|
||||
}
|
||||
|
||||
openDirectory(String path, {bool? isLocal}) async {
|
||||
isLocal = isLocal ?? _isLocal;
|
||||
final showHidden =
|
||||
isLocal ? _localOption.showHidden : _remoteOption.showHidden;
|
||||
final isWindows =
|
||||
isLocal ? _localOption.isWindows : _remoteOption.isWindows;
|
||||
try {
|
||||
final fd = await _fileFetcher.fetchDirectory(path, isLocal);
|
||||
fd.changeSortStyle(_sortStyle);
|
||||
final fd = await _fileFetcher.fetchDirectory(path, isLocal, showHidden);
|
||||
fd.format(isWindows, sort: _sortStyle);
|
||||
if (isLocal) {
|
||||
_currentLocalDir = fd;
|
||||
} else {
|
||||
@ -144,9 +223,12 @@ class FileModel extends ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
goHome() {
|
||||
openDirectory(currentHome);
|
||||
}
|
||||
|
||||
goToParentDirectory() {
|
||||
final fd = _isLocal ? _currentLocalDir : _currentRemoteDir;
|
||||
openDirectory(fd.parent);
|
||||
openDirectory(currentDir.parent);
|
||||
}
|
||||
|
||||
sendFiles(SelectedItems items) {
|
||||
@ -157,13 +239,17 @@ class FileModel extends ChangeNotifier {
|
||||
_jobProgress.state = JobState.inProgress;
|
||||
final toPath =
|
||||
items.isLocal! ? currentRemoteDir.path : currentLocalDir.path;
|
||||
final isWindows =
|
||||
items.isLocal! ? _localOption.isWindows : _remoteOption.isWindows;
|
||||
final showHidden =
|
||||
items.isLocal! ? _localOption.showHidden : _remoteOption.showHidden;
|
||||
items.items.forEach((from) {
|
||||
_jobId++;
|
||||
final msg = {
|
||||
"id": _jobId.toString(),
|
||||
"path": from.path,
|
||||
"to": Path.join(toPath, from.name),
|
||||
"show_hidden": "false", // TODO showHidden
|
||||
"to": PathUtil.join(toPath, from.name, isWindows),
|
||||
"show_hidden": showHidden.toString(),
|
||||
"is_remote": (!(items.isLocal!)).toString() // 指from的位置而不是to的位置
|
||||
};
|
||||
FFI.setByName("send_files", jsonEncode(msg));
|
||||
@ -178,6 +264,8 @@ class FileModel extends ChangeNotifier {
|
||||
debugPrint("Failed to removeFile ,wrong path state");
|
||||
return;
|
||||
}
|
||||
final isWindows =
|
||||
items.isLocal! ? _localOption.isWindows : _remoteOption.isWindows;
|
||||
await Future.forEach(items.items, (Entry item) async {
|
||||
_jobId++;
|
||||
var title = "";
|
||||
@ -191,35 +279,33 @@ class FileModel extends ChangeNotifier {
|
||||
title = "这不是一个空文件夹";
|
||||
showLoading("正在读取...");
|
||||
final fd = await _fileFetcher.fetchDirectoryRecursive(
|
||||
_jobId, item.path, items.isLocal!);
|
||||
_jobId, item.path, items.isLocal!, true);
|
||||
fd.format(isWindows);
|
||||
EasyLoading.dismiss();
|
||||
// 空文件夹
|
||||
if(fd.entries.isEmpty){
|
||||
final confirm = await showRemoveDialog("是否删除空文件夹",item.name,false);
|
||||
if(confirm == true){
|
||||
if (fd.entries.isEmpty) {
|
||||
final confirm = await showRemoveDialog("是否删除空文件夹", item.name, false);
|
||||
if (confirm == true) {
|
||||
sendRemoveEmptyDir(item.path, 0, items.isLocal!);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
debugPrint("removeDirAllIntent res:${fd.id}");
|
||||
entries = fd.entries;
|
||||
} else {
|
||||
debugPrint("none : ${item.toString()}");
|
||||
entries = [];
|
||||
}
|
||||
|
||||
for (var i = 0; i < entries.length; i++) {
|
||||
final dirShow = item.isDirectory?"是否删除文件夹下的文件?\n":"";
|
||||
final count = entries.length>1?"第 ${i + 1}/${entries.length} 项":"";
|
||||
final dirShow = item.isDirectory ? "是否删除文件夹下的文件?\n" : "";
|
||||
final count =
|
||||
entries.length > 1 ? "第 ${i + 1}/${entries.length} 项" : "";
|
||||
content = dirShow + "$count \n${entries[i].path}";
|
||||
final confirm = await showRemoveDialog(title,content,item.isDirectory);
|
||||
debugPrint("已选择:$confirm");
|
||||
final confirm =
|
||||
await showRemoveDialog(title, content, item.isDirectory);
|
||||
try {
|
||||
if (confirm == true) {
|
||||
sendRemoveFile(entries[i].path, i, items.isLocal!);
|
||||
final res = await _jobResultListener.start();
|
||||
debugPrint("remove got res ${res.toString()}");
|
||||
// handle remove res;
|
||||
if (item.isDirectory &&
|
||||
res['file_num'] == (entries.length - 1).toString()) {
|
||||
@ -231,7 +317,6 @@ class FileModel extends ChangeNotifier {
|
||||
for (var j = i + 1; j < entries.length; j++) {
|
||||
sendRemoveFile(entries[j].path, j, items.isLocal!);
|
||||
final res = await _jobResultListener.start();
|
||||
debugPrint("remove got res ${res.toString()}");
|
||||
if (item.isDirectory &&
|
||||
res['file_num'] == (entries.length - 1).toString()) {
|
||||
sendRemoveEmptyDir(item.path, i, items.isLocal!);
|
||||
@ -246,9 +331,10 @@ class FileModel extends ChangeNotifier {
|
||||
refresh();
|
||||
}
|
||||
|
||||
Future<bool?> showRemoveDialog(String title,String content,bool showCheckbox) async {
|
||||
return await DialogManager.show<bool>(
|
||||
(setState, Function(bool v) close) => CustomAlertDialog(
|
||||
Future<bool?> showRemoveDialog(
|
||||
String title, String content, bool showCheckbox) async {
|
||||
return await DialogManager.show<bool>((setState, Function(bool v) close) =>
|
||||
CustomAlertDialog(
|
||||
title: Row(
|
||||
children: [
|
||||
Icon(Icons.warning, color: Colors.red),
|
||||
@ -262,29 +348,33 @@ class FileModel extends ChangeNotifier {
|
||||
children: [
|
||||
Text(content),
|
||||
SizedBox(height: 5),
|
||||
Text("此操作不可逆!",style: TextStyle(fontWeight: FontWeight.bold)),
|
||||
showCheckbox?
|
||||
CheckboxListTile(
|
||||
contentPadding: const EdgeInsets.all(0),
|
||||
dense: true,
|
||||
controlAffinity: ListTileControlAffinity.leading,
|
||||
title: Text(
|
||||
"应用于文件夹下所有文件",
|
||||
),
|
||||
value: removeCheckboxRemember,
|
||||
onChanged: (v) {
|
||||
if (v == null) return;
|
||||
setState(() => removeCheckboxRemember = v);
|
||||
},
|
||||
):SizedBox.shrink()
|
||||
Text("此操作不可逆!",
|
||||
style: TextStyle(fontWeight: FontWeight.bold)),
|
||||
showCheckbox
|
||||
? CheckboxListTile(
|
||||
contentPadding: const EdgeInsets.all(0),
|
||||
dense: true,
|
||||
controlAffinity: ListTileControlAffinity.leading,
|
||||
title: Text(
|
||||
"应用于文件夹下所有文件",
|
||||
),
|
||||
value: removeCheckboxRemember,
|
||||
onChanged: (v) {
|
||||
if (v == null) return;
|
||||
setState(() => removeCheckboxRemember = v);
|
||||
},
|
||||
)
|
||||
: SizedBox.shrink()
|
||||
]),
|
||||
actions: [
|
||||
TextButton(
|
||||
style: flatButtonStyle,
|
||||
onPressed: () => close(true), child: Text("Yes")),
|
||||
onPressed: () => close(true),
|
||||
child: Text("Yes")),
|
||||
TextButton(
|
||||
style: flatButtonStyle,
|
||||
onPressed: () => close(false), child: Text("No"))
|
||||
onPressed: () => close(false),
|
||||
child: Text("No"))
|
||||
]));
|
||||
}
|
||||
|
||||
@ -308,18 +398,16 @@ class FileModel extends ChangeNotifier {
|
||||
}
|
||||
|
||||
createDir(String path) {
|
||||
_jobId ++;
|
||||
_jobId++;
|
||||
final msg = {
|
||||
"id": _jobId.toString(),
|
||||
"path": path,
|
||||
"is_remote": (!isLocal).toString()
|
||||
};
|
||||
FFI.setByName("create_dir",jsonEncode(msg));
|
||||
FFI.setByName("create_dir", jsonEncode(msg));
|
||||
}
|
||||
|
||||
cancelJob(int id){
|
||||
|
||||
}
|
||||
cancelJob(int id) {}
|
||||
|
||||
changeSortStyle(SortBy sort) {
|
||||
_sortStyle = sort;
|
||||
@ -327,11 +415,6 @@ class FileModel extends ChangeNotifier {
|
||||
_currentRemoteDir.changeSortStyle(sort);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void clear() {
|
||||
_currentLocalDir.clear();
|
||||
_currentRemoteDir.clear();
|
||||
}
|
||||
}
|
||||
|
||||
class JobResultListener<T> {
|
||||
@ -413,7 +496,6 @@ class FileFetcher {
|
||||
}
|
||||
|
||||
tryCompleteTask(String? msg, String? isLocalStr) {
|
||||
debugPrint("tryCompleteTask : $msg");
|
||||
if (msg == null || isLocalStr == null) return;
|
||||
late final isLocal;
|
||||
late final tasks;
|
||||
@ -444,15 +526,16 @@ class FileFetcher {
|
||||
}
|
||||
}
|
||||
|
||||
Future<FileDirectory> fetchDirectory(String path, bool isLocal) async {
|
||||
debugPrint("fetch :$path");
|
||||
Future<FileDirectory> fetchDirectory(
|
||||
String path, bool isLocal, bool showHidden) async {
|
||||
try {
|
||||
final msg = {"path": path, "show_hidden": showHidden.toString()};
|
||||
if (isLocal) {
|
||||
final res = FFI.getByName("read_dir", path);
|
||||
final res = FFI.getByName("read_local_dir_sync", jsonEncode(msg));
|
||||
final fd = FileDirectory.fromJson(jsonDecode(res));
|
||||
return fd;
|
||||
} else {
|
||||
FFI.setByName("read_remote_dir", path);
|
||||
FFI.setByName("read_remote_dir", jsonEncode(msg));
|
||||
return registerReadTask(isLocal, path);
|
||||
}
|
||||
} catch (e) {
|
||||
@ -461,12 +544,13 @@ class FileFetcher {
|
||||
}
|
||||
|
||||
Future<FileDirectory> fetchDirectoryRecursive(
|
||||
int id, String path, bool isLocal) async {
|
||||
debugPrint("fetchDirectoryRecursive id:$id , path:$path");
|
||||
int id, String path, bool isLocal, bool showHidden) async {
|
||||
// TODO test Recursive is show hidden default?
|
||||
try {
|
||||
final msg = {
|
||||
"id": id.toString(),
|
||||
"path": path,
|
||||
"show_hidden": showHidden.toString(),
|
||||
"is_remote": (!isLocal).toString()
|
||||
};
|
||||
FFI.setByName("read_dir_recursive", jsonEncode(msg));
|
||||
@ -486,26 +570,21 @@ class FileDirectory {
|
||||
|
||||
FileDirectory();
|
||||
|
||||
FileDirectory.fromJsonWithSort(Map<String, dynamic> json, SortBy sort) {
|
||||
id = json['id'];
|
||||
path = json['path'];
|
||||
if (json['entries'] != null) {
|
||||
entries = <Entry>[];
|
||||
json['entries'].forEach((v) {
|
||||
entries.add(new Entry.fromJsonWithPath(v, path));
|
||||
});
|
||||
entries = _sortList(entries, sort);
|
||||
}
|
||||
}
|
||||
|
||||
FileDirectory.fromJson(Map<String, dynamic> json) {
|
||||
id = json['id'];
|
||||
path = json['path'];
|
||||
if (json['entries'] != null) {
|
||||
entries = <Entry>[];
|
||||
json['entries'].forEach((v) {
|
||||
entries.add(new Entry.fromJsonWithPath(v, path));
|
||||
});
|
||||
json['entries'].forEach((v) {
|
||||
entries.add(new Entry.fromJson(v));
|
||||
});
|
||||
}
|
||||
|
||||
// generate full path for every entry , init sort style if need.
|
||||
format(bool isWindows, {SortBy? sort}) {
|
||||
entries.forEach((entry) {
|
||||
entry.path = PathUtil.join(path, entry.name, isWindows);
|
||||
});
|
||||
if (sort != null) {
|
||||
changeSortStyle(sort);
|
||||
}
|
||||
}
|
||||
|
||||
@ -529,12 +608,11 @@ class Entry {
|
||||
|
||||
Entry();
|
||||
|
||||
Entry.fromJsonWithPath(Map<String, dynamic> json, String parent) {
|
||||
Entry.fromJson(Map<String, dynamic> json) {
|
||||
entryType = json['entry_type'];
|
||||
modifiedTime = json['modified_time'];
|
||||
name = json['name'];
|
||||
size = json['size'];
|
||||
path = Path.join(parent, name);
|
||||
}
|
||||
|
||||
bool get isFile => entryType > 3;
|
||||
@ -571,6 +649,30 @@ class _PathStat {
|
||||
_PathStat(this.path, this.dateTime);
|
||||
}
|
||||
|
||||
class PathUtil {
|
||||
static final windowsContext = Path.Context(style: Path.Style.windows);
|
||||
static final posixContext = Path.Context(style: Path.Style.posix);
|
||||
|
||||
static String join(String path1, String path2, bool isWindows) {
|
||||
final pathUtil = isWindows ? windowsContext : posixContext;
|
||||
return pathUtil.join(path1, path2);
|
||||
}
|
||||
|
||||
static List<String> split(String path, bool isWindows) {
|
||||
final pathUtil = isWindows ? windowsContext : posixContext;
|
||||
return pathUtil.split(path);
|
||||
}
|
||||
}
|
||||
|
||||
class DirectoryOption {
|
||||
String home;
|
||||
bool showHidden;
|
||||
bool isWindows;
|
||||
|
||||
DirectoryOption(
|
||||
{this.home = "", this.showHidden = false, this.isWindows = false});
|
||||
}
|
||||
|
||||
// code from file_manager pkg after edit
|
||||
List<Entry> _sortList(List<Entry> list, SortBy sortType) {
|
||||
if (sortType == SortBy.name) {
|
||||
|
@ -4,7 +4,6 @@ import 'package:flutter_easyloading/flutter_easyloading.dart';
|
||||
import 'package:flutter_hbb/models/file_model.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter_breadcrumb/flutter_breadcrumb.dart';
|
||||
import 'package:path/path.dart' as Path;
|
||||
|
||||
import '../common.dart';
|
||||
import '../models/model.dart';
|
||||
@ -38,7 +37,7 @@ class _FileManagerPageState extends State<FileManagerPage> {
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
model.clear();
|
||||
model.onClose();
|
||||
_interval?.cancel();
|
||||
FFI.close();
|
||||
EasyLoading.dismiss();
|
||||
@ -97,28 +96,16 @@ class _FileManagerPageState extends State<FileManagerPage> {
|
||||
itemCount: entries.length + 1,
|
||||
itemBuilder: (context, index) {
|
||||
if (index >= entries.length) {
|
||||
// 添加尾部信息 文件统计信息等
|
||||
// 添加快速返回上部
|
||||
// 使用 bottomSheet 提示以选择的文件数量 点击后展开查看更多
|
||||
return listTail();
|
||||
}
|
||||
var selected = false;
|
||||
if (model.selectMode) {
|
||||
selected = _selectedItems.contains(entries[index]);
|
||||
}
|
||||
var sizeStr = "";
|
||||
if (entries[index].isFile) {
|
||||
final size = entries[index].size;
|
||||
if (size < 1024) {
|
||||
sizeStr += size.toString() + "B";
|
||||
} else if (size < 1024 * 1024) {
|
||||
sizeStr += (size / 1024).toStringAsFixed(2) + "kB";
|
||||
} else if (size < 1024 * 1024 * 1024) {
|
||||
sizeStr += (size / 1024 / 1024).toStringAsFixed(2) + "MB";
|
||||
} else if (size < 1024 * 1024 * 1024 * 1024) {
|
||||
sizeStr += (size / 1024 / 1024 / 1024).toStringAsFixed(2) + "GB";
|
||||
}
|
||||
}
|
||||
|
||||
final sizeStr = entries[index].isFile
|
||||
? readableFileSize(entries[index].size.toDouble())
|
||||
: "";
|
||||
return Card(
|
||||
child: ListTile(
|
||||
leading: Icon(
|
||||
@ -248,8 +235,21 @@ class _FileManagerPageState extends State<FileManagerPage> {
|
||||
children: [
|
||||
Expanded(
|
||||
child: BreadCrumb(
|
||||
items: getPathBreadCrumbItems(() => debugPrint("pressed home"),
|
||||
(e) => debugPrint("pressed url:$e")),
|
||||
items: getPathBreadCrumbItems(() => model.goHome(), (list) {
|
||||
var path = "";
|
||||
if (model.currentHome.startsWith(list[0])) {
|
||||
// absolute path
|
||||
for (var item in list) {
|
||||
path = PathUtil.join(path, item, model.currentIsWindows);
|
||||
}
|
||||
} else {
|
||||
path += model.currentHome;
|
||||
for (var item in list) {
|
||||
path = PathUtil.join(path, item, model.currentIsWindows);
|
||||
}
|
||||
}
|
||||
model.openDirectory(path);
|
||||
}),
|
||||
divider: Icon(Icons.chevron_right),
|
||||
overflow: ScrollableOverflow(controller: _breadCrumbScroller),
|
||||
)),
|
||||
@ -261,8 +261,8 @@ class _FileManagerPageState extends State<FileManagerPage> {
|
||||
itemBuilder: (context) {
|
||||
return SortBy.values
|
||||
.map((e) => PopupMenuItem(
|
||||
child:
|
||||
Text(translate(e.toString().split(".").last)),
|
||||
child: Text(
|
||||
e.toString().split(".").last.toUpperCase()),
|
||||
value: e,
|
||||
))
|
||||
.toList();
|
||||
@ -274,24 +274,45 @@ class _FileManagerPageState extends State<FileManagerPage> {
|
||||
return [
|
||||
PopupMenuItem(
|
||||
child: Row(
|
||||
children: [Icon(Icons.refresh), Text("刷新")],
|
||||
children: [
|
||||
Icon(Icons.refresh),
|
||||
SizedBox(width: 5),
|
||||
Text("刷新")
|
||||
],
|
||||
),
|
||||
value: "refresh",
|
||||
),
|
||||
PopupMenuItem(
|
||||
child: Row(
|
||||
children: [Icon(Icons.check), Text("多选")],
|
||||
children: [
|
||||
Icon(Icons.check),
|
||||
SizedBox(width: 5),
|
||||
Text("多选")
|
||||
],
|
||||
),
|
||||
value: "select",
|
||||
),
|
||||
PopupMenuItem(
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.folder),
|
||||
Icon(Icons.folder_outlined),
|
||||
SizedBox(width: 5),
|
||||
Text(translate("Create Folder"))
|
||||
],
|
||||
),
|
||||
value: "folder",
|
||||
),
|
||||
PopupMenuItem(
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(model.currentShowHidden
|
||||
? Icons.check_box_outlined
|
||||
: Icons.check_box_outline_blank),
|
||||
SizedBox(width: 5),
|
||||
Text(translate("Toggle Hidden"))
|
||||
],
|
||||
),
|
||||
value: "hidden",
|
||||
)
|
||||
];
|
||||
},
|
||||
@ -322,7 +343,10 @@ class _FileManagerPageState extends State<FileManagerPage> {
|
||||
style: flatButtonStyle,
|
||||
onPressed: () {
|
||||
if (name.value.text.isNotEmpty) {
|
||||
model.createDir(Path.join(model.currentDir.path,name.value.text));
|
||||
model.createDir(PathUtil.join(
|
||||
model.currentDir.path,
|
||||
name.value.text,
|
||||
model.currentIsWindows));
|
||||
close();
|
||||
}
|
||||
},
|
||||
@ -332,6 +356,8 @@ class _FileManagerPageState extends State<FileManagerPage> {
|
||||
onPressed: () => close(false),
|
||||
child: Text(translate("Cancel")))
|
||||
]));
|
||||
} else if (v == "hidden") {
|
||||
model.toggleShowHidden();
|
||||
}
|
||||
}),
|
||||
],
|
||||
@ -349,7 +375,20 @@ class _FileManagerPageState extends State<FileManagerPage> {
|
||||
}
|
||||
|
||||
Widget listTail() {
|
||||
return SizedBox(height: 100);
|
||||
return Container(
|
||||
height: 100,
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.all(2),
|
||||
child: Text(
|
||||
"总计: ${model.currentDir.entries.length}个项目",
|
||||
style: TextStyle(color: MyTheme.darkGray),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget? bottomSheet() {
|
||||
@ -402,8 +441,7 @@ class _FileManagerPageState extends State<FileManagerPage> {
|
||||
return BottomSheetBody(
|
||||
leading: CircularProgressIndicator(),
|
||||
title: "正在发送文件...",
|
||||
text:
|
||||
"速度: ${(model.jobProgress.speed / 1024).toStringAsFixed(2)} kb/s",
|
||||
text: "速度: ${readableFileSize(model.jobProgress.speed)}/s",
|
||||
onCanceled: null,
|
||||
);
|
||||
case JobState.done:
|
||||
@ -427,10 +465,9 @@ class _FileManagerPageState extends State<FileManagerPage> {
|
||||
}
|
||||
|
||||
List<BreadCrumbItem> getPathBreadCrumbItems(
|
||||
void Function() onHome, void Function(String) onPressed) {
|
||||
final path = model.currentDir.path;
|
||||
final list = Path.split(path);
|
||||
list.remove('/');
|
||||
void Function() onHome, void Function(List<String>) onPressed) {
|
||||
final path = model.currentShortPath;
|
||||
final list = PathUtil.split(path, model.currentIsWindows);
|
||||
final breadCrumbList = [
|
||||
BreadCrumbItem(
|
||||
content: IconButton(
|
||||
@ -438,12 +475,12 @@ class _FileManagerPageState extends State<FileManagerPage> {
|
||||
onPressed: onHome,
|
||||
))
|
||||
];
|
||||
breadCrumbList.addAll(list.map((e) => BreadCrumbItem(
|
||||
breadCrumbList.addAll(list.asMap().entries.map((e) => BreadCrumbItem(
|
||||
content: TextButton(
|
||||
child: Text(e),
|
||||
child: Text(e.value),
|
||||
style:
|
||||
ButtonStyle(minimumSize: MaterialStateProperty.all(Size(0, 0))),
|
||||
onPressed: () => onPressed(e)))));
|
||||
onPressed: () => onPressed(list.sublist(0, e.key + 1))))));
|
||||
return breadCrumbList;
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user