update file: add show hidden, add remember last path, add breadcrumb action

This commit is contained in:
csf 2022-03-17 21:03:52 +08:00
parent 281acf7474
commit 27b80f034c
3 changed files with 282 additions and 127 deletions

View File

@ -4,6 +4,12 @@ import 'package:flutter_easyloading/flutter_easyloading.dart';
final globalKey = GlobalKey<NavigatorState>(); final globalKey = GlobalKey<NavigatorState>();
var isAndroid = false;
var isIOS = false;
var isWeb = false;
var isDesktop = false;
var version = "";
typedef F = String Function(String); typedef F = String Function(String);
typedef FMethod = String Function(String, dynamic); typedef FMethod = String Function(String, dynamic);
@ -172,8 +178,18 @@ Color str2color(String str, [alpha = 0xFF]) {
return Color((hash & 0xFF7FFF) | (alpha << 24)); return Color((hash & 0xFF7FFF) | (alpha << 24));
} }
bool isAndroid = false; const K = 1024;
bool isIOS = false; const M = K * K;
bool isWeb = false; const G = M * K;
bool isDesktop = false;
var version = ""; 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";
}
}

View File

@ -19,15 +19,13 @@ enum SortBy { name, type, date, size }
// FileLink = 5, // FileLink = 5,
// } // }
class RemoveCompleter {}
typedef OnJobStateChange = void Function(JobState state, JobProgress jp);
class FileModel extends ChangeNotifier { class FileModel extends ChangeNotifier {
// TODO dispose 退
var _isLocal = false; var _isLocal = false;
var _selectMode = false; var _selectMode = false;
var _localOption = DirectoryOption();
var _remoteOption = DirectoryOption();
/// _jobIdfile_num是文件夹中的单独文件id /// _jobIdfile_num是文件夹中的单独文件id
/// ///
/// file_num = 0; /// file_num = 0;
@ -58,6 +56,28 @@ class FileModel extends ChangeNotifier {
FileDirectory get currentDir => _isLocal ? currentLocalDir : currentRemoteDir; 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 _fileFetcher = FileFetcher();
final _jobResultListener = JobResultListener<Map<String, dynamic>>(); final _jobResultListener = JobResultListener<Map<String, dynamic>>();
@ -72,6 +92,16 @@ class FileModel extends ChangeNotifier {
notifyListeners(); 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) { tryUpdateJobProgress(Map<String, dynamic> evt) {
try { try {
int id = int.parse(evt['id']); int id = int.parse(evt['id']);
@ -79,7 +109,6 @@ class FileModel extends ChangeNotifier {
_jobProgress.fileNum = int.parse(evt['file_num']); _jobProgress.fileNum = int.parse(evt['file_num']);
_jobProgress.speed = double.parse(evt['speed']); _jobProgress.speed = double.parse(evt['speed']);
_jobProgress.finishedSize = int.parse(evt['finished_size']); _jobProgress.finishedSize = int.parse(evt['finished_size']);
debugPrint("_jobProgress update:${_jobProgress.toString()}");
notifyListeners(); notifyListeners();
} catch (e) { } catch (e) {
debugPrint("Failed to tryUpdateJobProgress,evt:${evt.toString()}"); debugPrint("Failed to tryUpdateJobProgress,evt:${evt.toString()}");
@ -107,7 +136,7 @@ class FileModel extends ChangeNotifier {
return; return;
} }
// TODO debugPrint("jobError $evt");
_selectMode = false; _selectMode = false;
_jobProgress.clear(); _jobProgress.clear();
_jobProgress.state = JobState.error; _jobProgress.state = JobState.error;
@ -120,19 +149,69 @@ class FileModel extends ChangeNotifier {
} }
onReady() { onReady() {
openDirectory(FFI.getByName("get_home_dir"), isLocal: true); _localOption = DirectoryOption(
openDirectory(FFI.ffiModel.pi.homeDir, isLocal: false); 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() { refresh() {
openDirectory(_isLocal ? _currentLocalDir.path : _currentRemoteDir.path); openDirectory(currentDir.path);
} }
openDirectory(String path, {bool? isLocal}) async { openDirectory(String path, {bool? isLocal}) async {
isLocal = isLocal ?? _isLocal; isLocal = isLocal ?? _isLocal;
final showHidden =
isLocal ? _localOption.showHidden : _remoteOption.showHidden;
final isWindows =
isLocal ? _localOption.isWindows : _remoteOption.isWindows;
try { try {
final fd = await _fileFetcher.fetchDirectory(path, isLocal); final fd = await _fileFetcher.fetchDirectory(path, isLocal, showHidden);
fd.changeSortStyle(_sortStyle); fd.format(isWindows, sort: _sortStyle);
if (isLocal) { if (isLocal) {
_currentLocalDir = fd; _currentLocalDir = fd;
} else { } else {
@ -144,9 +223,12 @@ class FileModel extends ChangeNotifier {
} }
} }
goHome() {
openDirectory(currentHome);
}
goToParentDirectory() { goToParentDirectory() {
final fd = _isLocal ? _currentLocalDir : _currentRemoteDir; openDirectory(currentDir.parent);
openDirectory(fd.parent);
} }
sendFiles(SelectedItems items) { sendFiles(SelectedItems items) {
@ -157,13 +239,17 @@ class FileModel extends ChangeNotifier {
_jobProgress.state = JobState.inProgress; _jobProgress.state = JobState.inProgress;
final toPath = final toPath =
items.isLocal! ? currentRemoteDir.path : currentLocalDir.path; 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) { items.items.forEach((from) {
_jobId++; _jobId++;
final msg = { final msg = {
"id": _jobId.toString(), "id": _jobId.toString(),
"path": from.path, "path": from.path,
"to": Path.join(toPath, from.name), "to": PathUtil.join(toPath, from.name, isWindows),
"show_hidden": "false", // TODO showHidden "show_hidden": showHidden.toString(),
"is_remote": (!(items.isLocal!)).toString() // from的位置而不是to的位置 "is_remote": (!(items.isLocal!)).toString() // from的位置而不是to的位置
}; };
FFI.setByName("send_files", jsonEncode(msg)); FFI.setByName("send_files", jsonEncode(msg));
@ -178,6 +264,8 @@ class FileModel extends ChangeNotifier {
debugPrint("Failed to removeFile ,wrong path state"); debugPrint("Failed to removeFile ,wrong path state");
return; return;
} }
final isWindows =
items.isLocal! ? _localOption.isWindows : _remoteOption.isWindows;
await Future.forEach(items.items, (Entry item) async { await Future.forEach(items.items, (Entry item) async {
_jobId++; _jobId++;
var title = ""; var title = "";
@ -191,35 +279,33 @@ class FileModel extends ChangeNotifier {
title = "这不是一个空文件夹"; title = "这不是一个空文件夹";
showLoading("正在读取..."); showLoading("正在读取...");
final fd = await _fileFetcher.fetchDirectoryRecursive( final fd = await _fileFetcher.fetchDirectoryRecursive(
_jobId, item.path, items.isLocal!); _jobId, item.path, items.isLocal!, true);
fd.format(isWindows);
EasyLoading.dismiss(); EasyLoading.dismiss();
// //
if(fd.entries.isEmpty){ if (fd.entries.isEmpty) {
final confirm = await showRemoveDialog("是否删除空文件夹",item.name,false); final confirm = await showRemoveDialog("是否删除空文件夹", item.name, false);
if(confirm == true){ if (confirm == true) {
sendRemoveEmptyDir(item.path, 0, items.isLocal!); sendRemoveEmptyDir(item.path, 0, items.isLocal!);
} }
return; return;
} }
debugPrint("removeDirAllIntent res:${fd.id}");
entries = fd.entries; entries = fd.entries;
} else { } else {
debugPrint("none : ${item.toString()}");
entries = []; entries = [];
} }
for (var i = 0; i < entries.length; i++) { for (var i = 0; i < entries.length; i++) {
final dirShow = item.isDirectory?"是否删除文件夹下的文件?\n":""; final dirShow = item.isDirectory ? "是否删除文件夹下的文件?\n" : "";
final count = entries.length>1?"${i + 1}/${entries.length}":""; final count =
entries.length > 1 ? "${i + 1}/${entries.length}" : "";
content = dirShow + "$count \n${entries[i].path}"; content = dirShow + "$count \n${entries[i].path}";
final confirm = await showRemoveDialog(title,content,item.isDirectory); final confirm =
debugPrint("已选择:$confirm"); await showRemoveDialog(title, content, item.isDirectory);
try { try {
if (confirm == true) { if (confirm == true) {
sendRemoveFile(entries[i].path, i, items.isLocal!); sendRemoveFile(entries[i].path, i, items.isLocal!);
final res = await _jobResultListener.start(); final res = await _jobResultListener.start();
debugPrint("remove got res ${res.toString()}");
// handle remove res; // handle remove res;
if (item.isDirectory && if (item.isDirectory &&
res['file_num'] == (entries.length - 1).toString()) { res['file_num'] == (entries.length - 1).toString()) {
@ -231,7 +317,6 @@ class FileModel extends ChangeNotifier {
for (var j = i + 1; j < entries.length; j++) { for (var j = i + 1; j < entries.length; j++) {
sendRemoveFile(entries[j].path, j, items.isLocal!); sendRemoveFile(entries[j].path, j, items.isLocal!);
final res = await _jobResultListener.start(); final res = await _jobResultListener.start();
debugPrint("remove got res ${res.toString()}");
if (item.isDirectory && if (item.isDirectory &&
res['file_num'] == (entries.length - 1).toString()) { res['file_num'] == (entries.length - 1).toString()) {
sendRemoveEmptyDir(item.path, i, items.isLocal!); sendRemoveEmptyDir(item.path, i, items.isLocal!);
@ -246,9 +331,10 @@ class FileModel extends ChangeNotifier {
refresh(); refresh();
} }
Future<bool?> showRemoveDialog(String title,String content,bool showCheckbox) async { Future<bool?> showRemoveDialog(
return await DialogManager.show<bool>( String title, String content, bool showCheckbox) async {
(setState, Function(bool v) close) => CustomAlertDialog( return await DialogManager.show<bool>((setState, Function(bool v) close) =>
CustomAlertDialog(
title: Row( title: Row(
children: [ children: [
Icon(Icons.warning, color: Colors.red), Icon(Icons.warning, color: Colors.red),
@ -262,9 +348,10 @@ class FileModel extends ChangeNotifier {
children: [ children: [
Text(content), Text(content),
SizedBox(height: 5), SizedBox(height: 5),
Text("此操作不可逆!",style: TextStyle(fontWeight: FontWeight.bold)), Text("此操作不可逆!",
showCheckbox? style: TextStyle(fontWeight: FontWeight.bold)),
CheckboxListTile( showCheckbox
? CheckboxListTile(
contentPadding: const EdgeInsets.all(0), contentPadding: const EdgeInsets.all(0),
dense: true, dense: true,
controlAffinity: ListTileControlAffinity.leading, controlAffinity: ListTileControlAffinity.leading,
@ -276,15 +363,18 @@ class FileModel extends ChangeNotifier {
if (v == null) return; if (v == null) return;
setState(() => removeCheckboxRemember = v); setState(() => removeCheckboxRemember = v);
}, },
):SizedBox.shrink() )
: SizedBox.shrink()
]), ]),
actions: [ actions: [
TextButton( TextButton(
style: flatButtonStyle, style: flatButtonStyle,
onPressed: () => close(true), child: Text("Yes")), onPressed: () => close(true),
child: Text("Yes")),
TextButton( TextButton(
style: flatButtonStyle, 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) { createDir(String path) {
_jobId ++; _jobId++;
final msg = { final msg = {
"id": _jobId.toString(), "id": _jobId.toString(),
"path": path, "path": path,
"is_remote": (!isLocal).toString() "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) { changeSortStyle(SortBy sort) {
_sortStyle = sort; _sortStyle = sort;
@ -327,11 +415,6 @@ class FileModel extends ChangeNotifier {
_currentRemoteDir.changeSortStyle(sort); _currentRemoteDir.changeSortStyle(sort);
notifyListeners(); notifyListeners();
} }
void clear() {
_currentLocalDir.clear();
_currentRemoteDir.clear();
}
} }
class JobResultListener<T> { class JobResultListener<T> {
@ -413,7 +496,6 @@ class FileFetcher {
} }
tryCompleteTask(String? msg, String? isLocalStr) { tryCompleteTask(String? msg, String? isLocalStr) {
debugPrint("tryCompleteTask : $msg");
if (msg == null || isLocalStr == null) return; if (msg == null || isLocalStr == null) return;
late final isLocal; late final isLocal;
late final tasks; late final tasks;
@ -444,15 +526,16 @@ class FileFetcher {
} }
} }
Future<FileDirectory> fetchDirectory(String path, bool isLocal) async { Future<FileDirectory> fetchDirectory(
debugPrint("fetch :$path"); String path, bool isLocal, bool showHidden) async {
try { try {
final msg = {"path": path, "show_hidden": showHidden.toString()};
if (isLocal) { 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)); final fd = FileDirectory.fromJson(jsonDecode(res));
return fd; return fd;
} else { } else {
FFI.setByName("read_remote_dir", path); FFI.setByName("read_remote_dir", jsonEncode(msg));
return registerReadTask(isLocal, path); return registerReadTask(isLocal, path);
} }
} catch (e) { } catch (e) {
@ -461,12 +544,13 @@ class FileFetcher {
} }
Future<FileDirectory> fetchDirectoryRecursive( Future<FileDirectory> fetchDirectoryRecursive(
int id, String path, bool isLocal) async { int id, String path, bool isLocal, bool showHidden) async {
debugPrint("fetchDirectoryRecursive id:$id , path:$path"); // TODO test Recursive is show hidden default?
try { try {
final msg = { final msg = {
"id": id.toString(), "id": id.toString(),
"path": path, "path": path,
"show_hidden": showHidden.toString(),
"is_remote": (!isLocal).toString() "is_remote": (!isLocal).toString()
}; };
FFI.setByName("read_dir_recursive", jsonEncode(msg)); FFI.setByName("read_dir_recursive", jsonEncode(msg));
@ -486,27 +570,22 @@ class FileDirectory {
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) { FileDirectory.fromJson(Map<String, dynamic> json) {
id = json['id']; id = json['id'];
path = json['path']; path = json['path'];
if (json['entries'] != null) {
entries = <Entry>[];
json['entries'].forEach((v) { json['entries'].forEach((v) {
entries.add(new Entry.fromJsonWithPath(v, path)); 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);
}
} }
changeSortStyle(SortBy sort) { changeSortStyle(SortBy sort) {
@ -529,12 +608,11 @@ class Entry {
Entry(); Entry();
Entry.fromJsonWithPath(Map<String, dynamic> json, String parent) { Entry.fromJson(Map<String, dynamic> json) {
entryType = json['entry_type']; entryType = json['entry_type'];
modifiedTime = json['modified_time']; modifiedTime = json['modified_time'];
name = json['name']; name = json['name'];
size = json['size']; size = json['size'];
path = Path.join(parent, name);
} }
bool get isFile => entryType > 3; bool get isFile => entryType > 3;
@ -571,6 +649,30 @@ class _PathStat {
_PathStat(this.path, this.dateTime); _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 // code from file_manager pkg after edit
List<Entry> _sortList(List<Entry> list, SortBy sortType) { List<Entry> _sortList(List<Entry> list, SortBy sortType) {
if (sortType == SortBy.name) { if (sortType == SortBy.name) {

View File

@ -4,7 +4,6 @@ import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:flutter_hbb/models/file_model.dart'; import 'package:flutter_hbb/models/file_model.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:flutter_breadcrumb/flutter_breadcrumb.dart'; import 'package:flutter_breadcrumb/flutter_breadcrumb.dart';
import 'package:path/path.dart' as Path;
import '../common.dart'; import '../common.dart';
import '../models/model.dart'; import '../models/model.dart';
@ -38,7 +37,7 @@ class _FileManagerPageState extends State<FileManagerPage> {
@override @override
void dispose() { void dispose() {
model.clear(); model.onClose();
_interval?.cancel(); _interval?.cancel();
FFI.close(); FFI.close();
EasyLoading.dismiss(); EasyLoading.dismiss();
@ -97,28 +96,16 @@ class _FileManagerPageState extends State<FileManagerPage> {
itemCount: entries.length + 1, itemCount: entries.length + 1,
itemBuilder: (context, index) { itemBuilder: (context, index) {
if (index >= entries.length) { if (index >= entries.length) {
//
//
// 使 bottomSheet
return listTail(); return listTail();
} }
var selected = false; var selected = false;
if (model.selectMode) { if (model.selectMode) {
selected = _selectedItems.contains(entries[index]); selected = _selectedItems.contains(entries[index]);
} }
var sizeStr = "";
if (entries[index].isFile) { final sizeStr = entries[index].isFile
final size = entries[index].size; ? readableFileSize(entries[index].size.toDouble())
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";
}
}
return Card( return Card(
child: ListTile( child: ListTile(
leading: Icon( leading: Icon(
@ -248,8 +235,21 @@ class _FileManagerPageState extends State<FileManagerPage> {
children: [ children: [
Expanded( Expanded(
child: BreadCrumb( child: BreadCrumb(
items: getPathBreadCrumbItems(() => debugPrint("pressed home"), items: getPathBreadCrumbItems(() => model.goHome(), (list) {
(e) => debugPrint("pressed url:$e")), 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), divider: Icon(Icons.chevron_right),
overflow: ScrollableOverflow(controller: _breadCrumbScroller), overflow: ScrollableOverflow(controller: _breadCrumbScroller),
)), )),
@ -261,8 +261,8 @@ class _FileManagerPageState extends State<FileManagerPage> {
itemBuilder: (context) { itemBuilder: (context) {
return SortBy.values return SortBy.values
.map((e) => PopupMenuItem( .map((e) => PopupMenuItem(
child: child: Text(
Text(translate(e.toString().split(".").last)), e.toString().split(".").last.toUpperCase()),
value: e, value: e,
)) ))
.toList(); .toList();
@ -274,24 +274,45 @@ class _FileManagerPageState extends State<FileManagerPage> {
return [ return [
PopupMenuItem( PopupMenuItem(
child: Row( child: Row(
children: [Icon(Icons.refresh), Text("刷新")], children: [
Icon(Icons.refresh),
SizedBox(width: 5),
Text("刷新")
],
), ),
value: "refresh", value: "refresh",
), ),
PopupMenuItem( PopupMenuItem(
child: Row( child: Row(
children: [Icon(Icons.check), Text("多选")], children: [
Icon(Icons.check),
SizedBox(width: 5),
Text("多选")
],
), ),
value: "select", value: "select",
), ),
PopupMenuItem( PopupMenuItem(
child: Row( child: Row(
children: [ children: [
Icon(Icons.folder), Icon(Icons.folder_outlined),
SizedBox(width: 5),
Text(translate("Create Folder")) Text(translate("Create Folder"))
], ],
), ),
value: "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, style: flatButtonStyle,
onPressed: () { onPressed: () {
if (name.value.text.isNotEmpty) { 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(); close();
} }
}, },
@ -332,6 +356,8 @@ class _FileManagerPageState extends State<FileManagerPage> {
onPressed: () => close(false), onPressed: () => close(false),
child: Text(translate("Cancel"))) child: Text(translate("Cancel")))
])); ]));
} else if (v == "hidden") {
model.toggleShowHidden();
} }
}), }),
], ],
@ -349,7 +375,20 @@ class _FileManagerPageState extends State<FileManagerPage> {
} }
Widget listTail() { 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() { Widget? bottomSheet() {
@ -402,8 +441,7 @@ class _FileManagerPageState extends State<FileManagerPage> {
return BottomSheetBody( return BottomSheetBody(
leading: CircularProgressIndicator(), leading: CircularProgressIndicator(),
title: "正在发送文件...", title: "正在发送文件...",
text: text: "速度: ${readableFileSize(model.jobProgress.speed)}/s",
"速度: ${(model.jobProgress.speed / 1024).toStringAsFixed(2)} kb/s",
onCanceled: null, onCanceled: null,
); );
case JobState.done: case JobState.done:
@ -427,10 +465,9 @@ class _FileManagerPageState extends State<FileManagerPage> {
} }
List<BreadCrumbItem> getPathBreadCrumbItems( List<BreadCrumbItem> getPathBreadCrumbItems(
void Function() onHome, void Function(String) onPressed) { void Function() onHome, void Function(List<String>) onPressed) {
final path = model.currentDir.path; final path = model.currentShortPath;
final list = Path.split(path); final list = PathUtil.split(path, model.currentIsWindows);
list.remove('/');
final breadCrumbList = [ final breadCrumbList = [
BreadCrumbItem( BreadCrumbItem(
content: IconButton( content: IconButton(
@ -438,12 +475,12 @@ class _FileManagerPageState extends State<FileManagerPage> {
onPressed: onHome, onPressed: onHome,
)) ))
]; ];
breadCrumbList.addAll(list.map((e) => BreadCrumbItem( breadCrumbList.addAll(list.asMap().entries.map((e) => BreadCrumbItem(
content: TextButton( content: TextButton(
child: Text(e), child: Text(e.value),
style: style:
ButtonStyle(minimumSize: MaterialStateProperty.all(Size(0, 0))), ButtonStyle(minimumSize: MaterialStateProperty.all(Size(0, 0))),
onPressed: () => onPressed(e))))); onPressed: () => onPressed(list.sublist(0, e.key + 1))))));
return breadCrumbList; return breadCrumbList;
} }
} }