desktop file transfer ctrl + click multi selection

This commit is contained in:
csf 2022-10-17 22:26:18 +09:00
parent 76581d46f2
commit 7e7214bd07
2 changed files with 182 additions and 140 deletions

View File

@ -31,6 +31,8 @@ const int kMobileMaxDisplayHeight = 1280;
const int kDesktopMaxDisplayWidth = 1920; const int kDesktopMaxDisplayWidth = 1920;
const int kDesktopMaxDisplayHeight = 1080; const int kDesktopMaxDisplayHeight = 1080;
const int kDesktopDoubleClickTimeMilli = 200;
const Size kConnectionManagerWindowSize = Size(300, 400); const Size kConnectionManagerWindowSize = Size(300, 400);
// Tabbar transition duration, now we remove the duration // Tabbar transition duration, now we remove the duration
const Duration kTabTransitionDuration = Duration.zero; const Duration kTabTransitionDuration = Duration.zero;

View File

@ -1,8 +1,10 @@
import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'dart:math'; import 'dart:math';
import 'package:desktop_drop/desktop_drop.dart'; import 'package:desktop_drop/desktop_drop.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.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';
import 'package:flutter_hbb/models/file_model.dart'; import 'package:flutter_hbb/models/file_model.dart';
@ -44,15 +46,17 @@ class _FileManagerPageState extends State<FileManagerPage>
final _locationStatusLocal = LocationStatus.bread.obs; final _locationStatusLocal = LocationStatus.bread.obs;
final _locationStatusRemote = LocationStatus.bread.obs; final _locationStatusRemote = LocationStatus.bread.obs;
final FocusNode _locationNodeLocal = final _locationNodeLocal = FocusNode(debugLabel: "locationNodeLocal");
FocusNode(debugLabel: "locationNodeLocal"); final _locationNodeRemote = FocusNode(debugLabel: "locationNodeRemote");
final FocusNode _locationNodeRemote =
FocusNode(debugLabel: "locationNodeRemote");
final _searchTextLocal = "".obs; final _searchTextLocal = "".obs;
final _searchTextRemote = "".obs; final _searchTextRemote = "".obs;
final _breadCrumbScrollerLocal = ScrollController(); final _breadCrumbScrollerLocal = ScrollController();
final _breadCrumbScrollerRemote = ScrollController(); final _breadCrumbScrollerRemote = ScrollController();
/// [_lastClickTime], [_lastClickEntry] help to handle double click
int _lastClickTime = DateTime.now().millisecondsSinceEpoch;
Entry? _lastClickEntry;
final _dropMaskVisible = false.obs; // TODO impl drop mask final _dropMaskVisible = false.obs; // TODO impl drop mask
ScrollController getBreadCrumbScrollController(bool isLocal) { ScrollController getBreadCrumbScrollController(bool isLocal) {
@ -171,22 +175,6 @@ class _FileManagerPageState extends State<FileManagerPage>
} }
Widget body({bool isLocal = false}) { Widget body({bool isLocal = false}) {
final fd = model.getCurrentDir(isLocal);
final entries = fd.entries;
final sortIndex = (SortBy style) {
switch (style) {
case SortBy.Name:
return 0;
case SortBy.Type:
return 0;
case SortBy.Modified:
return 1;
case SortBy.Size:
return 2;
}
}(model.getSortStyle(isLocal));
final sortAscending =
isLocal ? model.localSortAscending : model.remoteSortAscending;
return Container( return Container(
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),
@ -208,7 +196,35 @@ class _FileManagerPageState extends State<FileManagerPage>
Expanded( Expanded(
child: SingleChildScrollView( child: SingleChildScrollView(
controller: ScrollController(), controller: ScrollController(),
child: ObxValue<RxString>( child: _buildDataTable(context, isLocal),
),
)
],
)),
]),
),
);
}
Widget _buildDataTable(BuildContext context, bool isLocal) {
final fd = model.getCurrentDir(isLocal);
final entries = fd.entries;
final sortIndex = (SortBy style) {
switch (style) {
case SortBy.Name:
return 0;
case SortBy.Type:
return 0;
case SortBy.Modified:
return 1;
case SortBy.Size:
return 2;
}
}(model.getSortStyle(isLocal));
final sortAscending =
isLocal ? model.localSortAscending : model.remoteSortAscending;
return ObxValue<RxString>(
(searchText) { (searchText) {
final filteredEntries = searchText.isNotEmpty final filteredEntries = searchText.isNotEmpty
? entries.where((element) { ? entries.where((element) {
@ -217,7 +233,7 @@ class _FileManagerPageState extends State<FileManagerPage>
: entries; : entries;
return DataTable( return DataTable(
key: ValueKey(isLocal ? 0 : 1), key: ValueKey(isLocal ? 0 : 1),
showCheckboxColumn: true, showCheckboxColumn: false,
dataRowHeight: 25, dataRowHeight: 25,
headingRowHeight: 30, headingRowHeight: 30,
horizontalMargin: 8, horizontalMargin: 8,
@ -250,35 +266,41 @@ class _FileManagerPageState extends State<FileManagerPage>
}), }),
], ],
rows: filteredEntries.map((entry) { rows: filteredEntries.map((entry) {
final sizeStr = entry.isFile final sizeStr =
? readableFileSize(entry.size.toDouble()) entry.isFile ? readableFileSize(entry.size.toDouble()) : "";
: ""; final lastModifiedStr =
"${entry.lastModified().toString().replaceAll(".000", "")} ";
return DataRow( return DataRow(
key: ValueKey(entry.name), key: ValueKey(entry.name),
onSelectChanged: (s) { onSelectChanged: (s) {
final isCtrlDown = RawKeyboard.instance.keysPressed
.contains(LogicalKeyboardKey.controlLeft);
final items = getSelectedItem(isLocal);
if (isCtrlDown) {
if (s != null) { if (s != null) {
if (s) { if (s) {
getSelectedItem(isLocal) items.add(isLocal, entry);
.add(isLocal, entry);
} else { } else {
getSelectedItem(isLocal).remove(entry); items.remove(entry);
}
}
} else {
items.clear();
items.add(isLocal, entry);
} }
setState(() {}); setState(() {});
}
}, },
selected: selected: getSelectedItem(isLocal).contains(entry),
getSelectedItem(isLocal).contains(entry),
cells: [ cells: [
DataCell( DataCell(
Container( Container(
width: 180, width: 200,
child: Tooltip( child: Tooltip(
waitDuration: Duration(milliseconds: 500),
message: entry.name, message: entry.name,
child: Row(children: [ child: Row(children: [
Icon( Icon(
entry.isFile entry.isFile ? Icons.feed_outlined : Icons.folder,
? Icons.feed_outlined
: Icons.folder,
size: 20, size: 20,
color: Theme.of(context) color: Theme.of(context)
.iconTheme .iconTheme
@ -287,56 +309,73 @@ class _FileManagerPageState extends State<FileManagerPage>
).marginSymmetric(horizontal: 2), ).marginSymmetric(horizontal: 2),
Expanded( Expanded(
child: Text(entry.name, child: Text(entry.name,
overflow: overflow: TextOverflow.ellipsis))
TextOverflow.ellipsis))
]), ]),
)), onTap: () { )),
if (entry.isDirectory) { onTap: () {
final items = getSelectedItem(isLocal);
// handle double click
if (_checkDoubleClick(entry)) {
openDirectory(entry.path, isLocal: isLocal); openDirectory(entry.path, isLocal: isLocal);
if (isLocal) { items.clear();
_localSelectedItems.clear(); return;
}
final isCtrlDown = RawKeyboard.instance.keysPressed
.contains(LogicalKeyboardKey.controlLeft);
if (isCtrlDown) {
if (items.contains(entry)) {
items.remove(entry);
} else { } else {
_remoteSelectedItems.clear(); items.add(isLocal, entry);
} }
} else { } else {
// Perform file-related tasks. items.clear();
final selectedItems = items.add(isLocal, entry);
getSelectedItem(isLocal);
if (selectedItems.contains(entry)) {
selectedItems.remove(entry);
} else {
selectedItems.add(isLocal, entry);
} }
setState(() {}); setState(() {});
} },
}), ),
DataCell(FittedBox( DataCell(FittedBox(
child: Tooltip(
waitDuration: Duration(milliseconds: 500),
message: lastModifiedStr,
child: Text( child: Text(
"${entry.lastModified().toString().replaceAll(".000", "")} ", lastModifiedStr,
style: TextStyle( style: TextStyle(
fontSize: 12, color: MyTheme.darkGray), fontSize: 12, color: MyTheme.darkGray),
))), )))),
DataCell(Text( DataCell(Tooltip(
waitDuration: Duration(milliseconds: 500),
message: sizeStr,
child: Text(
sizeStr, sizeStr,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: TextStyle( style: TextStyle(fontSize: 10, color: MyTheme.darkGray),
fontSize: 10, color: MyTheme.darkGray), ))),
)),
]); ]);
}).toList(growable: false), }).toList(growable: false),
); );
}, },
isLocal ? _searchTextLocal : _searchTextRemote, isLocal ? _searchTextLocal : _searchTextRemote,
),
),
)
],
)),
]),
),
); );
} }
bool _checkDoubleClick(Entry entry) {
final current = DateTime.now().millisecondsSinceEpoch;
final elapsed = current - _lastClickTime;
_lastClickTime = current;
if (_lastClickEntry == entry) {
if (elapsed < kDesktopDoubleClickTimeMilli) {
return true;
}
} else {
_lastClickEntry = entry;
}
return false;
}
/// transfer status list /// transfer status list
/// watch transfer status /// watch transfer status
Widget statusList() { Widget statusList() {
@ -369,6 +408,7 @@ class _FileManagerPageState extends State<FileManagerPage>
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Tooltip( Tooltip(
waitDuration: Duration(milliseconds: 500),
message: item.jobName, message: item.jobName,
child: Text( child: Text(
item.jobName, item.jobName,