Merge pull request #876 from Kingtous/flutter_desktop

add: file transfer dual logic with bridge
This commit is contained in:
RustDesk 2022-06-27 16:53:26 +08:00 committed by GitHub
commit 1fc8957c1c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 220 additions and 245 deletions

View File

@ -11,7 +11,6 @@ import 'package:provider/provider.dart';
import 'package:wakelock/wakelock.dart';
import '../../common.dart';
import '../../mobile/widgets/dialog.dart';
import '../../models/model.dart';
class FileManagerPage extends StatefulWidget {
@ -25,7 +24,8 @@ class FileManagerPage extends StatefulWidget {
class _FileManagerPageState extends State<FileManagerPage>
with AutomaticKeepAliveClientMixin {
final _selectedItems = SelectedItems();
final _breadCrumbScroller = ScrollController();
final _breadCrumbLocalScroller = ScrollController();
final _breadCrumbRemoteScroller = ScrollController();
/// FFI with name file_transfer_id
FFI get _ffi => ffi('ft_${widget.id}');
@ -66,135 +66,11 @@ class _FileManagerPageState extends State<FileManagerPage>
onWillPop: () async {
if (model.selectMode) {
model.toggleSelectMode();
} else {
goBack();
}
return false;
},
child: Scaffold(
backgroundColor: MyTheme.grayBg,
appBar: AppBar(
leading: Row(children: [
IconButton(icon: Icon(Icons.close), onPressed: clientClose),
]),
centerTitle: true,
// title: ToggleSwitch(
// initialLabelIndex: model.isLocal ? 0 : 1,
// activeBgColor: [MyTheme.idColor],
// inactiveBgColor: MyTheme.grayBg,
// inactiveFgColor: Colors.black54,
// totalSwitches: 2,
// minWidth: 100,
// fontSize: 15,
// iconSize: 18,
// labels: [translate("Local"), translate("Remote")],
// icons: [Icons.phone_android_sharp, Icons.screen_share],
// onToggle: (index) {
// final current = model.isLocal ? 0 : 1;
// if (index != current) {
// model.togglePage();
// }
// },
// ),
actions: [
PopupMenuButton<String>(
icon: Icon(Icons.more_vert),
itemBuilder: (context) {
return [
PopupMenuItem(
child: Row(
children: [
Icon(Icons.refresh, color: Colors.black),
SizedBox(width: 5),
Text(translate("Refresh File"))
],
),
value: "refresh",
),
PopupMenuItem(
child: Row(
children: [
Icon(Icons.check, color: Colors.black),
SizedBox(width: 5),
Text(translate("Multi Select"))
],
),
value: "select",
),
PopupMenuItem(
child: Row(
children: [
Icon(Icons.folder_outlined,
color: Colors.black),
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,
color: Colors.black),
SizedBox(width: 5),
Text(translate("Show Hidden Files"))
],
),
value: "hidden",
)
];
},
onSelected: (v) {
if (v == "refresh") {
model.refresh();
} else if (v == "select") {
_selectedItems.clear();
model.toggleSelectMode();
} else if (v == "folder") {
final name = TextEditingController();
DialogManager.show((setState, close) =>
CustomAlertDialog(
title: Text(translate("Create Folder")),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextFormField(
decoration: InputDecoration(
labelText: translate(
"Please enter the folder name"),
),
controller: name,
),
],
),
actions: [
TextButton(
style: flatButtonStyle,
onPressed: () => close(false),
child: Text(translate("Cancel"))),
ElevatedButton(
style: flatButtonStyle,
onPressed: () {
if (name.value.text.isNotEmpty) {
model.createDir(PathUtil.join(
model.currentDir.path,
name.value.text,
model.currentIsWindows));
close();
}
},
child: Text(translate("OK")))
]));
} else if (v == "hidden") {
model.toggleShowHidden();
}
}),
],
),
body: Row(
children: [
Flexible(flex: 1, child: body(isLocal: true)),
@ -213,11 +89,110 @@ class _FileManagerPageState extends State<FileManagerPage>
return !_selectedItems.isOtherPage(model.isLocal);
}
Widget menu({bool isLocal = false}) {
return PopupMenuButton<String>(
icon: Icon(Icons.more_vert),
itemBuilder: (context) {
return [
PopupMenuItem(
child: Row(
children: [
Icon(Icons.refresh, color: Colors.black),
SizedBox(width: 5),
Text(translate("Refresh File"))
],
),
value: "refresh",
),
PopupMenuItem(
child: Row(
children: [
Icon(Icons.check, color: Colors.black),
SizedBox(width: 5),
Text(translate("Multi Select"))
],
),
value: "select",
),
PopupMenuItem(
child: Row(
children: [
Icon(Icons.folder_outlined,
color: Colors.black),
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,
color: Colors.black),
SizedBox(width: 5),
Text(translate("Show Hidden Files"))
],
),
value: "hidden",
)
];
},
onSelected: (v) {
if (v == "refresh") {
model.refresh();
} else if (v == "select") {
_selectedItems.clear();
model.toggleSelectMode();
} else if (v == "folder") {
final name = TextEditingController();
DialogManager.show((setState, close) =>
CustomAlertDialog(
title: Text(translate("Create Folder")),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextFormField(
decoration: InputDecoration(
labelText: translate(
"Please enter the folder name"),
),
controller: name,
),
],
),
actions: [
TextButton(
style: flatButtonStyle,
onPressed: () => close(false),
child: Text(translate("Cancel"))),
ElevatedButton(
style: flatButtonStyle,
onPressed: () {
if (name.value.text.isNotEmpty) {
model.createDir(PathUtil.join(
model.currentDir.path,
name.value.text,
model.currentIsWindows));
close();
}
},
child: Text(translate("OK")))
]));
} else if (v == "hidden") {
model.toggleShowHidden(local: isLocal);
}
});
}
Widget body({bool isLocal = false}) {
final fd = isLocal ? model.currentLocalDir : model.currentRemoteDir;
final entries = fd.entries;
return Column(children: [
headTools(),
headTools(isLocal),
Expanded(
child: ListView.builder(
itemCount: entries.length + 1,
@ -301,8 +276,8 @@ class _FileManagerPageState extends State<FileManagerPage>
return;
}
if (entries[index].isDirectory) {
model.openDirectory(entries[index].path);
breadCrumbScrollToEnd();
model.openDirectory(entries[index].path, isLocal: isLocal);
breadCrumbScrollToEnd(isLocal);
} else {
// Perform file-related tasks.
}
@ -322,20 +297,21 @@ class _FileManagerPageState extends State<FileManagerPage>
]);
}
goBack() {
model.goToParentDirectory();
goBack({bool? isLocal}) {
model.goToParentDirectory(isLocal: isLocal);
}
breadCrumbScrollToEnd() {
breadCrumbScrollToEnd(bool isLocal) {
final controller = isLocal ? _breadCrumbLocalScroller : _breadCrumbRemoteScroller;
Future.delayed(Duration(milliseconds: 200), () {
_breadCrumbScroller.animateTo(
_breadCrumbScroller.position.maxScrollExtent,
controller.animateTo(
controller.position.maxScrollExtent,
duration: Duration(milliseconds: 200),
curve: Curves.fastLinearToSlowEaseIn);
});
}
Widget headTools() => Container(
Widget headTools(bool isLocal) => Container(
child: Row(
children: [
Expanded(
@ -353,16 +329,18 @@ class _FileManagerPageState extends State<FileManagerPage>
path = PathUtil.join(path, item, model.currentIsWindows);
}
}
model.openDirectory(path);
}),
model.openDirectory(path, isLocal: isLocal);
}, isLocal),
divider: Icon(Icons.chevron_right),
overflow: ScrollableOverflow(controller: _breadCrumbScroller),
overflow: ScrollableOverflow(controller: isLocal ? _breadCrumbLocalScroller : _breadCrumbRemoteScroller),
)),
Row(
children: [
IconButton(
icon: Icon(Icons.arrow_upward),
onPressed: goBack,
onPressed: () {
goBack(isLocal: isLocal);
},
),
PopupMenuButton<SortBy>(
icon: Icon(Icons.sort),
@ -375,7 +353,10 @@ class _FileManagerPageState extends State<FileManagerPage>
))
.toList();
},
onSelected: model.changeSortStyle),
onSelected: (sort) {
model.changeSortStyle(sort, isLocal: isLocal);
}),
menu(isLocal: isLocal)
],
)
],
@ -486,8 +467,8 @@ class _FileManagerPageState extends State<FileManagerPage>
}
List<BreadCrumbItem> getPathBreadCrumbItems(
void Function() onHome, void Function(List<String>) onPressed) {
final path = model.currentShortPath;
void Function() onHome, void Function(List<String>) onPressed, bool isLocal) {
final path = model.shortPath(isLocal);
final list = PathUtil.split(path, model.currentIsWindows);
final breadCrumbList = [
BreadCrumbItem(

View File

@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_hbb/desktop/pages/file_manager_page.dart';
import 'package:flutter_hbb/desktop/widgets/titlebar_widget.dart';
import 'package:flutter_hbb/utils/multi_window_manager.dart';
import 'package:get/get.dart';
/// File Transfer for multi tabs
class FileManagerTabPage extends StatefulWidget {
@ -21,7 +22,7 @@ class _FileManagerTabPageState extends State<FileManagerTabPage>
// refactor List<int> when using multi-tab
// this singleton is only for test
List<String> connectionIds = List.empty(growable: true);
var initialIndex = 0;
var initialIndex = 0.obs;
_FileManagerTabPageState(Map<String, dynamic> params) {
if (params['id'] != null) {
@ -37,21 +38,15 @@ class _FileManagerTabPageState extends State<FileManagerTabPage>
"call ${call.method} with args ${call.arguments} from window ${fromWindowId}");
// for simplify, just replace connectionId
if (call.method == "new_file_transfer") {
setState(() {
final args = jsonDecode(call.arguments);
final id = args['id'];
final indexOf = connectionIds.indexOf(id);
if (indexOf >= 0) {
setState(() {
initialIndex = indexOf;
});
} else {
connectionIds.add(id);
setState(() {
initialIndex = connectionIds.length - 1;
});
}
});
final args = jsonDecode(call.arguments);
final id = args['id'];
final indexOf = connectionIds.indexOf(id);
if (indexOf >= 0) {
initialIndex.value = indexOf;
} else {
connectionIds.add(id);
initialIndex.value = connectionIds.length - 1;
}
}
});
}
@ -59,51 +54,53 @@ class _FileManagerTabPageState extends State<FileManagerTabPage>
@override
Widget build(BuildContext context) {
return Scaffold(
body: DefaultTabController(
initialIndex: initialIndex,
length: connectionIds.length,
animationDuration: Duration.zero,
child: Column(
children: [
DesktopTitleBar(
child: TabBar(
isScrollable: true,
labelColor: Colors.white,
physics: NeverScrollableScrollPhysics(),
indicatorColor: Colors.white,
tabs: connectionIds
.map((e) => Tab(
child: Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(e),
SizedBox(
width: 4,
),
InkWell(
onTap: () {
onRemoveId(e);
},
child: Icon(
Icons.highlight_remove,
size: 20,
))
],
),
))
.toList()),
),
Expanded(
child: TabBarView(
children: connectionIds
.map((e) => Container(
child: FileManagerPage(
key: ValueKey(e),
id: e))) //RemotePage(key: ValueKey(e), id: e))
.toList()),
)
],
body: Obx(
()=> DefaultTabController(
initialIndex: initialIndex.value,
length: connectionIds.length,
animationDuration: Duration.zero,
child: Column(
children: [
DesktopTitleBar(
child: TabBar(
isScrollable: true,
labelColor: Colors.white,
physics: NeverScrollableScrollPhysics(),
indicatorColor: Colors.white,
tabs: connectionIds
.map((e) => Tab(
child: Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(e),
SizedBox(
width: 4,
),
InkWell(
onTap: () {
onRemoveId(e);
},
child: Icon(
Icons.highlight_remove,
size: 20,
))
],
),
))
.toList()),
),
Expanded(
child: TabBarView(
children: connectionIds
.map((e) => Container(
child: FileManagerPage(
key: ValueKey(e),
id: e))) //RemotePage(key: ValueKey(e), id: e))
.toList()),
)
],
),
),
),
);
@ -114,9 +111,7 @@ class _FileManagerTabPageState extends State<FileManagerTabPage>
if (indexOf == -1) {
return;
}
setState(() {
connectionIds.removeAt(indexOf);
initialIndex = max(0, initialIndex - 1);
});
connectionIds.removeAt(indexOf);
initialIndex.value = max(0, initialIndex.value - 1);
}
}

View File

@ -60,6 +60,21 @@ class FileModel extends ChangeNotifier {
}
}
String shortPath(bool isLocal) {
final dir = isLocal ? currentLocalDir : currentRemoteDir;
if (dir.path.startsWith(currentHome)) {
var path = dir.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 dir.path.replaceFirst(currentHome, "");
}
}
bool get currentShowHidden =>
_isLocal ? _localOption.showHidden : _remoteOption.showHidden;
@ -265,9 +280,9 @@ class FileModel extends ChangeNotifier {
openDirectory(currentHome);
}
goToParentDirectory() {
goToParentDirectory({bool? isLocal}) {
final parent = PathUtil.dirname(currentDir.path, currentIsWindows);
openDirectory(parent);
openDirectory(parent, isLocal: isLocal);
}
sendFiles(SelectedItems items) {
@ -282,17 +297,10 @@ class FileModel extends ChangeNotifier {
items.isLocal! ? _localOption.isWindows : _remoteOption.isWindows;
final showHidden =
items.isLocal! ? _localOption.showHidden : _remoteOption.showHidden;
items.items.forEach((from) {
items.items.forEach((from) async {
_jobId++;
final msg = {
"id": _jobId.toString(),
"path": from.path,
"to": PathUtil.join(toPath, from.name, isWindows),
"file_num": "0",
"show_hidden": showHidden.toString(),
"is_remote": (!(items.isLocal!)).toString()
};
_ffi.target?.setByName("send_files", jsonEncode(msg));
await _ffi.target?.bind.sessionSendFiles(id: '${_ffi.target?.getId()}', actId: _jobId, path: from.path, to: PathUtil.join(toPath, from.name, isWindows)
,fileNum: 0, includeHidden: showHidden, isRemote: !(items.isLocal!));
});
}
@ -485,43 +493,34 @@ class FileModel extends ChangeNotifier {
}
sendRemoveFile(String path, int fileNum, bool isLocal) {
final msg = {
"id": _jobId.toString(),
"path": path,
"file_num": fileNum.toString(),
"is_remote": (!(isLocal)).toString()
};
_ffi.target?.setByName("remove_file", jsonEncode(msg));
_ffi.target?.bind.sessionRemoveFile(id: '${_ffi.target?.getId()}', actId: _jobId, path: path, isRemote: !isLocal, fileNum: fileNum);
}
sendRemoveEmptyDir(String path, int fileNum, bool isLocal) {
final msg = {
"id": _jobId.toString(),
"path": path,
"is_remote": (!isLocal).toString()
};
_ffi.target?.setByName("remove_all_empty_dirs", jsonEncode(msg));
_ffi.target?.bind.sessionRemoveAllEmptyDirs(id: '${_ffi.target?.getId()}', actId: _jobId, path: path, isRemote: !isLocal);
}
createDir(String path) {
createDir(String path) async {
_jobId++;
final msg = {
"id": _jobId.toString(),
"path": path,
"is_remote": (!isLocal).toString()
};
_ffi.target?.setByName("create_dir", jsonEncode(msg));
_ffi.target?.bind.sessionCreateDir(id: '${_ffi.target?.getId()}', actId: _jobId, path: path, isRemote: !isLocal);
}
cancelJob(int id) {
_ffi.target?.setByName("cancel_job", id.toString());
cancelJob(int id) async {
_ffi.target?.bind.sessionCancelJob(id: '${_ffi.target?.getId()}', actId: id);
jobReset();
}
changeSortStyle(SortBy sort) {
changeSortStyle(SortBy sort, {bool? isLocal}) {
_sortStyle = sort;
_currentLocalDir.changeSortStyle(sort);
_currentRemoteDir.changeSortStyle(sort);
if (isLocal == null) {
// compatible for mobile logic
_currentLocalDir.changeSortStyle(sort);
_currentRemoteDir.changeSortStyle(sort);
} else if (isLocal) {
_currentLocalDir.changeSortStyle(sort);
} else {
_currentRemoteDir.changeSortStyle(sort);
}
notifyListeners();
}

View File

@ -101,7 +101,7 @@ class RustDeskMultiWindowManager {
case WindowType.RemoteDesktop:
return _remoteDesktopWindowId;
case WindowType.FileTransfer:
break;
return _fileTransferWindowId;
case WindowType.PortForward:
break;
case WindowType.Unknown: