Merge pull request #3352 from Kingtous/master
opt: fs explorer resizable & search next for loop
This commit is contained in:
commit
b6afd2c437
@ -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;
|
||||||
|
|
||||||
|
@ -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))
|
||||||
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
53
flutter/lib/desktop/widgets/dragable_divider.dart
Normal file
53
flutter/lib/desktop/widgets/dragable_divider.dart
Normal 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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -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) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user