diff --git a/flutter/lib/desktop/pages/file_manager_page.dart b/flutter/lib/desktop/pages/file_manager_page.dart index fc20d5277..0111e5f90 100644 --- a/flutter/lib/desktop/pages/file_manager_page.dart +++ b/flutter/lib/desktop/pages/file_manager_page.dart @@ -1,6 +1,7 @@ import 'dart:io'; import 'dart:math'; +import 'package:desktop_drop/desktop_drop.dart'; import 'package:flutter/material.dart'; import 'package:flutter_breadcrumb/flutter_breadcrumb.dart'; import 'package:flutter_hbb/mobile/pages/file_manager_page.dart'; @@ -39,6 +40,8 @@ class _FileManagerPageState extends State final _breadCrumbScrollerLocal = ScrollController(); final _breadCrumbScrollerRemote = ScrollController(); + final _dropMaskVisible = false.obs; + ScrollController getBreadCrumbScrollController(bool isLocal) { return isLocal ? _breadCrumbScrollerLocal : _breadCrumbScrollerRemote; } @@ -155,243 +158,248 @@ class _FileManagerPageState extends State decoration: BoxDecoration(border: Border.all(color: Colors.black26)), margin: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(8.0), - child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - headTools(isLocal), - Expanded( - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - child: SingleChildScrollView( - child: entries.isEmpty - ? Offstage() - : Obx( - () { - final searchText = - isLocal ? _searchTextLocal : _searchTextRemote; - final filteredEntries = searchText.isEmpty - ? entries.where((element) { - if (searchText.isEmpty) { - return true; - } else { - return element.name - .contains(searchText.value); - } - }).toList(growable: false) - : entries; - return DataTable( - key: ValueKey(isLocal ? 0 : 1), - showCheckboxColumn: true, - dataRowHeight: 25, - headingRowHeight: 30, - columnSpacing: 8, - showBottomBorder: true, - sortColumnIndex: sortIndex, - sortAscending: sortAscending, - columns: [ - DataColumn(label: Text(translate(" "))), // icon - DataColumn( - label: Text( - translate("Name"), - ), - onSort: (columnIndex, ascending) { - model.changeSortStyle(SortBy.Name, - isLocal: isLocal, ascending: ascending); - }), - DataColumn( - label: Text( - translate("Modified"), - ), - onSort: (columnIndex, ascending) { - model.changeSortStyle(SortBy.Modified, - isLocal: isLocal, ascending: ascending); - }), - DataColumn( - label: Text(translate("Size")), - onSort: (columnIndex, ascending) { - model.changeSortStyle(SortBy.Size, - isLocal: isLocal, ascending: ascending); - }), - ], - rows: filteredEntries.map((entry) { - final sizeStr = entry.isFile - ? readableFileSize(entry.size.toDouble()) - : ""; - return DataRow( - key: ValueKey(entry.name), - onSelectChanged: (s) { - if (s != null) { - if (s) { - getSelectedItem(isLocal).add(isLocal, entry); - } else { - getSelectedItem(isLocal).remove(entry); - } - setState(() {}); + child: DropTarget( + onDragDone: (detail) => handleDragDone(detail, isLocal), + onDragEntered: (enter) { + _dropMaskVisible.value = true; + }, + onDragExited: (exit) { + _dropMaskVisible.value = false; + }, + child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ + headTools(isLocal), + Expanded( + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: SingleChildScrollView( + child: ObxValue( + (searchText) { + final filteredEntries = searchText.isEmpty + ? entries.where((element) { + if (searchText.isEmpty) { + return true; + } else { + return element.name.contains(searchText.value); } - }, - selected: getSelectedItem(isLocal).contains(entry), - cells: [ - DataCell(Icon( - entry.isFile - ? Icons.feed_outlined - : Icons.folder, - size: 25)), - DataCell( - ConstrainedBox( - constraints: - BoxConstraints(maxWidth: 100), - child: Tooltip( - message: entry.name, - child: Text(entry.name, - overflow: TextOverflow.ellipsis), - )), onTap: () { - if (entry.isDirectory) { - openDirectory(entry.path, isLocal: isLocal); - if (isLocal) { - _localSelectedItems.clear(); + }).toList(growable: false) + : entries; + return DataTable( + key: ValueKey(isLocal ? 0 : 1), + showCheckboxColumn: true, + dataRowHeight: 25, + headingRowHeight: 30, + columnSpacing: 8, + showBottomBorder: true, + sortColumnIndex: sortIndex, + sortAscending: sortAscending, + columns: [ + DataColumn(label: Text(translate(" "))), // icon + DataColumn( + label: Text( + translate("Name"), + ), + onSort: (columnIndex, ascending) { + model.changeSortStyle(SortBy.Name, + isLocal: isLocal, ascending: ascending); + }), + DataColumn( + label: Text( + translate("Modified"), + ), + onSort: (columnIndex, ascending) { + model.changeSortStyle(SortBy.Modified, + isLocal: isLocal, ascending: ascending); + }), + DataColumn( + label: Text(translate("Size")), + onSort: (columnIndex, ascending) { + model.changeSortStyle(SortBy.Size, + isLocal: isLocal, ascending: ascending); + }), + ], + rows: filteredEntries.map((entry) { + final sizeStr = entry.isFile + ? readableFileSize(entry.size.toDouble()) + : ""; + return DataRow( + key: ValueKey(entry.name), + onSelectChanged: (s) { + if (s != null) { + if (s) { + getSelectedItem(isLocal) + .add(isLocal, entry); } else { - _remoteSelectedItems.clear(); - } - } else { - // Perform file-related tasks. - final _selectedItems = - getSelectedItem(isLocal); - if (_selectedItems.contains(entry)) { - _selectedItems.remove(entry); - } else { - _selectedItems.add(isLocal, entry); + getSelectedItem(isLocal).remove(entry); } setState(() {}); } - }), - DataCell(Text( - entry - .lastModified() - .toString() - .replaceAll(".000", "") + - " ", - style: TextStyle( - fontSize: 12, - color: MyTheme.darkGray), - )), - DataCell(Text( - sizeStr, - style: TextStyle( - fontSize: 12, - color: MyTheme.darkGray), - )), - ]); - }).toList(growable: false), - ); - }, + }, + selected: + getSelectedItem(isLocal).contains(entry), + cells: [ + DataCell(Icon( + entry.isFile + ? Icons.feed_outlined + : Icons.folder, + size: 25)), + DataCell( + ConstrainedBox( + constraints: + BoxConstraints(maxWidth: 100), + child: Tooltip( + message: entry.name, + child: Text(entry.name, + overflow: TextOverflow.ellipsis), + )), onTap: () { + if (entry.isDirectory) { + openDirectory(entry.path, isLocal: isLocal); + if (isLocal) { + _localSelectedItems.clear(); + } else { + _remoteSelectedItems.clear(); + } + } else { + // Perform file-related tasks. + final _selectedItems = + getSelectedItem(isLocal); + if (_selectedItems.contains(entry)) { + _selectedItems.remove(entry); + } else { + _selectedItems.add(isLocal, entry); + } + setState(() {}); + } + }), + DataCell(Text( + entry + .lastModified() + .toString() + .replaceAll(".000", "") + + " ", + style: TextStyle( + fontSize: 12, color: MyTheme.darkGray), + )), + DataCell(Text( + sizeStr, + style: TextStyle( + fontSize: 12, color: MyTheme.darkGray), + )), + ]); + }).toList(growable: false), + ); + }, + isLocal ? _searchTextLocal : _searchTextRemote, + ), ), - ), - ) - ], - )), - // Center(child: listTail(isLocal: isLocal)), - // Expanded( - // child: ListView.builder( - // itemCount: entries.length + 1, - // itemBuilder: (context, index) { - // if (index >= entries.length) { - // return listTail(isLocal: isLocal); - // } - // var selected = false; - // if (model.selectMode) { - // selected = _selectedItems.contains(entries[index]); - // } - // - // final sizeStr = entries[index].isFile - // ? readableFileSize(entries[index].size.toDouble()) - // : ""; - // return Card( - // child: ListTile( - // leading: Icon( - // entries[index].isFile ? Icons.feed_outlined : Icons.folder, - // size: 40), - // title: Text(entries[index].name), - // selected: selected, - // subtitle: Text( - // entries[index] - // .lastModified() - // .toString() - // .replaceAll(".000", "") + - // " " + - // sizeStr, - // style: TextStyle(fontSize: 12, color: MyTheme.darkGray), - // ), - // trailing: needShowCheckBox() - // ? Checkbox( - // value: selected, - // onChanged: (v) { - // if (v == null) return; - // if (v && !selected) { - // _selectedItems.add(isLocal, entries[index]); - // } else if (!v && selected) { - // _selectedItems.remove(entries[index]); - // } - // setState(() {}); - // }) - // : PopupMenuButton( - // icon: Icon(Icons.more_vert), - // itemBuilder: (context) { - // return [ - // PopupMenuItem( - // child: Text(translate("Delete")), - // value: "delete", - // ), - // PopupMenuItem( - // child: Text(translate("Multi Select")), - // value: "multi_select", - // ), - // PopupMenuItem( - // child: Text(translate("Properties")), - // value: "properties", - // enabled: false, - // ) - // ]; - // }, - // onSelected: (v) { - // if (v == "delete") { - // final items = SelectedItems(); - // items.add(isLocal, entries[index]); - // model.removeAction(items); - // } else if (v == "multi_select") { - // _selectedItems.clear(); - // model.toggleSelectMode(); - // } - // }), - // onTap: () { - // if (model.selectMode && !_selectedItems.isOtherPage(isLocal)) { - // if (selected) { - // _selectedItems.remove(entries[index]); - // } else { - // _selectedItems.add(isLocal, entries[index]); - // } - // setState(() {}); - // return; - // } - // if (entries[index].isDirectory) { - // openDirectory(entries[index].path, isLocal: isLocal); - // breadCrumbScrollToEnd(isLocal); - // } else { - // // Perform file-related tasks. - // } - // }, - // onLongPress: () { - // _selectedItems.clear(); - // model.toggleSelectMode(); - // if (model.selectMode) { - // _selectedItems.add(isLocal, entries[index]); - // } - // setState(() {}); - // }, - // ), - // ); - // }, - // )) - ]), + ) + ], + )), + // Center(child: listTail(isLocal: isLocal)), + // Expanded( + // child: ListView.builder( + // itemCount: entries.length + 1, + // itemBuilder: (context, index) { + // if (index >= entries.length) { + // return listTail(isLocal: isLocal); + // } + // var selected = false; + // if (model.selectMode) { + // selected = _selectedItems.contains(entries[index]); + // } + // + // final sizeStr = entries[index].isFile + // ? readableFileSize(entries[index].size.toDouble()) + // : ""; + // return Card( + // child: ListTile( + // leading: Icon( + // entries[index].isFile ? Icons.feed_outlined : Icons.folder, + // size: 40), + // title: Text(entries[index].name), + // selected: selected, + // subtitle: Text( + // entries[index] + // .lastModified() + // .toString() + // .replaceAll(".000", "") + + // " " + + // sizeStr, + // style: TextStyle(fontSize: 12, color: MyTheme.darkGray), + // ), + // trailing: needShowCheckBox() + // ? Checkbox( + // value: selected, + // onChanged: (v) { + // if (v == null) return; + // if (v && !selected) { + // _selectedItems.add(isLocal, entries[index]); + // } else if (!v && selected) { + // _selectedItems.remove(entries[index]); + // } + // setState(() {}); + // }) + // : PopupMenuButton( + // icon: Icon(Icons.more_vert), + // itemBuilder: (context) { + // return [ + // PopupMenuItem( + // child: Text(translate("Delete")), + // value: "delete", + // ), + // PopupMenuItem( + // child: Text(translate("Multi Select")), + // value: "multi_select", + // ), + // PopupMenuItem( + // child: Text(translate("Properties")), + // value: "properties", + // enabled: false, + // ) + // ]; + // }, + // onSelected: (v) { + // if (v == "delete") { + // final items = SelectedItems(); + // items.add(isLocal, entries[index]); + // model.removeAction(items); + // } else if (v == "multi_select") { + // _selectedItems.clear(); + // model.toggleSelectMode(); + // } + // }), + // onTap: () { + // if (model.selectMode && !_selectedItems.isOtherPage(isLocal)) { + // if (selected) { + // _selectedItems.remove(entries[index]); + // } else { + // _selectedItems.add(isLocal, entries[index]); + // } + // setState(() {}); + // return; + // } + // if (entries[index].isDirectory) { + // openDirectory(entries[index].path, isLocal: isLocal); + // breadCrumbScrollToEnd(isLocal); + // } else { + // // Perform file-related tasks. + // } + // }, + // onLongPress: () { + // _selectedItems.clear(); + // model.toggleSelectMode(); + // if (model.selectMode) { + // _selectedItems.add(isLocal, entries[index]); + // } + // setState(() {}); + // }, + // ), + // ); + // }, + // )) + ]), + ), ); } @@ -831,4 +839,23 @@ class _FileManagerPageState extends State breadCrumbScrollToEnd(isLocal); }); } + + void handleDragDone(DropDoneDetails details, bool isLocal) { + if (isLocal) { + // ignore local + return; + } + var items = SelectedItems(); + details.files.forEach((file) { + final f = File(file.path); + items.add( + true, + Entry() + ..path = file.path + ..name = file.name + ..size = + FileSystemEntity.isDirectorySync(f.path) ? 0 : f.lengthSync()); + }); + model.sendFiles(items, isRemote: false); + } } diff --git a/flutter/lib/models/file_model.dart b/flutter/lib/models/file_model.dart index 1c3960b50..74c2cd515 100644 --- a/flutter/lib/models/file_model.dart +++ b/flutter/lib/models/file_model.dart @@ -732,6 +732,7 @@ class FileModel extends ChangeNotifier { job.totalSize = total_size.toInt(); } debugPrint("update folder files: ${info}"); + notifyListeners(); } bool get remoteSortAscending => _remoteSortAscending; diff --git a/flutter/pubspec.lock b/flutter/pubspec.lock index fe7359bf5..6ccfe72ac 100644 --- a/flutter/pubspec.lock +++ b/flutter/pubspec.lock @@ -239,6 +239,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.0.12" + desktop_drop: + dependency: "direct main" + description: + name: desktop_drop + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.3" desktop_multi_window: dependency: "direct main" description: @@ -817,7 +824,7 @@ packages: name: qr_code_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.0.0" + version: "1.0.1" quiver: dependency: transitive description: diff --git a/flutter/pubspec.yaml b/flutter/pubspec.yaml index a911903f8..40aa1ca43 100644 --- a/flutter/pubspec.yaml +++ b/flutter/pubspec.yaml @@ -69,6 +69,7 @@ dependencies: get: ^4.6.5 visibility_detector: ^0.3.3 contextmenu: ^3.0.0 + desktop_drop: ^0.3.3 dev_dependencies: flutter_launcher_icons: ^0.9.1