feat: drop to send files to remote
Signed-off-by: Kingtous <kingtous@qq.com>
This commit is contained in:
parent
4bd5fe1509
commit
a001b15335
@ -1,6 +1,7 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:desktop_drop/desktop_drop.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_breadcrumb/flutter_breadcrumb.dart';
|
import 'package:flutter_breadcrumb/flutter_breadcrumb.dart';
|
||||||
import 'package:flutter_hbb/mobile/pages/file_manager_page.dart';
|
import 'package:flutter_hbb/mobile/pages/file_manager_page.dart';
|
||||||
@ -39,6 +40,8 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
final _breadCrumbScrollerLocal = ScrollController();
|
final _breadCrumbScrollerLocal = ScrollController();
|
||||||
final _breadCrumbScrollerRemote = ScrollController();
|
final _breadCrumbScrollerRemote = ScrollController();
|
||||||
|
|
||||||
|
final _dropMaskVisible = false.obs;
|
||||||
|
|
||||||
ScrollController getBreadCrumbScrollController(bool isLocal) {
|
ScrollController getBreadCrumbScrollController(bool isLocal) {
|
||||||
return isLocal ? _breadCrumbScrollerLocal : _breadCrumbScrollerRemote;
|
return isLocal ? _breadCrumbScrollerLocal : _breadCrumbScrollerRemote;
|
||||||
}
|
}
|
||||||
@ -155,243 +158,248 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
decoration: BoxDecoration(border: Border.all(color: Colors.black26)),
|
decoration: BoxDecoration(border: Border.all(color: Colors.black26)),
|
||||||
margin: const EdgeInsets.all(16.0),
|
margin: const EdgeInsets.all(16.0),
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(8.0),
|
||||||
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
child: DropTarget(
|
||||||
headTools(isLocal),
|
onDragDone: (detail) => handleDragDone(detail, isLocal),
|
||||||
Expanded(
|
onDragEntered: (enter) {
|
||||||
child: Row(
|
_dropMaskVisible.value = true;
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
},
|
||||||
children: [
|
onDragExited: (exit) {
|
||||||
Expanded(
|
_dropMaskVisible.value = false;
|
||||||
child: SingleChildScrollView(
|
},
|
||||||
child: entries.isEmpty
|
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||||
? Offstage()
|
headTools(isLocal),
|
||||||
: Obx(
|
Expanded(
|
||||||
() {
|
child: Row(
|
||||||
final searchText =
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
isLocal ? _searchTextLocal : _searchTextRemote;
|
children: [
|
||||||
final filteredEntries = searchText.isEmpty
|
Expanded(
|
||||||
? entries.where((element) {
|
child: SingleChildScrollView(
|
||||||
if (searchText.isEmpty) {
|
child: ObxValue<RxString>(
|
||||||
return true;
|
(searchText) {
|
||||||
} else {
|
final filteredEntries = searchText.isEmpty
|
||||||
return element.name
|
? entries.where((element) {
|
||||||
.contains(searchText.value);
|
if (searchText.isEmpty) {
|
||||||
}
|
return true;
|
||||||
}).toList(growable: false)
|
} else {
|
||||||
: entries;
|
return element.name.contains(searchText.value);
|
||||||
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(() {});
|
|
||||||
}
|
}
|
||||||
},
|
}).toList(growable: false)
|
||||||
selected: getSelectedItem(isLocal).contains(entry),
|
: entries;
|
||||||
cells: [
|
return DataTable(
|
||||||
DataCell(Icon(
|
key: ValueKey(isLocal ? 0 : 1),
|
||||||
entry.isFile
|
showCheckboxColumn: true,
|
||||||
? Icons.feed_outlined
|
dataRowHeight: 25,
|
||||||
: Icons.folder,
|
headingRowHeight: 30,
|
||||||
size: 25)),
|
columnSpacing: 8,
|
||||||
DataCell(
|
showBottomBorder: true,
|
||||||
ConstrainedBox(
|
sortColumnIndex: sortIndex,
|
||||||
constraints:
|
sortAscending: sortAscending,
|
||||||
BoxConstraints(maxWidth: 100),
|
columns: [
|
||||||
child: Tooltip(
|
DataColumn(label: Text(translate(" "))), // icon
|
||||||
message: entry.name,
|
DataColumn(
|
||||||
child: Text(entry.name,
|
label: Text(
|
||||||
overflow: TextOverflow.ellipsis),
|
translate("Name"),
|
||||||
)), onTap: () {
|
),
|
||||||
if (entry.isDirectory) {
|
onSort: (columnIndex, ascending) {
|
||||||
openDirectory(entry.path, isLocal: isLocal);
|
model.changeSortStyle(SortBy.Name,
|
||||||
if (isLocal) {
|
isLocal: isLocal, ascending: ascending);
|
||||||
_localSelectedItems.clear();
|
}),
|
||||||
|
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 {
|
} else {
|
||||||
_remoteSelectedItems.clear();
|
getSelectedItem(isLocal).remove(entry);
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Perform file-related tasks.
|
|
||||||
final _selectedItems =
|
|
||||||
getSelectedItem(isLocal);
|
|
||||||
if (_selectedItems.contains(entry)) {
|
|
||||||
_selectedItems.remove(entry);
|
|
||||||
} else {
|
|
||||||
_selectedItems.add(isLocal, entry);
|
|
||||||
}
|
}
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
}
|
||||||
}),
|
},
|
||||||
DataCell(Text(
|
selected:
|
||||||
entry
|
getSelectedItem(isLocal).contains(entry),
|
||||||
.lastModified()
|
cells: [
|
||||||
.toString()
|
DataCell(Icon(
|
||||||
.replaceAll(".000", "") +
|
entry.isFile
|
||||||
" ",
|
? Icons.feed_outlined
|
||||||
style: TextStyle(
|
: Icons.folder,
|
||||||
fontSize: 12,
|
size: 25)),
|
||||||
color: MyTheme.darkGray),
|
DataCell(
|
||||||
)),
|
ConstrainedBox(
|
||||||
DataCell(Text(
|
constraints:
|
||||||
sizeStr,
|
BoxConstraints(maxWidth: 100),
|
||||||
style: TextStyle(
|
child: Tooltip(
|
||||||
fontSize: 12,
|
message: entry.name,
|
||||||
color: MyTheme.darkGray),
|
child: Text(entry.name,
|
||||||
)),
|
overflow: TextOverflow.ellipsis),
|
||||||
]);
|
)), onTap: () {
|
||||||
}).toList(growable: false),
|
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)),
|
||||||
// Center(child: listTail(isLocal: isLocal)),
|
// Expanded(
|
||||||
// Expanded(
|
// child: ListView.builder(
|
||||||
// child: ListView.builder(
|
// itemCount: entries.length + 1,
|
||||||
// itemCount: entries.length + 1,
|
// itemBuilder: (context, index) {
|
||||||
// itemBuilder: (context, index) {
|
// if (index >= entries.length) {
|
||||||
// if (index >= entries.length) {
|
// return listTail(isLocal: isLocal);
|
||||||
// return listTail(isLocal: isLocal);
|
// }
|
||||||
// }
|
// var selected = false;
|
||||||
// var selected = false;
|
// if (model.selectMode) {
|
||||||
// if (model.selectMode) {
|
// selected = _selectedItems.contains(entries[index]);
|
||||||
// selected = _selectedItems.contains(entries[index]);
|
// }
|
||||||
// }
|
//
|
||||||
//
|
// final sizeStr = entries[index].isFile
|
||||||
// final sizeStr = entries[index].isFile
|
// ? readableFileSize(entries[index].size.toDouble())
|
||||||
// ? readableFileSize(entries[index].size.toDouble())
|
// : "";
|
||||||
// : "";
|
// return Card(
|
||||||
// return Card(
|
// child: ListTile(
|
||||||
// child: ListTile(
|
// leading: Icon(
|
||||||
// leading: Icon(
|
// entries[index].isFile ? Icons.feed_outlined : Icons.folder,
|
||||||
// entries[index].isFile ? Icons.feed_outlined : Icons.folder,
|
// size: 40),
|
||||||
// size: 40),
|
// title: Text(entries[index].name),
|
||||||
// title: Text(entries[index].name),
|
// selected: selected,
|
||||||
// selected: selected,
|
// subtitle: Text(
|
||||||
// subtitle: Text(
|
// entries[index]
|
||||||
// entries[index]
|
// .lastModified()
|
||||||
// .lastModified()
|
// .toString()
|
||||||
// .toString()
|
// .replaceAll(".000", "") +
|
||||||
// .replaceAll(".000", "") +
|
// " " +
|
||||||
// " " +
|
// sizeStr,
|
||||||
// sizeStr,
|
// style: TextStyle(fontSize: 12, color: MyTheme.darkGray),
|
||||||
// style: TextStyle(fontSize: 12, color: MyTheme.darkGray),
|
// ),
|
||||||
// ),
|
// trailing: needShowCheckBox()
|
||||||
// trailing: needShowCheckBox()
|
// ? Checkbox(
|
||||||
// ? Checkbox(
|
// value: selected,
|
||||||
// value: selected,
|
// onChanged: (v) {
|
||||||
// onChanged: (v) {
|
// if (v == null) return;
|
||||||
// if (v == null) return;
|
// if (v && !selected) {
|
||||||
// if (v && !selected) {
|
// _selectedItems.add(isLocal, entries[index]);
|
||||||
// _selectedItems.add(isLocal, entries[index]);
|
// } else if (!v && selected) {
|
||||||
// } else if (!v && selected) {
|
// _selectedItems.remove(entries[index]);
|
||||||
// _selectedItems.remove(entries[index]);
|
// }
|
||||||
// }
|
// setState(() {});
|
||||||
// setState(() {});
|
// })
|
||||||
// })
|
// : PopupMenuButton<String>(
|
||||||
// : PopupMenuButton<String>(
|
// icon: Icon(Icons.more_vert),
|
||||||
// icon: Icon(Icons.more_vert),
|
// itemBuilder: (context) {
|
||||||
// itemBuilder: (context) {
|
// return [
|
||||||
// return [
|
// PopupMenuItem(
|
||||||
// PopupMenuItem(
|
// child: Text(translate("Delete")),
|
||||||
// child: Text(translate("Delete")),
|
// value: "delete",
|
||||||
// value: "delete",
|
// ),
|
||||||
// ),
|
// PopupMenuItem(
|
||||||
// PopupMenuItem(
|
// child: Text(translate("Multi Select")),
|
||||||
// child: Text(translate("Multi Select")),
|
// value: "multi_select",
|
||||||
// value: "multi_select",
|
// ),
|
||||||
// ),
|
// PopupMenuItem(
|
||||||
// PopupMenuItem(
|
// child: Text(translate("Properties")),
|
||||||
// child: Text(translate("Properties")),
|
// value: "properties",
|
||||||
// value: "properties",
|
// enabled: false,
|
||||||
// enabled: false,
|
// )
|
||||||
// )
|
// ];
|
||||||
// ];
|
// },
|
||||||
// },
|
// onSelected: (v) {
|
||||||
// onSelected: (v) {
|
// if (v == "delete") {
|
||||||
// if (v == "delete") {
|
// final items = SelectedItems();
|
||||||
// final items = SelectedItems();
|
// items.add(isLocal, entries[index]);
|
||||||
// items.add(isLocal, entries[index]);
|
// model.removeAction(items);
|
||||||
// model.removeAction(items);
|
// } else if (v == "multi_select") {
|
||||||
// } else if (v == "multi_select") {
|
// _selectedItems.clear();
|
||||||
// _selectedItems.clear();
|
// model.toggleSelectMode();
|
||||||
// model.toggleSelectMode();
|
// }
|
||||||
// }
|
// }),
|
||||||
// }),
|
// onTap: () {
|
||||||
// onTap: () {
|
// if (model.selectMode && !_selectedItems.isOtherPage(isLocal)) {
|
||||||
// if (model.selectMode && !_selectedItems.isOtherPage(isLocal)) {
|
// if (selected) {
|
||||||
// if (selected) {
|
// _selectedItems.remove(entries[index]);
|
||||||
// _selectedItems.remove(entries[index]);
|
// } else {
|
||||||
// } else {
|
// _selectedItems.add(isLocal, entries[index]);
|
||||||
// _selectedItems.add(isLocal, entries[index]);
|
// }
|
||||||
// }
|
// setState(() {});
|
||||||
// setState(() {});
|
// return;
|
||||||
// return;
|
// }
|
||||||
// }
|
// if (entries[index].isDirectory) {
|
||||||
// if (entries[index].isDirectory) {
|
// openDirectory(entries[index].path, isLocal: isLocal);
|
||||||
// openDirectory(entries[index].path, isLocal: isLocal);
|
// breadCrumbScrollToEnd(isLocal);
|
||||||
// breadCrumbScrollToEnd(isLocal);
|
// } else {
|
||||||
// } else {
|
// // Perform file-related tasks.
|
||||||
// // Perform file-related tasks.
|
// }
|
||||||
// }
|
// },
|
||||||
// },
|
// onLongPress: () {
|
||||||
// onLongPress: () {
|
// _selectedItems.clear();
|
||||||
// _selectedItems.clear();
|
// model.toggleSelectMode();
|
||||||
// model.toggleSelectMode();
|
// if (model.selectMode) {
|
||||||
// if (model.selectMode) {
|
// _selectedItems.add(isLocal, entries[index]);
|
||||||
// _selectedItems.add(isLocal, entries[index]);
|
// }
|
||||||
// }
|
// setState(() {});
|
||||||
// setState(() {});
|
// },
|
||||||
// },
|
// ),
|
||||||
// ),
|
// );
|
||||||
// );
|
// },
|
||||||
// },
|
// ))
|
||||||
// ))
|
]),
|
||||||
]),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -831,4 +839,23 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
breadCrumbScrollToEnd(isLocal);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -732,6 +732,7 @@ class FileModel extends ChangeNotifier {
|
|||||||
job.totalSize = total_size.toInt();
|
job.totalSize = total_size.toInt();
|
||||||
}
|
}
|
||||||
debugPrint("update folder files: ${info}");
|
debugPrint("update folder files: ${info}");
|
||||||
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool get remoteSortAscending => _remoteSortAscending;
|
bool get remoteSortAscending => _remoteSortAscending;
|
||||||
|
@ -239,6 +239,13 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.0.12"
|
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:
|
desktop_multi_window:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -817,7 +824,7 @@ packages:
|
|||||||
name: qr_code_scanner
|
name: qr_code_scanner
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.0"
|
version: "1.0.1"
|
||||||
quiver:
|
quiver:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -69,6 +69,7 @@ dependencies:
|
|||||||
get: ^4.6.5
|
get: ^4.6.5
|
||||||
visibility_detector: ^0.3.3
|
visibility_detector: ^0.3.3
|
||||||
contextmenu: ^3.0.0
|
contextmenu: ^3.0.0
|
||||||
|
desktop_drop: ^0.3.3
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_launcher_icons: ^0.9.1
|
flutter_launcher_icons: ^0.9.1
|
||||||
|
Loading…
x
Reference in New Issue
Block a user