add: sortby, address link, platform, last jobs[1/2]

This commit is contained in:
Kingtous 2022-07-11 16:07:49 +08:00
parent 79217ca1d9
commit 5aded67597
5 changed files with 376 additions and 212 deletions

View File

@ -1,4 +1,3 @@
import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'dart:math'; import 'dart:math';
@ -26,8 +25,6 @@ class _FileManagerPageState extends State<FileManagerPage>
with AutomaticKeepAliveClientMixin { with AutomaticKeepAliveClientMixin {
final _localSelectedItems = SelectedItems(); final _localSelectedItems = SelectedItems();
final _remoteSelectedItems = SelectedItems(); final _remoteSelectedItems = SelectedItems();
final _breadCrumbLocalScroller = ScrollController();
final _breadCrumbRemoteScroller = ScrollController();
/// FFI with name file_transfer_id /// FFI with name file_transfer_id
FFI get _ffi => ffi('ft_${widget.id}'); FFI get _ffi => ffi('ft_${widget.id}');
@ -94,41 +91,11 @@ class _FileManagerPageState extends State<FileManagerPage>
icon: Icon(Icons.more_vert), icon: Icon(Icons.more_vert),
itemBuilder: (context) { itemBuilder: (context) {
return [ return [
PopupMenuItem(
child: Row(
children: [
Icon(Icons.refresh, color: Colors.black),
SizedBox(width: 5),
Text(translate("Refresh File"))
],
),
value: "refresh",
),
PopupMenuItem(
child: Row(
children: [
Icon(Icons.check, color: Colors.black),
SizedBox(width: 5),
Text(translate("Multi Select"))
],
),
value: "select",
),
PopupMenuItem(
child: Row(
children: [
Icon(Icons.folder_outlined, color: Colors.black),
SizedBox(width: 5),
Text(translate("Create Folder"))
],
),
value: "folder",
),
PopupMenuItem( PopupMenuItem(
child: Row( child: Row(
children: [ children: [
Icon( Icon(
model.currentShowHidden model.getCurrentShowHidden(isLocal)
? Icons.check_box_outlined ? Icons.check_box_outlined
: Icons.check_box_outline_blank, : Icons.check_box_outline_blank,
color: Colors.black), color: Colors.black),
@ -141,46 +108,7 @@ class _FileManagerPageState extends State<FileManagerPage>
]; ];
}, },
onSelected: (v) { onSelected: (v) {
if (v == "refresh") { if (v == "hidden") {
model.refresh();
} else if (v == "select") {
_localSelectedItems.clear();
model.toggleSelectMode();
} else if (v == "folder") {
final name = TextEditingController();
DialogManager.show((setState, close) => CustomAlertDialog(
title: Text(translate("Create Folder")),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextFormField(
decoration: InputDecoration(
labelText:
translate("Please enter the folder name"),
),
controller: name,
),
],
),
actions: [
TextButton(
style: flatButtonStyle,
onPressed: () => close(false),
child: Text(translate("Cancel"))),
ElevatedButton(
style: flatButtonStyle,
onPressed: () {
if (name.value.text.isNotEmpty) {
model.createDir(PathUtil.join(
model.currentDir.path,
name.value.text,
model.currentIsWindows));
close();
}
},
child: Text(translate("OK")))
]));
} else if (v == "hidden") {
model.toggleShowHidden(local: isLocal); model.toggleShowHidden(local: isLocal);
} }
}); });
@ -189,9 +117,23 @@ class _FileManagerPageState extends State<FileManagerPage>
Widget body({bool isLocal = false}) { Widget body({bool isLocal = false}) {
final fd = isLocal ? model.currentLocalDir : model.currentRemoteDir; final fd = isLocal ? model.currentLocalDir : model.currentRemoteDir;
final entries = fd.entries; final entries = fd.entries;
final sortIndex = (SortBy style) {
switch (style) {
case SortBy.Name:
return 1;
case SortBy.Type:
return 0;
case SortBy.Modified:
return 2;
case SortBy.Size:
return 3;
}
}(model.getSortStyle(isLocal));
final sortAscending =
isLocal ? model.localSortAscending : model.remoteSortAscending;
return Container( return Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white70, border: Border.all(color: Colors.grey)), color: Colors.white54, 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: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
@ -204,16 +146,36 @@ class _FileManagerPageState extends State<FileManagerPage>
child: SingleChildScrollView( child: SingleChildScrollView(
child: DataTable( child: DataTable(
showCheckboxColumn: true, showCheckboxColumn: true,
dataRowHeight: 30, dataRowHeight: 25,
headingRowHeight: 30,
columnSpacing: 8, columnSpacing: 8,
showBottomBorder: true,
sortColumnIndex: sortIndex,
sortAscending: sortAscending,
columns: [ columns: [
DataColumn(label: Text(translate(" "))), // icon DataColumn(label: Text(translate(" "))), // icon
DataColumn( DataColumn(
label: Text( label: Text(
translate("Name"), translate("Name"),
)), ),
DataColumn(label: Text(translate("Modified"))), onSort: (columnIndex, ascending) {
DataColumn(label: Text(translate("Size"))), 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: entries.map((entry) { rows: entries.map((entry) {
final sizeStr = entry.isFile final sizeStr = entry.isFile
@ -228,23 +190,29 @@ class _FileManagerPageState extends State<FileManagerPage>
} else { } else {
getSelectedItem(isLocal).remove(entry); getSelectedItem(isLocal).remove(entry);
} }
setState((){}); setState(() {});
} }
}, },
selected: getSelectedItem(isLocal).contains(entry), selected: getSelectedItem(isLocal).contains(entry),
cells: [ cells: [
// TODO: icon
DataCell(Icon( DataCell(Icon(
entry.isFile ? Icons.feed_outlined : Icons.folder, entry.isFile ? Icons.feed_outlined : Icons.folder,
size: 25)), size: 25)),
DataCell( DataCell(
ConstrainedBox( ConstrainedBox(
constraints: BoxConstraints(maxWidth: 100), constraints: BoxConstraints(maxWidth: 100),
child: Text(entry.name, child: Tooltip(
overflow: TextOverflow.ellipsis)), message: entry.name,
onTap: () { child: Text(entry.name,
overflow: TextOverflow.ellipsis),
)), onTap: () {
if (entry.isDirectory) { if (entry.isDirectory) {
model.openDirectory(entry.path, isLocal: isLocal); model.openDirectory(entry.path, isLocal: isLocal);
if (isLocal) {
_localSelectedItems.clear();
} else {
_remoteSelectedItems.clear();
}
} else { } else {
// Perform file-related tasks. // Perform file-related tasks.
final _selectedItems = getSelectedItem(isLocal); final _selectedItems = getSelectedItem(isLocal);
@ -253,7 +221,7 @@ class _FileManagerPageState extends State<FileManagerPage>
} else { } else {
_selectedItems.add(isLocal, entry); _selectedItems.add(isLocal, entry);
} }
setState((){}); setState(() {});
} }
}), }),
DataCell(Text( DataCell(Text(
@ -277,7 +245,7 @@ class _FileManagerPageState extends State<FileManagerPage>
) )
], ],
)), )),
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,
@ -388,9 +356,10 @@ class _FileManagerPageState extends State<FileManagerPage>
Widget statusList() { Widget statusList() {
return PreferredSize( return PreferredSize(
child: Container( child: Container(
margin: const EdgeInsets.only(top: 16.0,bottom: 16.0, right: 16.0), margin: const EdgeInsets.only(top: 16.0, bottom: 16.0, right: 16.0),
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
decoration: BoxDecoration(color: Colors.white70,border: Border.all(color: Colors.grey)), decoration: BoxDecoration(
color: Colors.white70, border: Border.all(color: Colors.grey)),
child: Obx( child: Obx(
() => ListView.builder( () => ListView.builder(
itemBuilder: (BuildContext context, int index) { itemBuilder: (BuildContext context, int index) {
@ -404,7 +373,9 @@ class _FileManagerPageState extends State<FileManagerPage>
Transform.rotate( Transform.rotate(
angle: item.isRemote ? pi : 0, angle: item.isRemote ? pi : 0,
child: Icon(Icons.send)), child: Icon(Icons.send)),
SizedBox(width: 16.0,), SizedBox(
width: 16.0,
),
Expanded( Expanded(
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
@ -412,15 +383,28 @@ class _FileManagerPageState extends State<FileManagerPage>
children: [ children: [
Tooltip( Tooltip(
message: item.jobName, message: item.jobName,
child: Text('${item.jobName}', child: Text(
'${item.jobName}',
maxLines: 1, maxLines: 1,
style: TextStyle(color: Colors.black45), overflow: TextOverflow.ellipsis,)), style: TextStyle(color: Colors.black45),
overflow: TextOverflow.ellipsis,
)),
Wrap( Wrap(
children: [ children: [
Text('${item.state.display()} ${max(0, item.fileNum)}/${item.fileCount} '), Text(
Text('${translate("files")} ${readableFileSize(item.totalSize.toDouble())} '), '${item.state.display()} ${max(0, item.fileNum)}/${item.fileCount} '),
Offstage(offstage: item.state != JobState.inProgress, child: Text('${readableFileSize(item.speed) + "/s"} ')), Text(
Text('${(item.finishedSize.toDouble() * 100 / item.totalSize.toDouble()).toStringAsFixed(2)}%'), '${translate("files")} ${readableFileSize(item.totalSize.toDouble())} '),
Offstage(
offstage:
item.state != JobState.inProgress,
child: Text(
'${readableFileSize(item.speed) + "/s"} ')),
Offstage(
offstage: item.totalSize <= 0,
child: Text(
'${(item.finishedSize.toDouble() * 100 / item.totalSize.toDouble()).toStringAsFixed(2)}%'),
),
], ],
), ),
], ],
@ -429,19 +413,26 @@ class _FileManagerPageState extends State<FileManagerPage>
Row( Row(
mainAxisAlignment: MainAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.end,
children: [ children: [
IconButton(icon: Icon(Icons.delete), onPressed: () { IconButton(
model.jobTable.removeAt(index); icon: Icon(Icons.delete),
model.cancelJob(item.id); onPressed: () {
},), model.jobTable.removeAt(index);
model.cancelJob(item.id);
},
),
], ],
) )
], ],
), ),
SizedBox(height: 8.0,), SizedBox(
Divider(height: 2.0, ) height: 8.0,
),
Divider(
height: 2.0,
)
], ],
); );
}, },
itemCount: model.jobTable.length, itemCount: model.jobTable.length,
), ),
), ),
@ -453,100 +444,175 @@ class _FileManagerPageState extends State<FileManagerPage>
model.goToParentDirectory(isLocal: isLocal); model.goToParentDirectory(isLocal: isLocal);
} }
breadCrumbScrollToEnd(bool isLocal) {
final controller =
isLocal ? _breadCrumbLocalScroller : _breadCrumbRemoteScroller;
Future.delayed(Duration(milliseconds: 200), () {
controller.animateTo(controller.position.maxScrollExtent,
duration: Duration(milliseconds: 200),
curve: Curves.fastLinearToSlowEaseIn);
});
}
Widget headTools(bool isLocal) => Container( Widget headTools(bool isLocal) => Container(
child: Row( child: Column(
children: [ children: [
Offstage( // symbols
offstage: isLocal, PreferredSize(
child: TextButton.icon( child: Row(
onPressed: (){ crossAxisAlignment: CrossAxisAlignment.center,
final items = getSelectedItem(isLocal); children: [
model.sendFiles(items, isRemote: true); Container(
}, icon: Transform.rotate( width: 50,
angle: isLocal ? 0 : pi, height: 50,
child: Icon( decoration: BoxDecoration(color: Colors.blue),
Icons.send padding: EdgeInsets.all(8.0),
child: FutureBuilder<String>(
future: _ffi.bind.sessionGetPlatform(
id: _ffi.id, isRemote: !isLocal),
builder: (context, snapshot) {
if (snapshot.hasData && snapshot.data!.isNotEmpty) {
return getPlatformImage('${snapshot.data}');
} else {
return CircularProgressIndicator(color: Colors.white,);
}
})),
Text(isLocal
? translate("Local Computer")
: translate("Remote Computer"))
.marginOnly(left: 8.0)
],
), ),
), label: Text(translate('Receive'))), preferredSize: Size(double.infinity, 70)),
), // buttons
Expanded(
child: Container(
decoration: BoxDecoration(
border: Border.all(color: Colors.black12)
),
child: BreadCrumb(
items: getPathBreadCrumbItems(() => model.goHome(isLocal: isLocal), (list) {
var path = "";
final currentHome = model.getCurrentHome(isLocal);
final currentIsWindows = model.getCurrentIsWindows(isLocal);
if (currentHome.startsWith(list[0])) {
// absolute path
for (var item in list) {
path = PathUtil.join(path, item, currentIsWindows);
}
} else {
path += currentHome;
for (var item in list) {
path = PathUtil.join(path, item, currentIsWindows);
}
}
model.openDirectory(path, isLocal: isLocal);
}, isLocal),
divider: Icon(Icons.chevron_right),
overflow: ScrollableOverflow(
controller: isLocal
? _breadCrumbLocalScroller
: _breadCrumbRemoteScroller),
),
)),
Row( Row(
children: [ children: [
IconButton( Row(
icon: Icon(Icons.arrow_upward), children: [
onPressed: () { IconButton(
goBack(isLocal: isLocal); onPressed: () {
}, model.goHome(isLocal: isLocal);
},
icon: Icon(Icons.home_outlined)),
IconButton(
icon: Icon(Icons.arrow_upward),
onPressed: () {
goBack(isLocal: isLocal);
},
),
menu(isLocal: isLocal),
],
), ),
PopupMenuButton<SortBy>( Expanded(
icon: Icon(Icons.sort), child: Container(
itemBuilder: (context) { decoration: BoxDecoration(
return SortBy.values border: Border.all(color: Colors.black12)),
.map((e) => PopupMenuItem( child: TextField(
child: decoration: InputDecoration(
Text(translate(e.toString().split(".").last)), border: InputBorder.none,
value: e, isDense: true,
)) prefix: Padding(padding: EdgeInsets.only(left: 4.0)),
.toList(); suffix: DropdownButton<String>(
isDense: true,
underline: Offstage(),
items: [
// TODO: favourite
DropdownMenuItem(child: Text('/'), value: '/',)
], onChanged: (path) {
if (path is String && path.isNotEmpty){
model.openDirectory(path, isLocal: isLocal);
}
})
),
controller: TextEditingController(
text: isLocal
? model.currentLocalDir.path
: model.currentRemoteDir.path),
onSubmitted: (path) {
model.openDirectory(path, isLocal: isLocal);
},
))),
IconButton(
onPressed: () {
model.refresh(isLocal: isLocal);
}, },
onSelected: (sort) { icon: Icon(Icons.refresh))
model.changeSortStyle(sort, isLocal: isLocal);
}),
menu(isLocal: isLocal),
], ],
), ),
Offstage( Row(
offstage: !isLocal, textDirection: isLocal ? TextDirection.ltr : TextDirection.rtl,
child: TextButton.icon( children: [
onPressed: (){ Expanded(
final items = getSelectedItem(isLocal); child: Row(
model.sendFiles(items, isRemote: !isLocal); mainAxisAlignment:
}, icon: Transform.rotate( isLocal ? MainAxisAlignment.start : MainAxisAlignment.end,
angle: isLocal ? 0 : pi, children: [
child: Icon( IconButton(
Icons.send onPressed: () {
final name = TextEditingController();
DialogManager.show((setState, close) =>
CustomAlertDialog(
title: Text(translate("Create Folder")),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextFormField(
decoration: InputDecoration(
labelText: translate(
"Please enter the folder name"),
),
controller: name,
),
],
),
actions: [
TextButton(
style: flatButtonStyle,
onPressed: () => close(false),
child: Text(translate("Cancel"))),
ElevatedButton(
style: flatButtonStyle,
onPressed: () {
if (name.value.text.isNotEmpty) {
model.createDir(
PathUtil.join(
model
.getCurrentDir(isLocal)
.path,
name.value.text,
model.getCurrentIsWindows(
isLocal)),
isLocal: isLocal);
close();
}
},
child: Text(translate("OK")))
]));
},
icon: Icon(Icons.create_new_folder_outlined)),
IconButton(
onPressed: () async {
final items = isLocal
? _localSelectedItems
: _remoteSelectedItems;
debugPrint("remove items: ${items.items}");
await (model.removeAction(items));
items.clear();
},
icon: Icon(Icons.delete_forever_outlined)),
],
),
), ),
), label: Text(translate('Send'))), TextButton.icon(
) onPressed: () {
final items = getSelectedItem(isLocal);
model.sendFiles(items, isRemote: !isLocal);
},
icon: Transform.rotate(
angle: isLocal ? 0 : pi,
child: Icon(
Icons.send,
color: Colors.black54,
),
),
label: Text(
isLocal ? translate('Send') : translate('Receive'),
style: TextStyle(
color: Colors.black54,
),
)),
],
).marginOnly(top: 8.0)
], ],
)); ));
@ -578,7 +644,8 @@ class _FileManagerPageState extends State<FileManagerPage>
Widget? bottomSheet() { Widget? bottomSheet() {
final state = model.jobState; final state = model.jobState;
final isOtherPage = _localSelectedItems.isOtherPage(model.isLocal); final isOtherPage = _localSelectedItems.isOtherPage(model.isLocal);
final selectedItemsLen = "${_localSelectedItems.length} ${translate("items")}"; final selectedItemsLen =
"${_localSelectedItems.length} ${translate("items")}";
final local = _localSelectedItems.isLocal == null final local = _localSelectedItems.isLocal == null
? "" ? ""
: " [${_localSelectedItems.isLocal! ? translate("Local") : translate("Remote")}]"; : " [${_localSelectedItems.isLocal! ? translate("Local") : translate("Remote")}]";
@ -677,6 +744,15 @@ class _FileManagerPageState extends State<FileManagerPage>
@override @override
bool get wantKeepAlive => true; bool get wantKeepAlive => true;
/// Get the image for the current [platform].
Widget getPlatformImage(String platform) {
platform = platform.toLowerCase();
if (platform == 'mac os')
platform = 'mac';
else if (platform != 'linux' && platform != 'android') platform = 'win';
return Image.asset('assets/$platform.png', width: 25, height: 25);
}
} }
class BottomSheetBody extends StatelessWidget { class BottomSheetBody extends StatelessWidget {

View File

@ -40,6 +40,20 @@ class FileModel extends ChangeNotifier {
SortBy get sortStyle => _sortStyle; SortBy get sortStyle => _sortStyle;
SortBy _localSortStyle = SortBy.Name;
bool _localSortAscending = true;
bool _remoteSortAscending = true;
SortBy _remoteSortStyle = SortBy.Name;
bool get localSortAscending => _localSortAscending;
SortBy getSortStyle(bool isLocal){
return isLocal ? _localSortStyle : _remoteSortStyle;
}
FileDirectory _currentLocalDir = FileDirectory(); FileDirectory _currentLocalDir = FileDirectory();
FileDirectory get currentLocalDir => _currentLocalDir; FileDirectory get currentLocalDir => _currentLocalDir;
@ -50,6 +64,10 @@ class FileModel extends ChangeNotifier {
FileDirectory get currentDir => _isLocal ? currentLocalDir : currentRemoteDir; FileDirectory get currentDir => _isLocal ? currentLocalDir : currentRemoteDir;
FileDirectory getCurrentDir(bool isLocal) {
return isLocal ? currentLocalDir : currentRemoteDir;
}
String get currentHome => _isLocal ? _localOption.home : _remoteOption.home; String get currentHome => _isLocal ? _localOption.home : _remoteOption.home;
String getCurrentHome(bool isLocal) { String getCurrentHome(bool isLocal) {
@ -92,6 +110,10 @@ class FileModel extends ChangeNotifier {
bool get currentShowHidden => bool get currentShowHidden =>
_isLocal ? _localOption.showHidden : _remoteOption.showHidden; _isLocal ? _localOption.showHidden : _remoteOption.showHidden;
bool getCurrentShowHidden(bool isLocal) {
return isLocal ? _localOption.showHidden : _remoteOption.showHidden;
}
bool get currentIsWindows => bool get currentIsWindows =>
_isLocal ? _localOption.isWindows : _remoteOption.isWindows; _isLocal ? _localOption.isWindows : _remoteOption.isWindows;
@ -163,13 +185,15 @@ class FileModel extends ChangeNotifier {
try { try {
final fd = FileDirectory.fromJson(jsonDecode(evt['value'])); final fd = FileDirectory.fromJson(jsonDecode(evt['value']));
fd.format(_remoteOption.isWindows, sort: _sortStyle); fd.format(_remoteOption.isWindows, sort: _sortStyle);
if (fd.id > 0){ if (fd.id > 0) {
final jobIndex = getJob(fd.id); final jobIndex = getJob(fd.id);
if (jobIndex != -1){ if (jobIndex != -1) {
final job = jobTable[jobIndex]; final job = jobTable[jobIndex];
var totalSize = 0; var totalSize = 0;
var fileCount = fd.entries.length; var fileCount = fd.entries.length;
fd.entries.forEach((element) {totalSize += element.size;}); fd.entries.forEach((element) {
totalSize += element.size;
});
job.totalSize = totalSize; job.totalSize = totalSize;
job.fileCount = fileCount; job.fileCount = fileCount;
debugPrint("update receive details:${fd.path}"); debugPrint("update receive details:${fd.path}");
@ -179,11 +203,11 @@ class FileModel extends ChangeNotifier {
debugPrint("init remote home:${fd.path}"); debugPrint("init remote home:${fd.path}");
_currentRemoteDir = fd; _currentRemoteDir = fd;
} }
notifyListeners(); }
return; finally {}
} finally {}
} }
_fileFetcher.tryCompleteTask(evt['value'], evt['is_local']); _fileFetcher.tryCompleteTask(evt['value'], evt['is_local']);
notifyListeners();
} }
jobDone(Map<String, dynamic> evt) { jobDone(Map<String, dynamic> evt) {
@ -307,10 +331,10 @@ class FileModel extends ChangeNotifier {
_remoteOption.clear(); _remoteOption.clear();
} }
refresh() { refresh({bool? isLocal}) {
if (isDesktop) { if (isDesktop) {
openDirectory(currentRemoteDir.path); isLocal = isLocal ?? _isLocal;
openDirectory(currentLocalDir.path); isLocal ? openDirectory(currentLocalDir.path) : openDirectory(currentRemoteDir.path);
} else { } else {
openDirectory(currentDir.path); openDirectory(currentDir.path);
} }
@ -344,7 +368,8 @@ class FileModel extends ChangeNotifier {
} }
goHome({bool? isLocal}) { goHome({bool? isLocal}) {
openDirectory(currentHome, isLocal: isLocal); isLocal = isLocal ?? _isLocal;
openDirectory(getCurrentHome(isLocal), isLocal: isLocal);
} }
goToParentDirectory({bool? isLocal}) { goToParentDirectory({bool? isLocal}) {
@ -598,7 +623,8 @@ class FileModel extends ChangeNotifier {
_ffi.target?.bind.sessionRemoveAllEmptyDirs(id: '${_ffi.target?.id}', actId: _jobId, path: path, isRemote: !isLocal); _ffi.target?.bind.sessionRemoveAllEmptyDirs(id: '${_ffi.target?.id}', actId: _jobId, path: path, isRemote: !isLocal);
} }
createDir(String path) async { createDir(String path, {bool? isLocal}) async {
isLocal = isLocal ?? this.isLocal;
_jobId++; _jobId++;
_ffi.target?.bind.sessionCreateDir(id: '${_ffi.target?.id}', actId: _jobId, path: path, isRemote: !isLocal); _ffi.target?.bind.sessionCreateDir(id: '${_ffi.target?.id}', actId: _jobId, path: path, isRemote: !isLocal);
} }
@ -608,16 +634,20 @@ class FileModel extends ChangeNotifier {
jobReset(); jobReset();
} }
changeSortStyle(SortBy sort, {bool? isLocal}) { changeSortStyle(SortBy sort, {bool? isLocal, bool ascending = true}) {
_sortStyle = sort; _sortStyle = sort;
if (isLocal == null) { if (isLocal == null) {
// compatible for mobile logic // compatible for mobile logic
_currentLocalDir.changeSortStyle(sort); _currentLocalDir.changeSortStyle(sort, ascending: ascending);
_currentRemoteDir.changeSortStyle(sort); _currentRemoteDir.changeSortStyle(sort, ascending: ascending);
_localSortStyle = sort; _localSortAscending = ascending;
_remoteSortStyle = sort; _remoteSortAscending = ascending;
} else if (isLocal) { } else if (isLocal) {
_currentLocalDir.changeSortStyle(sort); _currentLocalDir.changeSortStyle(sort, ascending: ascending);
_localSortStyle = sort; _localSortAscending = ascending;
} else { } else {
_currentRemoteDir.changeSortStyle(sort); _currentRemoteDir.changeSortStyle(sort, ascending: ascending);
_remoteSortStyle = sort; _remoteSortAscending = ascending;
} }
notifyListeners(); notifyListeners();
} }
@ -640,6 +670,8 @@ class FileModel extends ChangeNotifier {
} }
debugPrint("update folder files: ${info}"); debugPrint("update folder files: ${info}");
} }
bool get remoteSortAscending => _remoteSortAscending;
} }
class JobResultListener<T> { class JobResultListener<T> {
@ -809,8 +841,8 @@ class FileDirectory {
} }
} }
changeSortStyle(SortBy sort) { changeSortStyle(SortBy sort, {bool ascending = true}) {
entries = _sortList(entries, sort); entries = _sortList(entries, sort, ascending);
} }
clear() { clear() {
@ -929,7 +961,7 @@ class DirectoryOption {
} }
// code from file_manager pkg after edit // code from file_manager pkg after edit
List<Entry> _sortList(List<Entry> list, SortBy sortType) { List<Entry> _sortList(List<Entry> list, SortBy sortType, bool ascending) {
if (sortType == SortBy.Name) { if (sortType == SortBy.Name) {
// making list of only folders. // making list of only folders.
final dirs = list.where((element) => element.isDirectory).toList(); final dirs = list.where((element) => element.isDirectory).toList();
@ -942,7 +974,7 @@ List<Entry> _sortList(List<Entry> list, SortBy sortType) {
files.sort((a, b) => a.name.toLowerCase().compareTo(b.name.toLowerCase())); files.sort((a, b) => a.name.toLowerCase().compareTo(b.name.toLowerCase()));
// first folders will go to list (if available) then files will go to list. // first folders will go to list (if available) then files will go to list.
return [...dirs, ...files]; return ascending ? [...dirs, ...files] : [...dirs.reversed.toList(), ...files.reversed.toList()];
} else if (sortType == SortBy.Modified) { } else if (sortType == SortBy.Modified) {
// making the list of Path & DateTime // making the list of Path & DateTime
List<_PathStat> _pathStat = []; List<_PathStat> _pathStat = [];
@ -957,7 +989,7 @@ List<Entry> _sortList(List<Entry> list, SortBy sortType) {
list.sort((a, b) => _pathStat list.sort((a, b) => _pathStat
.indexWhere((element) => element.path == a.name) .indexWhere((element) => element.path == a.name)
.compareTo(_pathStat.indexWhere((element) => element.path == b.name))); .compareTo(_pathStat.indexWhere((element) => element.path == b.name)));
return list; return ascending ? list : list.reversed.toList();
} else if (sortType == SortBy.Type) { } else if (sortType == SortBy.Type) {
// making list of only folders. // making list of only folders.
final dirs = list.where((element) => element.isDirectory).toList(); final dirs = list.where((element) => element.isDirectory).toList();
@ -974,7 +1006,7 @@ List<Entry> _sortList(List<Entry> list, SortBy sortType) {
.split('.') .split('.')
.last .last
.compareTo(b.name.toLowerCase().split('.').last)); .compareTo(b.name.toLowerCase().split('.').last));
return [...dirs, ...files]; return ascending ? [...dirs, ...files]: [...dirs.reversed.toList(), ...files.reversed.toList()];
} else if (sortType == SortBy.Size) { } else if (sortType == SortBy.Size) {
// create list of path and size // create list of path and size
Map<String, int> _sizeMap = {}; Map<String, int> _sizeMap = {};
@ -999,7 +1031,7 @@ List<Entry> _sortList(List<Entry> list, SortBy sortType) {
.indexWhere((element) => element.key == a.name) .indexWhere((element) => element.key == a.name)
.compareTo( .compareTo(
_sizeMapList.indexWhere((element) => element.key == b.name))); _sizeMapList.indexWhere((element) => element.key == b.name)));
return [...dirs, ...files]; return ascending ? [...dirs, ...files]: [...dirs.reversed.toList(), ...files.reversed.toList()];
} }
return []; return [];
} }

View File

@ -102,6 +102,10 @@ void wire_session_peer_option(int64_t port_,
struct wire_uint_8_list *name, struct wire_uint_8_list *name,
struct wire_uint_8_list *value); struct wire_uint_8_list *value);
void wire_session_get_peer_option(int64_t port_,
struct wire_uint_8_list *id,
struct wire_uint_8_list *name);
void wire_session_input_os_password(int64_t port_, void wire_session_input_os_password(int64_t port_,
struct wire_uint_8_list *id, struct wire_uint_8_list *id,
struct wire_uint_8_list *value); struct wire_uint_8_list *value);
@ -139,7 +143,8 @@ void wire_session_read_dir_recursive(int64_t port_,
struct wire_uint_8_list *id, struct wire_uint_8_list *id,
int32_t act_id, int32_t act_id,
struct wire_uint_8_list *path, struct wire_uint_8_list *path,
bool is_remote); bool is_remote,
bool show_hidden);
void wire_session_remove_all_empty_dirs(int64_t port_, void wire_session_remove_all_empty_dirs(int64_t port_,
struct wire_uint_8_list *id, struct wire_uint_8_list *id,
@ -197,6 +202,7 @@ static int64_t dummy_method_to_enforce_bundling(void) {
dummy_var ^= ((int64_t) (void*) wire_session_send_chat); dummy_var ^= ((int64_t) (void*) wire_session_send_chat);
dummy_var ^= ((int64_t) (void*) wire_session_send_mouse); dummy_var ^= ((int64_t) (void*) wire_session_send_mouse);
dummy_var ^= ((int64_t) (void*) wire_session_peer_option); dummy_var ^= ((int64_t) (void*) wire_session_peer_option);
dummy_var ^= ((int64_t) (void*) wire_session_get_peer_option);
dummy_var ^= ((int64_t) (void*) wire_session_input_os_password); dummy_var ^= ((int64_t) (void*) wire_session_input_os_password);
dummy_var ^= ((int64_t) (void*) wire_session_read_remote_dir); dummy_var ^= ((int64_t) (void*) wire_session_read_remote_dir);
dummy_var ^= ((int64_t) (void*) wire_session_send_files); dummy_var ^= ((int64_t) (void*) wire_session_send_files);

View File

@ -5,6 +5,8 @@ use std::{
use flutter_rust_bridge::{StreamSink, ZeroCopyBuffer}; use flutter_rust_bridge::{StreamSink, ZeroCopyBuffer};
use hbb_common::config::PeerConfig;
use hbb_common::fs::TransferJobMeta;
use hbb_common::{ use hbb_common::{
allow_err, allow_err,
compress::decompress, compress::decompress,
@ -464,6 +466,41 @@ impl Session {
log::debug!("{:?}", msg_out); log::debug!("{:?}", msg_out);
self.send_msg(msg_out); self.send_msg(msg_out);
} }
pub fn load_config(&self) -> PeerConfig {
load_config(&self.id)
}
pub fn get_platform(&self, is_remote: bool) -> String {
if is_remote {
self.lc.read().unwrap().info.platform.clone()
} else {
whoami::platform().to_string()
}
}
pub fn load_last_jobs(&self) {
let pc = self.load_config();
if pc.transfer.write_jobs.is_empty() && pc.transfer.read_jobs.is_empty() {
// no last jobs
return;
}
let mut cnt = 1;
for job_str in pc.transfer.read_jobs.iter() {
if !job_str.is_empty() {
self.push_event("addJob", vec![("value", job_str)]);
cnt += 1;
println!("restore read_job: {:?}", job);
}
}
for job_str in pc.transfer.write_jobs.iter() {
if !job_str.is_empty() {
self.push_event("addJob", vec![("value", job_str)]);
cnt += 1;
println!("restore write_job: {:?}", job);
}
}
}
} }
impl FileManager for Session {} impl FileManager for Session {}

View File

@ -339,6 +339,19 @@ pub fn session_read_local_dir_sync(id: String, path: String, show_hidden: bool)
"".to_string() "".to_string()
} }
pub fn session_get_platform(id: String, is_remote: bool) -> String {
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
return session.get_platform(is_remote);
}
"".to_string()
}
pub fn session_load_last_transfer_jobs(id: String) {
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
return session.load_last_jobs();
}
}
/// FFI for **get** commands which are idempotent. /// FFI for **get** commands which are idempotent.
/// Return result in c string. /// Return result in c string.
/// ///