Merge pull request #3214 from Kingtous/master
refactor: use listview for the file lists
This commit is contained in:
commit
38a0b8c960
@ -50,6 +50,18 @@ const int kMobileMaxDisplayHeight = 1280;
|
|||||||
const int kDesktopMaxDisplayWidth = 1920;
|
const int kDesktopMaxDisplayWidth = 1920;
|
||||||
const int kDesktopMaxDisplayHeight = 1080;
|
const int kDesktopMaxDisplayHeight = 1080;
|
||||||
|
|
||||||
|
const double kDesktopFileTransferNameColWidth = 200;
|
||||||
|
const double kDesktopFileTransferModifiedColWidth = 120;
|
||||||
|
const double kDesktopFileTransferRowHeight = 25.0;
|
||||||
|
const double kDesktopFileTransferHeaderHeight = 25.0;
|
||||||
|
|
||||||
|
// https://en.wikipedia.org/wiki/Non-breaking_space
|
||||||
|
const int $nbsp = 0x00A0;
|
||||||
|
|
||||||
|
extension StringExtension on String {
|
||||||
|
String get nonBreaking => replaceAll(' ', String.fromCharCode($nbsp));
|
||||||
|
}
|
||||||
|
|
||||||
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;
|
||||||
|
@ -236,10 +236,7 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: SingleChildScrollView(
|
child: _buildFileList(context, isLocal, scrollController),
|
||||||
controller: scrollController,
|
|
||||||
child: _buildDataTable(context, isLocal, scrollController),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
)),
|
)),
|
||||||
@ -248,25 +245,11 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildDataTable(
|
Widget _buildFileList(
|
||||||
BuildContext context, bool isLocal, ScrollController scrollController) {
|
BuildContext context, bool isLocal, ScrollController scrollController) {
|
||||||
const rowHeight = 25.0;
|
|
||||||
final fd = model.getCurrentDir(isLocal);
|
final fd = model.getCurrentDir(isLocal);
|
||||||
final entries = fd.entries;
|
final entries = fd.entries;
|
||||||
final sortIndex = (SortBy style) {
|
final selectedEntries = getSelectedItems(isLocal);
|
||||||
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 MouseRegion(
|
return MouseRegion(
|
||||||
onEnter: (evt) {
|
onEnter: (evt) {
|
||||||
@ -287,7 +270,6 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
onNext: (buffer) {
|
onNext: (buffer) {
|
||||||
debugPrint("searching next for $buffer");
|
debugPrint("searching next for $buffer");
|
||||||
assert(buffer.length == 1);
|
assert(buffer.length == 1);
|
||||||
final selectedEntries = getSelectedItems(isLocal);
|
|
||||||
assert(selectedEntries.length <= 1);
|
assert(selectedEntries.length <= 1);
|
||||||
var skipCount = 0;
|
var skipCount = 0;
|
||||||
if (selectedEntries.items.isNotEmpty) {
|
if (selectedEntries.items.isNotEmpty) {
|
||||||
@ -312,7 +294,8 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_jumpToEntry(
|
_jumpToEntry(
|
||||||
isLocal, searchResult.first, scrollController, rowHeight, buffer);
|
isLocal, searchResult.first, scrollController,
|
||||||
|
kDesktopFileTransferRowHeight, buffer);
|
||||||
},
|
},
|
||||||
onSearch: (buffer) {
|
onSearch: (buffer) {
|
||||||
debugPrint("searching for $buffer");
|
debugPrint("searching for $buffer");
|
||||||
@ -327,7 +310,8 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_jumpToEntry(
|
_jumpToEntry(
|
||||||
isLocal, searchResult.first, scrollController, rowHeight, buffer);
|
isLocal, searchResult.first, scrollController,
|
||||||
|
kDesktopFileTransferRowHeight, buffer);
|
||||||
},
|
},
|
||||||
child: ObxValue<RxString>(
|
child: ObxValue<RxString>(
|
||||||
(searchText) {
|
(searchText) {
|
||||||
@ -336,118 +320,120 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
return element.name.contains(searchText.value);
|
return element.name.contains(searchText.value);
|
||||||
}).toList(growable: false)
|
}).toList(growable: false)
|
||||||
: entries;
|
: entries;
|
||||||
return DataTable(
|
final rows = filteredEntries.map((entry) {
|
||||||
key: ValueKey(isLocal ? 0 : 1),
|
|
||||||
showCheckboxColumn: false,
|
|
||||||
dataRowHeight: rowHeight,
|
|
||||||
headingRowHeight: 30,
|
|
||||||
horizontalMargin: 8,
|
|
||||||
columnSpacing: 8,
|
|
||||||
showBottomBorder: true,
|
|
||||||
sortColumnIndex: sortIndex,
|
|
||||||
sortAscending: sortAscending,
|
|
||||||
columns: [
|
|
||||||
DataColumn(
|
|
||||||
label: Text(
|
|
||||||
translate("Name"),
|
|
||||||
).marginSymmetric(horizontal: 4),
|
|
||||||
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 =
|
final sizeStr =
|
||||||
entry.isFile ? readableFileSize(entry.size.toDouble()) : "";
|
entry.isFile ? readableFileSize(entry.size.toDouble()) : "";
|
||||||
final lastModifiedStr = entry.isDrive
|
final lastModifiedStr = entry.isDrive
|
||||||
? " "
|
? " "
|
||||||
: "${entry.lastModified().toString().replaceAll(".000", "")} ";
|
: "${entry.lastModified().toString().replaceAll(".000", "")} ";
|
||||||
return DataRow(
|
final isSelected = selectedEntries.contains(entry);
|
||||||
key: ValueKey(entry.name),
|
return SizedBox(
|
||||||
onSelectChanged: (s) {
|
key: ValueKey(entry.name),
|
||||||
_onSelectedChanged(getSelectedItems(isLocal),
|
height: kDesktopFileTransferRowHeight,
|
||||||
filteredEntries, entry, isLocal);
|
child: Column(
|
||||||
},
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
selected: getSelectedItems(isLocal).contains(entry),
|
children: [
|
||||||
cells: [
|
const Divider(
|
||||||
DataCell(
|
height: 1,
|
||||||
Container(
|
),
|
||||||
width: 200,
|
Expanded(
|
||||||
child: Tooltip(
|
child: Ink(
|
||||||
waitDuration: Duration(milliseconds: 500),
|
decoration: isSelected
|
||||||
message: entry.name,
|
? BoxDecoration(color: Theme.of(context).hoverColor)
|
||||||
child: Row(children: [
|
: null,
|
||||||
entry.isDrive
|
child: InkWell(
|
||||||
? Image(
|
child: Row(children: [
|
||||||
image: iconHardDrive,
|
GestureDetector(
|
||||||
fit: BoxFit.scaleDown,
|
child: Container(
|
||||||
color: Theme.of(context)
|
width: kDesktopFileTransferNameColWidth,
|
||||||
.iconTheme
|
child: Tooltip(
|
||||||
.color
|
waitDuration: Duration(milliseconds: 500),
|
||||||
?.withOpacity(0.7))
|
message: entry.name,
|
||||||
.paddingAll(4)
|
child: Row(children: [
|
||||||
: Icon(
|
entry.isDrive
|
||||||
entry.isFile
|
? Image(
|
||||||
? Icons.feed_outlined
|
image: iconHardDrive,
|
||||||
: Icons.folder,
|
fit: BoxFit.scaleDown,
|
||||||
size: 20,
|
color: Theme.of(context)
|
||||||
color: Theme.of(context)
|
.iconTheme
|
||||||
.iconTheme
|
.color
|
||||||
.color
|
?.withOpacity(0.7))
|
||||||
?.withOpacity(0.7),
|
.paddingAll(4)
|
||||||
).marginSymmetric(horizontal: 2),
|
: Icon(
|
||||||
Expanded(
|
entry.isFile
|
||||||
child: Text(entry.name,
|
? Icons.feed_outlined
|
||||||
overflow: TextOverflow.ellipsis))
|
: Icons.folder,
|
||||||
]),
|
size: 20,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.iconTheme
|
||||||
|
.color
|
||||||
|
?.withOpacity(0.7),
|
||||||
|
).marginSymmetric(horizontal: 2),
|
||||||
|
Expanded(
|
||||||
|
child: Text(entry.name.nonBreaking,
|
||||||
|
overflow: TextOverflow.ellipsis))
|
||||||
|
]),
|
||||||
|
)),
|
||||||
|
onTap: () {
|
||||||
|
final items = getSelectedItems(isLocal);
|
||||||
|
// handle double click
|
||||||
|
if (_checkDoubleClick(entry)) {
|
||||||
|
openDirectory(entry.path, isLocal: isLocal);
|
||||||
|
items.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_onSelectedChanged(
|
||||||
|
items, filteredEntries, entry, isLocal);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
GestureDetector(
|
||||||
|
child: SizedBox(
|
||||||
|
width: kDesktopFileTransferModifiedColWidth,
|
||||||
|
child: Tooltip(
|
||||||
|
waitDuration: Duration(milliseconds: 500),
|
||||||
|
message: lastModifiedStr,
|
||||||
|
child: Text(
|
||||||
|
lastModifiedStr,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12, color: MyTheme.darkGray),
|
||||||
|
)),
|
||||||
)),
|
)),
|
||||||
onTap: () {
|
GestureDetector(
|
||||||
final items = getSelectedItems(isLocal);
|
child: Tooltip(
|
||||||
|
waitDuration: Duration(milliseconds: 500),
|
||||||
// handle double click
|
message: sizeStr,
|
||||||
if (_checkDoubleClick(entry)) {
|
child: Text(
|
||||||
openDirectory(entry.path, isLocal: isLocal);
|
sizeStr,
|
||||||
items.clear();
|
overflow: TextOverflow.ellipsis,
|
||||||
return;
|
style: TextStyle(
|
||||||
}
|
fontSize: 10,
|
||||||
_onSelectedChanged(
|
color: MyTheme.darkGray),
|
||||||
items, filteredEntries, entry, isLocal);
|
))),
|
||||||
},
|
]),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
DataCell(FittedBox(
|
),
|
||||||
child: Tooltip(
|
],
|
||||||
waitDuration: Duration(milliseconds: 500),
|
),
|
||||||
message: lastModifiedStr,
|
);
|
||||||
child: Text(
|
}).toList(growable: false);
|
||||||
lastModifiedStr,
|
|
||||||
style: TextStyle(
|
return Column(
|
||||||
fontSize: 12, color: MyTheme.darkGray),
|
children: [
|
||||||
)))),
|
// Header
|
||||||
DataCell(Tooltip(
|
_buildFileBrowserHeader(context, isLocal),
|
||||||
waitDuration: Duration(milliseconds: 500),
|
// Body
|
||||||
message: sizeStr,
|
Expanded(
|
||||||
child: Text(
|
child: ListView.builder(
|
||||||
sizeStr,
|
controller: scrollController,
|
||||||
overflow: TextOverflow.ellipsis,
|
itemExtent: kDesktopFileTransferRowHeight,
|
||||||
style: TextStyle(
|
itemBuilder: (context, index) {
|
||||||
fontSize: 10, color: MyTheme.darkGray),
|
return rows[index];
|
||||||
))),
|
},
|
||||||
]);
|
itemCount: rows.length,
|
||||||
}).toList(growable: false),
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
isLocal ? _searchTextLocal : _searchTextRemote,
|
isLocal ? _searchTextLocal : _searchTextRemote,
|
||||||
@ -1133,4 +1119,60 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget headerItemFunc(
|
||||||
|
double? width, SortBy sortBy, String name, bool isLocal) {
|
||||||
|
final headerTextStyle =
|
||||||
|
Theme.of(context).dataTableTheme.headingTextStyle ?? TextStyle();
|
||||||
|
return ObxValue<Rx<bool?>>(
|
||||||
|
(ascending) => InkWell(
|
||||||
|
onTap: () {
|
||||||
|
if (ascending.value == null) {
|
||||||
|
ascending.value = true;
|
||||||
|
} else {
|
||||||
|
ascending.value = !ascending.value!;
|
||||||
|
}
|
||||||
|
model.changeSortStyle(sortBy,
|
||||||
|
isLocal: isLocal, ascending: ascending.value!);
|
||||||
|
},
|
||||||
|
child: SizedBox(
|
||||||
|
width: width,
|
||||||
|
height: kDesktopFileTransferHeaderHeight,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
name,
|
||||||
|
style: headerTextStyle,
|
||||||
|
).marginSymmetric(
|
||||||
|
horizontal: sortBy == SortBy.name ? 4 : 0.0),
|
||||||
|
ascending.value != null
|
||||||
|
? Icon(ascending.value!
|
||||||
|
? Icons.arrow_upward
|
||||||
|
: Icons.arrow_downward)
|
||||||
|
: const Offstage()
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
), () {
|
||||||
|
if (model.getSortStyle(isLocal) == sortBy) {
|
||||||
|
return model.getSortAscending(isLocal).obs;
|
||||||
|
} else {
|
||||||
|
return Rx<bool?>(null);
|
||||||
|
}
|
||||||
|
}());
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildFileBrowserHeader(BuildContext context, bool isLocal) {
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
headerItemFunc(kDesktopFileTransferNameColWidth, SortBy.name,
|
||||||
|
translate("Name"), isLocal),
|
||||||
|
headerItemFunc(kDesktopFileTransferModifiedColWidth, SortBy.modified,
|
||||||
|
translate("Modified"), isLocal),
|
||||||
|
Expanded(
|
||||||
|
child:
|
||||||
|
headerItemFunc(null, SortBy.size, translate("Size"), isLocal))
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -439,9 +439,12 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
|||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
border: Border.all(color: MyTheme.border),
|
border: Border.all(color: MyTheme.border),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: SingleChildScrollView(
|
||||||
mainAxisSize: MainAxisSize.min,
|
scrollDirection: Axis.horizontal,
|
||||||
children: menubarItems,
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: menubarItems,
|
||||||
|
),
|
||||||
)),
|
)),
|
||||||
_buildDraggableShowHide(context),
|
_buildDraggableShowHide(context),
|
||||||
]));
|
]));
|
||||||
|
@ -75,6 +75,10 @@ class FileModel extends ChangeNotifier {
|
|||||||
return isLocal ? _localSortStyle : _remoteSortStyle;
|
return isLocal ? _localSortStyle : _remoteSortStyle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool getSortAscending(bool isLocal) {
|
||||||
|
return isLocal ? _localSortAscending : _remoteSortAscending;
|
||||||
|
}
|
||||||
|
|
||||||
FileDirectory _currentLocalDir = FileDirectory();
|
FileDirectory _currentLocalDir = FileDirectory();
|
||||||
|
|
||||||
FileDirectory get currentLocalDir => _currentLocalDir;
|
FileDirectory get currentLocalDir => _currentLocalDir;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user