Merge pull request #3352 from Kingtous/master

opt: fs explorer resizable & search next for loop
This commit is contained in:
RustDesk 2023-02-24 16:15:58 +08:00 committed by GitHub
commit b6afd2c437
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 188 additions and 69 deletions

View File

@ -53,6 +53,8 @@ const int kDesktopMaxDisplayHeight = 1080;
const double kDesktopFileTransferNameColWidth = 200; const double kDesktopFileTransferNameColWidth = 200;
const double kDesktopFileTransferModifiedColWidth = 120; const double kDesktopFileTransferModifiedColWidth = 120;
const double kDesktopFileTransferMinimumWidth = 100;
const double kDesktopFileTransferMaximumWidth = 300;
const double kDesktopFileTransferRowHeight = 30.0; const double kDesktopFileTransferRowHeight = 30.0;
const double kDesktopFileTransferHeaderHeight = 25.0; const double kDesktopFileTransferHeaderHeight = 25.0;

View File

@ -2,6 +2,7 @@ import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'dart:math'; import 'dart:math';
import 'package:flutter_hbb/desktop/widgets/dragable_divider.dart';
import 'package:percent_indicator/percent_indicator.dart'; import 'package:percent_indicator/percent_indicator.dart';
import 'package:desktop_drop/desktop_drop.dart'; import 'package:desktop_drop/desktop_drop.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
@ -78,6 +79,10 @@ class _FileManagerPageState extends State<FileManagerPage>
final _keyboardNodeRemote = FocusNode(debugLabel: "keyboardNodeRemote"); final _keyboardNodeRemote = FocusNode(debugLabel: "keyboardNodeRemote");
final _listSearchBufferLocal = TimeoutStringBuffer(); final _listSearchBufferLocal = TimeoutStringBuffer();
final _listSearchBufferRemote = TimeoutStringBuffer(); final _listSearchBufferRemote = TimeoutStringBuffer();
final _nameColWidthLocal = kDesktopFileTransferNameColWidth.obs;
final _modifiedColWidthLocal = kDesktopFileTransferModifiedColWidth.obs;
final _nameColWidthRemote = kDesktopFileTransferNameColWidth.obs;
final _modifiedColWidthRemote = kDesktopFileTransferModifiedColWidth.obs;
/// [_lastClickTime], [_lastClickEntry] help to handle double click /// [_lastClickTime], [_lastClickEntry] help to handle double click
int _lastClickTime = int _lastClickTime =
@ -297,11 +302,12 @@ class _FileManagerPageState extends State<FileManagerPage>
} }
var searchResult = entries var searchResult = entries
.skip(skipCount) .skip(skipCount)
.where((element) => element.name.startsWith(buffer)); .where((element) => element.name.toLowerCase().startsWith(buffer));
if (searchResult.isEmpty) { if (searchResult.isEmpty) {
// cannot find next, lets restart search from head // cannot find next, lets restart search from head
debugPrint("restart search from head");
searchResult = searchResult =
entries.where((element) => element.name.startsWith(buffer)); entries.where((element) => element.name.toLowerCase().startsWith(buffer));
} }
if (searchResult.isEmpty) { if (searchResult.isEmpty) {
setState(() { setState(() {
@ -310,13 +316,13 @@ class _FileManagerPageState extends State<FileManagerPage>
return; return;
} }
_jumpToEntry(isLocal, searchResult.first, scrollController, _jumpToEntry(isLocal, searchResult.first, scrollController,
kDesktopFileTransferRowHeight, buffer); kDesktopFileTransferRowHeight);
}, },
onSearch: (buffer) { onSearch: (buffer) {
debugPrint("searching for $buffer"); debugPrint("searching for $buffer");
final selectedEntries = getSelectedItems(isLocal); final selectedEntries = getSelectedItems(isLocal);
final searchResult = final searchResult =
entries.where((element) => element.name.startsWith(buffer)); entries.where((element) => element.name.toLowerCase().startsWith(buffer));
selectedEntries.clear(); selectedEntries.clear();
if (searchResult.isEmpty) { if (searchResult.isEmpty) {
setState(() { setState(() {
@ -325,7 +331,7 @@ class _FileManagerPageState extends State<FileManagerPage>
return; return;
} }
_jumpToEntry(isLocal, searchResult.first, scrollController, _jumpToEntry(isLocal, searchResult.first, scrollController,
kDesktopFileTransferRowHeight, buffer); kDesktopFileTransferRowHeight);
}, },
child: ObxValue<RxString>( child: ObxValue<RxString>(
(searchText) { (searchText) {
@ -362,37 +368,41 @@ class _FileManagerPageState extends State<FileManagerPage>
child: Row( child: Row(
children: [ children: [
GestureDetector( GestureDetector(
child: Container( child: Obx(
width: kDesktopFileTransferNameColWidth, () => Container(
child: Tooltip( width: isLocal
waitDuration: ? _nameColWidthLocal.value
Duration(milliseconds: 500), : _nameColWidthRemote.value,
message: entry.name, child: Tooltip(
child: Row(children: [ waitDuration:
entry.isDrive Duration(milliseconds: 500),
? Image( message: entry.name,
image: iconHardDrive, child: Row(children: [
fit: BoxFit.scaleDown, entry.isDrive
color: Theme.of(context) ? Image(
.iconTheme image: iconHardDrive,
.color fit: BoxFit.scaleDown,
?.withOpacity(0.7)) color: Theme.of(context)
.paddingAll(4) .iconTheme
: SvgPicture.asset( .color
entry.isFile ?.withOpacity(0.7))
? "assets/file.svg" .paddingAll(4)
: "assets/folder.svg", : SvgPicture.asset(
color: Theme.of(context) entry.isFile
.tabBarTheme ? "assets/file.svg"
.labelColor, : "assets/folder.svg",
), color: Theme.of(context)
Expanded( .tabBarTheme
child: Text( .labelColor,
entry.name.nonBreaking, ),
overflow: Expanded(
TextOverflow.ellipsis)) child: Text(
]), entry.name.nonBreaking,
)), overflow:
TextOverflow.ellipsis))
]),
)),
),
onTap: () { onTap: () {
final items = getSelectedItems(isLocal); final items = getSelectedItems(isLocal);
// handle double click // handle double click
@ -406,24 +416,35 @@ class _FileManagerPageState extends State<FileManagerPage>
items, filteredEntries, entry, isLocal); items, filteredEntries, entry, isLocal);
}, },
), ),
SizedBox(
width: 2.0,
),
GestureDetector( GestureDetector(
child: SizedBox( child: Obx(
width: kDesktopFileTransferModifiedColWidth, () => SizedBox(
child: Tooltip( width: isLocal
waitDuration: ? _modifiedColWidthLocal.value
Duration(milliseconds: 500), : _modifiedColWidthRemote.value,
message: lastModifiedStr, child: Tooltip(
child: Text( waitDuration:
lastModifiedStr, Duration(milliseconds: 500),
style: TextStyle( message: lastModifiedStr,
fontSize: 12, child: Text(
color: MyTheme.darkGray, lastModifiedStr,
), style: TextStyle(
)), fontSize: 12,
color: MyTheme.darkGray,
),
)),
),
), ),
), ),
// Divider from header.
SizedBox( SizedBox(
width: 100, width: 2.0,
),
Expanded(
// width: 100,
child: GestureDetector( child: GestureDetector(
child: Tooltip( child: Tooltip(
waitDuration: Duration(milliseconds: 500), waitDuration: Duration(milliseconds: 500),
@ -450,7 +471,11 @@ class _FileManagerPageState extends State<FileManagerPage>
return Column( return Column(
children: [ children: [
// Header // Header
_buildFileBrowserHeader(context, isLocal), Row(
children: [
Expanded(child: _buildFileBrowserHeader(context, isLocal)),
],
),
// Body // Body
Expanded( Expanded(
child: ListView.builder( child: ListView.builder(
@ -472,7 +497,7 @@ class _FileManagerPageState extends State<FileManagerPage>
} }
void _jumpToEntry(bool isLocal, Entry entry, void _jumpToEntry(bool isLocal, Entry entry,
ScrollController scrollController, double rowHeight, String buffer) { ScrollController scrollController, double rowHeight) {
final entries = model.getCurrentDir(isLocal).entries; final entries = model.getCurrentDir(isLocal).entries;
final index = entries.indexOf(entry); final index = entries.indexOf(entry);
if (index == -1) { if (index == -1) {
@ -480,7 +505,7 @@ class _FileManagerPageState extends State<FileManagerPage>
} }
final selectedEntries = getSelectedItems(isLocal); final selectedEntries = getSelectedItems(isLocal);
final searchResult = final searchResult =
entries.where((element) => element.name.startsWith(buffer)); entries.where((element) => element == entry);
selectedEntries.clear(); selectedEntries.clear();
if (searchResult.isEmpty) { if (searchResult.isEmpty) {
return; return;
@ -1362,17 +1387,23 @@ class _FileManagerPageState extends State<FileManagerPage>
height: kDesktopFileTransferHeaderHeight, height: kDesktopFileTransferHeaderHeight,
child: Row( child: Row(
children: [ children: [
Text( Flexible(
name, flex: 2,
style: headerTextStyle, child: Text(
).marginSymmetric(horizontal: 4), name,
ascending.value != null style: headerTextStyle,
overflow: TextOverflow.ellipsis,
).marginSymmetric(horizontal: 4),
),
Flexible(
flex: 1,
child: ascending.value != null
? Icon( ? Icon(
ascending.value! ascending.value!
? Icons.keyboard_arrow_up_rounded ? Icons.keyboard_arrow_up_rounded
: Icons.keyboard_arrow_down_rounded, : Icons.keyboard_arrow_down_rounded,
) )
: const Offstage() : const Offstage())
], ],
), ),
), ),
@ -1386,16 +1417,48 @@ class _FileManagerPageState extends State<FileManagerPage>
} }
Widget _buildFileBrowserHeader(BuildContext context, bool isLocal) { Widget _buildFileBrowserHeader(BuildContext context, bool isLocal) {
return Row( final nameColWidth = isLocal ? _nameColWidthLocal : _nameColWidthRemote;
children: [ final modifiedColWidth =
headerItemFunc(kDesktopFileTransferNameColWidth, SortBy.name, isLocal ? _modifiedColWidthLocal : _modifiedColWidthRemote;
translate("Name"), isLocal), final padding = EdgeInsets.all(1.0);
headerItemFunc(kDesktopFileTransferModifiedColWidth, SortBy.modified, return SizedBox(
translate("Modified"), isLocal), height: kDesktopFileTransferHeaderHeight,
Expanded( child: Row(
child: children: [
headerItemFunc(null, SortBy.size, translate("Size"), isLocal)) Obx(
], () => headerItemFunc(
nameColWidth.value, SortBy.name, translate("Name"), isLocal),
),
DraggableDivider(
axis: Axis.vertical,
onPointerMove: (dx) {
nameColWidth.value += dx;
nameColWidth.value = min(
kDesktopFileTransferMaximumWidth,
max(kDesktopFileTransferMinimumWidth,
nameColWidth.value));
},
padding: padding,
),
Obx(
() => headerItemFunc(modifiedColWidth.value, SortBy.modified,
translate("Modified"), isLocal),
),
DraggableDivider(
axis: Axis.vertical,
onPointerMove: (dx) {
modifiedColWidth.value += dx;
modifiedColWidth.value = min(
kDesktopFileTransferMaximumWidth,
max(kDesktopFileTransferMinimumWidth,
modifiedColWidth.value));
},
padding: padding),
Expanded(
child:
headerItemFunc(null, SortBy.size, translate("Size"), isLocal))
],
),
); );
} }
} }

View File

@ -0,0 +1,53 @@
import 'package:flutter/material.dart';
import 'package:flutter/src/widgets/framework.dart';
import 'package:flutter/src/widgets/placeholder.dart';
class DraggableDivider extends StatefulWidget {
final Axis axis;
final double thickness;
final Color color;
final Function(double)? onPointerMove;
final VoidCallback? onHover;
final EdgeInsets padding;
const DraggableDivider({
super.key,
this.axis = Axis.horizontal,
this.thickness = 1.0,
this.color = const Color.fromARGB(200, 177, 175, 175),
this.onPointerMove,
this.padding = const EdgeInsets.symmetric(horizontal: 1.0),
this.onHover,
});
@override
State<DraggableDivider> createState() => _DraggableDividerState();
}
class _DraggableDividerState extends State<DraggableDivider> {
@override
Widget build(BuildContext context) {
return Listener(
onPointerMove: (event) {
final dl =
widget.axis == Axis.horizontal ? event.localDelta.dy : event.localDelta.dx;
widget.onPointerMove?.call(dl);
},
onPointerHover: (event) => widget.onHover?.call(),
child: MouseRegion(
cursor: SystemMouseCursors.resizeLeftRight,
child: Padding(
padding: widget.padding,
child: Container(
decoration: BoxDecoration(color: widget.color),
width: widget.axis == Axis.horizontal
? double.infinity
: widget.thickness,
height: widget.axis == Axis.horizontal
? widget.thickness
: double.infinity,
),
),
),
);
}
}

View File

@ -55,6 +55,7 @@ class TimeoutStringBuffer {
} }
ListSearchAction input(String ch) { ListSearchAction input(String ch) {
ch = ch.toLowerCase();
final curr = DateTime.now(); final curr = DateTime.now();
try { try {
if (curr.difference(_duration).inMilliseconds > timeoutMilliSec) { if (curr.difference(_duration).inMilliseconds > timeoutMilliSec) {