Merge pull request #876 from Kingtous/flutter_desktop
add: file transfer dual logic with bridge
This commit is contained in:
		
						commit
						1fc8957c1c
					
				@ -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,38 +66,31 @@ 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>(
 | 
			
		||||
                body: Row(
 | 
			
		||||
                  children: [
 | 
			
		||||
                    Flexible(flex: 1, child: body(isLocal: true)),
 | 
			
		||||
                    Flexible(flex: 1, child: body(isLocal: false))
 | 
			
		||||
                  ],
 | 
			
		||||
                ),
 | 
			
		||||
                bottomSheet: bottomSheet(),
 | 
			
		||||
              ));
 | 
			
		||||
        }));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  bool needShowCheckBox() {
 | 
			
		||||
    if (!model.selectMode) {
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
    return !_selectedItems.isOtherPage(model.isLocal);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Widget menu({bool isLocal = false}) {
 | 
			
		||||
    return PopupMenuButton<String>(
 | 
			
		||||
        icon: Icon(Icons.more_vert),
 | 
			
		||||
        itemBuilder: (context) {
 | 
			
		||||
          return [
 | 
			
		||||
@ -190,34 +183,16 @@ class _FileManagerPageState extends State<FileManagerPage>
 | 
			
		||||
                          child: Text(translate("OK")))
 | 
			
		||||
                    ]));
 | 
			
		||||
          } else if (v == "hidden") {
 | 
			
		||||
                            model.toggleShowHidden();
 | 
			
		||||
            model.toggleShowHidden(local: isLocal);
 | 
			
		||||
          }
 | 
			
		||||
                        }),
 | 
			
		||||
                  ],
 | 
			
		||||
                ),
 | 
			
		||||
                body: Row(
 | 
			
		||||
                  children: [
 | 
			
		||||
                    Flexible(flex: 1, child: body(isLocal: true)),
 | 
			
		||||
                    Flexible(flex: 1, child: body(isLocal: false))
 | 
			
		||||
                  ],
 | 
			
		||||
                ),
 | 
			
		||||
                bottomSheet: bottomSheet(),
 | 
			
		||||
              ));
 | 
			
		||||
        }));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  bool needShowCheckBox() {
 | 
			
		||||
    if (!model.selectMode) {
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
    return !_selectedItems.isOtherPage(model.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(
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
            });
 | 
			
		||||
          initialIndex.value = indexOf;
 | 
			
		||||
        } else {
 | 
			
		||||
          connectionIds.add(id);
 | 
			
		||||
            setState(() {
 | 
			
		||||
              initialIndex = connectionIds.length - 1;
 | 
			
		||||
            });
 | 
			
		||||
          initialIndex.value = connectionIds.length - 1;
 | 
			
		||||
        }
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
@ -59,8 +54,9 @@ class _FileManagerTabPageState extends State<FileManagerTabPage>
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    return Scaffold(
 | 
			
		||||
      body: DefaultTabController(
 | 
			
		||||
        initialIndex: initialIndex,
 | 
			
		||||
      body: Obx(
 | 
			
		||||
        ()=> DefaultTabController(
 | 
			
		||||
          initialIndex: initialIndex.value,
 | 
			
		||||
          length: connectionIds.length,
 | 
			
		||||
          animationDuration: Duration.zero,
 | 
			
		||||
          child: Column(
 | 
			
		||||
@ -106,6 +102,7 @@ class _FileManagerTabPageState extends State<FileManagerTabPage>
 | 
			
		||||
            ],
 | 
			
		||||
          ),
 | 
			
		||||
        ),
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -114,9 +111,7 @@ class _FileManagerTabPageState extends State<FileManagerTabPage>
 | 
			
		||||
    if (indexOf == -1) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    setState(() {
 | 
			
		||||
    connectionIds.removeAt(indexOf);
 | 
			
		||||
      initialIndex = max(0, initialIndex - 1);
 | 
			
		||||
    });
 | 
			
		||||
    initialIndex.value = max(0, initialIndex.value - 1);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
    if (isLocal == null) {
 | 
			
		||||
      // compatible for mobile logic
 | 
			
		||||
      _currentLocalDir.changeSortStyle(sort);
 | 
			
		||||
      _currentRemoteDir.changeSortStyle(sort);
 | 
			
		||||
    } else if (isLocal) {
 | 
			
		||||
      _currentLocalDir.changeSortStyle(sort);
 | 
			
		||||
    } else {
 | 
			
		||||
      _currentRemoteDir.changeSortStyle(sort);
 | 
			
		||||
    }
 | 
			
		||||
    notifyListeners();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -101,7 +101,7 @@ class RustDeskMultiWindowManager {
 | 
			
		||||
      case WindowType.RemoteDesktop:
 | 
			
		||||
        return _remoteDesktopWindowId;
 | 
			
		||||
      case WindowType.FileTransfer:
 | 
			
		||||
        break;
 | 
			
		||||
        return _fileTransferWindowId;
 | 
			
		||||
      case WindowType.PortForward:
 | 
			
		||||
        break;
 | 
			
		||||
      case WindowType.Unknown:
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user