rustdesk/lib/pages/file_manager_page.dart

428 lines
13 KiB
Dart
Raw Normal View History

2022-03-07 22:54:34 +08:00
import 'dart:async';
import 'package:flutter/material.dart';
2022-03-07 22:54:34 +08:00
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:flutter_hbb/models/file_model.dart';
import 'package:provider/provider.dart';
2022-03-09 17:07:24 +08:00
import 'package:flutter_breadcrumb/flutter_breadcrumb.dart';
2022-03-11 01:28:13 +08:00
import 'package:path/path.dart' as Path;
2022-03-07 22:54:34 +08:00
import '../common.dart';
import '../models/model.dart';
import '../widgets/dialog.dart';
2022-03-11 01:28:13 +08:00
2022-03-07 22:54:34 +08:00
class FileManagerPage extends StatefulWidget {
FileManagerPage({Key? key, required this.id}) : super(key: key);
final String id;
@override
State<StatefulWidget> createState() => _FileManagerPageState();
}
class _FileManagerPageState extends State<FileManagerPage> {
2022-03-11 01:28:13 +08:00
final model = FFI.fileModel;
final _selectedItems = SelectedItems();
2022-03-07 22:54:34 +08:00
Timer? _interval;
Timer? _timer;
var _reconnects = 1;
2022-03-11 01:28:13 +08:00
final _breadCrumbScroller = ScrollController();
2022-03-07 22:54:34 +08:00
@override
void initState() {
super.initState();
showLoading(translate('Connecting...'));
FFI.connect(widget.id, isFileTransfer: true);
2022-03-11 01:28:13 +08:00
final res = FFI.getByName("read_dir", FFI.getByName("get_home_dir"));
debugPrint("read_dir local :$res");
model.tryUpdateDir(res, true);
2022-03-07 22:54:34 +08:00
_interval = Timer.periodic(Duration(milliseconds: 30),
(timer) => FFI.ffiModel.update(widget.id, context, handleMsgBox));
}
@override
void dispose() {
2022-03-11 01:28:13 +08:00
model.clear();
2022-03-07 22:54:34 +08:00
_interval?.cancel();
FFI.close();
EasyLoading.dismiss();
super.dispose();
}
@override
2022-03-11 01:28:13 +08:00
Widget build(BuildContext context) => Consumer<FileModel>(builder: (_context, _model, _child) {
return WillPopScope(
onWillPop: () async {
if (model.selectMode) {
model.toggleSelectMode();
} else {
goBack();
}
return false;
},
2022-03-07 22:54:34 +08:00
child: Scaffold(
2022-03-09 17:07:24 +08:00
backgroundColor: MyTheme.grayBg,
2022-03-07 22:54:34 +08:00
appBar: AppBar(
leading: Row(children: [
IconButton(icon: Icon(Icons.arrow_back), onPressed: goBack),
IconButton(icon: Icon(Icons.close), onPressed: clientClose),
]),
leadingWidth: 200,
centerTitle: true,
2022-03-11 01:28:13 +08:00
title: Text(translate(model.isLocal ? "Local" : "Remote")),
2022-03-07 22:54:34 +08:00
actions: [
IconButton(
2022-03-09 22:43:05 +08:00
icon: Icon(Icons.change_circle),
2022-03-11 01:28:13 +08:00
onPressed: ()=> model.togglePage(),
2022-03-09 22:43:05 +08:00
)
2022-03-07 22:54:34 +08:00
],
),
2022-03-09 17:07:24 +08:00
body: body(),
bottomSheet: bottomSheet(),
2022-03-07 22:54:34 +08:00
));
2022-03-11 01:28:13 +08:00
});
bool needShowCheckBox(){
if(!model.selectMode){
return false;
}
return !_selectedItems.isOtherPage(model.isLocal);
2022-03-07 22:54:34 +08:00
}
2022-03-11 01:28:13 +08:00
Widget body() {
final isLocal = model.isLocal;
final fd = model.currentDir;
2022-03-09 22:43:05 +08:00
final entries = fd.entries;
return Column(children: [
headTools(),
Expanded(
child: ListView.builder(
itemCount: entries.length + 1,
itemBuilder: (context, index) {
if (index >= entries.length) {
// 添加尾部信息 文件统计信息等
// 添加快速返回上部
// 使用 bottomSheet 提示以选择的文件数量 点击后展开查看更多
return listTail();
}
2022-03-11 01:28:13 +08:00
final path = Path.join(fd.path, entries[index].name);
2022-03-09 22:43:05 +08:00
var selected = false;
2022-03-11 01:28:13 +08:00
if (model.selectMode) {
selected = _selectedItems.contains(path);
2022-03-09 22:43:05 +08:00
}
return Card(
child: ListTile(
2022-03-11 01:28:13 +08:00
leading: Icon(entries[index].isFile?Icons.feed_outlined:Icons.folder,
size: 40),
2022-03-09 22:43:05 +08:00
title: Text(entries[index].name),
2022-03-11 01:28:13 +08:00
selected: selected,
// subtitle: Text(entries[index].lastModified().toString()),
trailing: needShowCheckBox()
2022-03-09 22:43:05 +08:00
? Checkbox(
value: selected,
onChanged: (v) {
if (v == null) return;
if (v && !selected) {
2022-03-11 01:28:13 +08:00
_selectedItems.add(isLocal,path);
2022-03-09 22:43:05 +08:00
} else if (!v && selected) {
_selectedItems.remove(path);
}
2022-03-11 01:28:13 +08:00
setState(() {});
2022-03-09 22:43:05 +08:00
})
: null,
onTap: () {
2022-03-11 01:28:13 +08:00
if (model.selectMode && !_selectedItems.isOtherPage(isLocal)) {
if (selected) {
_selectedItems.remove(path);
} else {
_selectedItems.add(isLocal,path);
}
setState(() {});
return;
}
2022-03-09 22:43:05 +08:00
if (entries[index].isDirectory) {
2022-03-11 01:28:13 +08:00
model.openDirectory(path);
breadCrumbScrollToEnd();
2022-03-09 22:43:05 +08:00
} else {
// Perform file-related tasks.
}
},
onLongPress: () {
2022-03-11 01:28:13 +08:00
_selectedItems.clear();
model.toggleSelectMode();
if (model.selectMode) {
_selectedItems.add(isLocal,path);
}
setState(() {});
2022-03-09 22:43:05 +08:00
},
),
);
},
))
]);
2022-03-11 01:28:13 +08:00
}
2022-03-09 17:07:24 +08:00
2022-03-07 22:54:34 +08:00
goBack() {
2022-03-11 01:28:13 +08:00
model.goToParentDirectory();
2022-03-07 22:54:34 +08:00
}
void handleMsgBox(Map<String, dynamic> evt, String id) {
var type = evt['type'];
var title = evt['title'];
var text = evt['text'];
if (type == 're-input-password') {
wrongPasswordDialog(id);
} else if (type == 'input-password') {
enterPasswordDialog(id);
} else {
var hasRetry = evt['hasRetry'] == 'true';
print(evt);
showMsgBox(type, title, text, hasRetry);
}
}
void showMsgBox(String type, String title, String text, bool hasRetry) {
msgBox(type, title, text);
if (hasRetry) {
_timer?.cancel();
_timer = Timer(Duration(seconds: _reconnects), () {
FFI.reconnect();
showLoading(translate('Connecting...'));
});
_reconnects *= 2;
} else {
_reconnects = 1;
}
}
2022-03-09 17:07:24 +08:00
2022-03-11 01:28:13 +08:00
breadCrumbScrollToEnd() {
Future.delayed(Duration(milliseconds: 200), () {
_breadCrumbScroller.animateTo(
_breadCrumbScroller.position.maxScrollExtent,
duration: Duration(milliseconds: 200),
curve: Curves.fastLinearToSlowEaseIn);
});
}
2022-03-09 17:07:24 +08:00
Widget headTools() => Container(
child: Row(
children: [
Expanded(
child: BreadCrumb(
items: getPathBreadCrumbItems(() => debugPrint("pressed home"),
(e) => debugPrint("pressed url:$e")),
divider: Icon(Icons.chevron_right),
2022-03-11 01:28:13 +08:00
overflow: ScrollableOverflow(controller: _breadCrumbScroller),
2022-03-09 17:07:24 +08:00
)),
Row(
children: [
// IconButton(onPressed: () {}, icon: Icon(Icons.sort)),
PopupMenuButton<SortBy>(
2022-03-09 22:43:05 +08:00
icon: Icon(Icons.sort),
itemBuilder: (context) {
return SortBy.values
.map((e) => PopupMenuItem(
child:
Text(translate(e.toString().split(".").last)),
value: e,
))
.toList();
},
2022-03-11 01:28:13 +08:00
onSelected: model.changeSortStyle),
2022-03-09 22:43:05 +08:00
PopupMenuButton<String>(
icon: Icon(Icons.more_vert),
2022-03-09 17:07:24 +08:00
itemBuilder: (context) {
2022-03-09 22:43:05 +08:00
return [
PopupMenuItem(
child: Row(
2022-03-11 01:28:13 +08:00
children: [Icon(Icons.refresh), Text("刷新")],
2022-03-09 22:43:05 +08:00
),
value: "refresh",
2022-03-11 01:28:13 +08:00
),
PopupMenuItem(
child: Row(
children: [Icon(Icons.check), Text("多选")],
),
value: "select",
2022-03-09 22:43:05 +08:00
)
];
2022-03-09 17:07:24 +08:00
},
2022-03-11 01:28:13 +08:00
onSelected: (v) {
if (v == "refresh") {
model.refresh();
} else if (v == "select") {
_selectedItems.clear();
model.toggleSelectMode();
2022-03-09 22:43:05 +08:00
}
}),
2022-03-09 17:07:24 +08:00
],
)
],
));
Widget emptyPage() {
return Column(
children: [
headTools(),
Expanded(child: Center(child: Text("Empty Directory")))
],
);
}
Widget listTail() {
return SizedBox(height: 100);
}
2022-03-11 01:28:13 +08:00
/// 有几种状态
/// 选择模式 localPage
/// 准备复制模式 otherPage
/// 正在复制模式 动态数字和显示速度
/// 粘贴完成模式
2022-03-09 17:07:24 +08:00
BottomSheet? bottomSheet() {
2022-03-11 01:28:13 +08:00
if (!model.selectMode) return null;
2022-03-09 17:07:24 +08:00
return BottomSheet(
backgroundColor: MyTheme.grayBg,
enableDrag: false,
onClosing: () {
debugPrint("BottomSheet close");
},
builder: (context) {
2022-03-11 01:28:13 +08:00
final isOtherPage = _selectedItems.isOtherPage(model.isLocal);
2022-03-09 17:07:24 +08:00
return Container(
height: 65,
alignment: Alignment.centerLeft,
decoration: BoxDecoration(
color: MyTheme.accent50,
borderRadius: BorderRadius.vertical(top: Radius.circular(10))),
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 15),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
2022-03-11 01:28:13 +08:00
// 做一个bottomSheet类框架 不同状态下显示不同的内容
2022-03-09 17:07:24 +08:00
Row(
children: [
2022-03-11 01:28:13 +08:00
CircularProgressIndicator(),
isOtherPage?Icon(Icons.input):Icon(Icons.check),
SizedBox(width: 16),
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(isOtherPage?'粘贴到这里?':'已选择',style: TextStyle(fontSize: 18)),
Text("${_selectedItems.length} 个文件 [${model.isLocal?'本地':'远程'}]",style: TextStyle(fontSize: 14,color: MyTheme.grayBg))
],
)
2022-03-09 17:07:24 +08:00
],
),
Row(
children: [
2022-03-11 01:28:13 +08:00
(_selectedItems.length>0 && isOtherPage)? IconButton(
2022-03-09 17:07:24 +08:00
icon: Icon(Icons.paste),
2022-03-11 01:28:13 +08:00
onPressed:() {
2022-03-09 22:43:05 +08:00
debugPrint("paste");
2022-03-11 01:28:13 +08:00
// TODO 
model.sendFiles(
_selectedItems.items.first,
model.currentRemoteDir.path +
'/' +
_selectedItems.items.first.split('/').last,
false,
false);
// unused set callback
// _fileModel.set
2022-03-09 22:43:05 +08:00
},
2022-03-11 01:28:13 +08:00
):IconButton(
2022-03-09 17:07:24 +08:00
icon: Icon(Icons.delete_forever),
onPressed: () {},
),
IconButton(
icon: Icon(Icons.cancel_outlined),
onPressed: () {
2022-03-11 01:28:13 +08:00
model.toggleSelectMode();
2022-03-09 17:07:24 +08:00
},
),
],
)
],
),
),
);
});
}
List<BreadCrumbItem> getPathBreadCrumbItems(
void Function() onHome, void Function(String) onPressed) {
2022-03-11 01:28:13 +08:00
final path = model.currentDir.path;
final list = path.trim().split('/'); // TODO use Path
2022-03-09 17:07:24 +08:00
list.remove("");
final breadCrumbList = [
BreadCrumbItem(
content: IconButton(
icon: Icon(Icons.home_filled),
onPressed: onHome,
))
];
breadCrumbList.addAll(list.map((e) => BreadCrumbItem(
content: TextButton(
child: Text(e),
style:
ButtonStyle(minimumSize: MaterialStateProperty.all(Size(0, 0))),
onPressed: () => onPressed(e)))));
return breadCrumbList;
}
2022-03-11 01:28:13 +08:00
}
class SelectedItems {
bool? _isLocal;
final List<String> _items = [];
List<String> get items => _items;
int get length => _items.length;
// bool get isEmpty => _items.length == 0;
add(bool isLocal, String path) {
if (_isLocal == null) {
_isLocal = isLocal;
}
if (_isLocal != null && _isLocal != isLocal) {
return;
}
if (!_items.contains(path)) {
_items.add(path);
}
}
2022-03-09 17:07:24 +08:00
2022-03-11 01:28:13 +08:00
bool contains(String path) {
return _items.contains(path);
}
remove(String path) {
_items.remove(path);
if (_items.length == 0) {
_isLocal = null;
}
}
bool isOtherPage(bool currentIsLocal) {
if (_isLocal == null) {
return false;
} else {
return _isLocal != currentIsLocal;
}
}
clear() {
_items.clear();
_isLocal = null;
}
}