From 5aded67597fc497a25f08349f6546f9a408f3a17 Mon Sep 17 00:00:00 2001 From: Kingtous Date: Mon, 11 Jul 2022 16:07:49 +0800 Subject: [PATCH] add: sortby, address link, platform, last jobs[1/2] --- .../lib/desktop/pages/file_manager_page.dart | 452 ++++++++++-------- flutter/lib/models/file_model.dart | 78 ++- flutter/macos/Runner/bridge_generated.h | 8 +- src/flutter.rs | 37 ++ src/flutter_ffi.rs | 13 + 5 files changed, 376 insertions(+), 212 deletions(-) diff --git a/flutter/lib/desktop/pages/file_manager_page.dart b/flutter/lib/desktop/pages/file_manager_page.dart index 5de1c206c..e3ffa9d0c 100644 --- a/flutter/lib/desktop/pages/file_manager_page.dart +++ b/flutter/lib/desktop/pages/file_manager_page.dart @@ -1,4 +1,3 @@ -import 'dart:async'; import 'dart:io'; import 'dart:math'; @@ -26,8 +25,6 @@ class _FileManagerPageState extends State with AutomaticKeepAliveClientMixin { final _localSelectedItems = SelectedItems(); final _remoteSelectedItems = SelectedItems(); - final _breadCrumbLocalScroller = ScrollController(); - final _breadCrumbRemoteScroller = ScrollController(); /// FFI with name file_transfer_id FFI get _ffi => ffi('ft_${widget.id}'); @@ -94,41 +91,11 @@ class _FileManagerPageState extends State icon: Icon(Icons.more_vert), itemBuilder: (context) { 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( child: Row( children: [ Icon( - model.currentShowHidden + model.getCurrentShowHidden(isLocal) ? Icons.check_box_outlined : Icons.check_box_outline_blank, color: Colors.black), @@ -141,46 +108,7 @@ class _FileManagerPageState extends State ]; }, onSelected: (v) { - if (v == "refresh") { - 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") { + if (v == "hidden") { model.toggleShowHidden(local: isLocal); } }); @@ -189,9 +117,23 @@ class _FileManagerPageState extends State Widget body({bool isLocal = false}) { final fd = isLocal ? model.currentLocalDir : model.currentRemoteDir; 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( 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), padding: const EdgeInsets.all(8.0), child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -204,16 +146,36 @@ class _FileManagerPageState extends State child: SingleChildScrollView( child: DataTable( showCheckboxColumn: true, - dataRowHeight: 30, + dataRowHeight: 25, + headingRowHeight: 30, columnSpacing: 8, + showBottomBorder: true, + sortColumnIndex: sortIndex, + sortAscending: sortAscending, columns: [ DataColumn(label: Text(translate(" "))), // icon DataColumn( label: Text( - translate("Name"), - )), - DataColumn(label: Text(translate("Modified"))), - DataColumn(label: Text(translate("Size"))), + translate("Name"), + ), + 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: entries.map((entry) { final sizeStr = entry.isFile @@ -228,23 +190,29 @@ class _FileManagerPageState extends State } else { getSelectedItem(isLocal).remove(entry); } - setState((){}); + setState(() {}); } }, selected: getSelectedItem(isLocal).contains(entry), cells: [ - // TODO: icon DataCell(Icon( entry.isFile ? Icons.feed_outlined : Icons.folder, size: 25)), DataCell( ConstrainedBox( constraints: BoxConstraints(maxWidth: 100), - child: Text(entry.name, - overflow: TextOverflow.ellipsis)), - onTap: () { + child: Tooltip( + message: entry.name, + child: Text(entry.name, + overflow: TextOverflow.ellipsis), + )), onTap: () { if (entry.isDirectory) { model.openDirectory(entry.path, isLocal: isLocal); + if (isLocal) { + _localSelectedItems.clear(); + } else { + _remoteSelectedItems.clear(); + } } else { // Perform file-related tasks. final _selectedItems = getSelectedItem(isLocal); @@ -253,7 +221,7 @@ class _FileManagerPageState extends State } else { _selectedItems.add(isLocal, entry); } - setState((){}); + setState(() {}); } }), DataCell(Text( @@ -277,7 +245,7 @@ class _FileManagerPageState extends State ) ], )), - Center(child: listTail(isLocal: isLocal)), + // Center(child: listTail(isLocal: isLocal)), // Expanded( // child: ListView.builder( // itemCount: entries.length + 1, @@ -388,9 +356,10 @@ class _FileManagerPageState extends State Widget statusList() { return PreferredSize( 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), - decoration: BoxDecoration(color: Colors.white70,border: Border.all(color: Colors.grey)), + decoration: BoxDecoration( + color: Colors.white70, border: Border.all(color: Colors.grey)), child: Obx( () => ListView.builder( itemBuilder: (BuildContext context, int index) { @@ -404,7 +373,9 @@ class _FileManagerPageState extends State Transform.rotate( angle: item.isRemote ? pi : 0, child: Icon(Icons.send)), - SizedBox(width: 16.0,), + SizedBox( + width: 16.0, + ), Expanded( child: Column( mainAxisSize: MainAxisSize.min, @@ -412,15 +383,28 @@ class _FileManagerPageState extends State children: [ Tooltip( message: item.jobName, - child: Text('${item.jobName}', + child: Text( + '${item.jobName}', maxLines: 1, - style: TextStyle(color: Colors.black45), overflow: TextOverflow.ellipsis,)), + style: TextStyle(color: Colors.black45), + overflow: TextOverflow.ellipsis, + )), Wrap( children: [ - Text('${item.state.display()} ${max(0, item.fileNum)}/${item.fileCount} '), - Text('${translate("files")} ${readableFileSize(item.totalSize.toDouble())} '), - Offstage(offstage: item.state != JobState.inProgress, child: Text('${readableFileSize(item.speed) + "/s"} ')), - Text('${(item.finishedSize.toDouble() * 100 / item.totalSize.toDouble()).toStringAsFixed(2)}%'), + Text( + '${item.state.display()} ${max(0, item.fileNum)}/${item.fileCount} '), + Text( + '${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 Row( mainAxisAlignment: MainAxisAlignment.end, children: [ - IconButton(icon: Icon(Icons.delete), onPressed: () { - model.jobTable.removeAt(index); - model.cancelJob(item.id); - },), + IconButton( + icon: Icon(Icons.delete), + onPressed: () { + model.jobTable.removeAt(index); + model.cancelJob(item.id); + }, + ), ], ) ], ), - SizedBox(height: 8.0,), - Divider(height: 2.0, ) + SizedBox( + height: 8.0, + ), + Divider( + height: 2.0, + ) ], ); - }, + }, itemCount: model.jobTable.length, ), ), @@ -453,100 +444,175 @@ class _FileManagerPageState extends State 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( - child: Row( + child: Column( children: [ - Offstage( - offstage: isLocal, - child: TextButton.icon( - onPressed: (){ - final items = getSelectedItem(isLocal); - model.sendFiles(items, isRemote: true); - }, icon: Transform.rotate( - angle: isLocal ? 0 : pi, - child: Icon( - Icons.send + // symbols + PreferredSize( + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + width: 50, + height: 50, + decoration: BoxDecoration(color: Colors.blue), + padding: EdgeInsets.all(8.0), + child: FutureBuilder( + 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'))), - ), - 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), - ), - )), + preferredSize: Size(double.infinity, 70)), + // buttons Row( children: [ - IconButton( - icon: Icon(Icons.arrow_upward), - onPressed: () { - goBack(isLocal: isLocal); - }, + Row( + children: [ + IconButton( + onPressed: () { + model.goHome(isLocal: isLocal); + }, + icon: Icon(Icons.home_outlined)), + IconButton( + icon: Icon(Icons.arrow_upward), + onPressed: () { + goBack(isLocal: isLocal); + }, + ), + menu(isLocal: isLocal), + ], ), - PopupMenuButton( - icon: Icon(Icons.sort), - itemBuilder: (context) { - return SortBy.values - .map((e) => PopupMenuItem( - child: - Text(translate(e.toString().split(".").last)), - value: e, - )) - .toList(); + Expanded( + child: Container( + decoration: BoxDecoration( + border: Border.all(color: Colors.black12)), + child: TextField( + decoration: InputDecoration( + border: InputBorder.none, + isDense: true, + prefix: Padding(padding: EdgeInsets.only(left: 4.0)), + suffix: DropdownButton( + 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) { - model.changeSortStyle(sort, isLocal: isLocal); - }), - menu(isLocal: isLocal), + icon: Icon(Icons.refresh)) ], ), - Offstage( - offstage: !isLocal, - child: TextButton.icon( - onPressed: (){ - final items = getSelectedItem(isLocal); - model.sendFiles(items, isRemote: !isLocal); - }, icon: Transform.rotate( - angle: isLocal ? 0 : pi, - child: Icon( - Icons.send + Row( + textDirection: isLocal ? TextDirection.ltr : TextDirection.rtl, + children: [ + Expanded( + child: Row( + mainAxisAlignment: + isLocal ? MainAxisAlignment.start : MainAxisAlignment.end, + children: [ + IconButton( + 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 Widget? bottomSheet() { final state = model.jobState; final isOtherPage = _localSelectedItems.isOtherPage(model.isLocal); - final selectedItemsLen = "${_localSelectedItems.length} ${translate("items")}"; + final selectedItemsLen = + "${_localSelectedItems.length} ${translate("items")}"; final local = _localSelectedItems.isLocal == null ? "" : " [${_localSelectedItems.isLocal! ? translate("Local") : translate("Remote")}]"; @@ -677,6 +744,15 @@ class _FileManagerPageState extends State @override 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 { diff --git a/flutter/lib/models/file_model.dart b/flutter/lib/models/file_model.dart index 996c5112c..bd71aff15 100644 --- a/flutter/lib/models/file_model.dart +++ b/flutter/lib/models/file_model.dart @@ -40,6 +40,20 @@ class FileModel extends ChangeNotifier { 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 get currentLocalDir => _currentLocalDir; @@ -50,6 +64,10 @@ class FileModel extends ChangeNotifier { FileDirectory get currentDir => _isLocal ? currentLocalDir : currentRemoteDir; + FileDirectory getCurrentDir(bool isLocal) { + return isLocal ? currentLocalDir : currentRemoteDir; + } + String get currentHome => _isLocal ? _localOption.home : _remoteOption.home; String getCurrentHome(bool isLocal) { @@ -92,6 +110,10 @@ class FileModel extends ChangeNotifier { bool get currentShowHidden => _isLocal ? _localOption.showHidden : _remoteOption.showHidden; + bool getCurrentShowHidden(bool isLocal) { + return isLocal ? _localOption.showHidden : _remoteOption.showHidden; + } + bool get currentIsWindows => _isLocal ? _localOption.isWindows : _remoteOption.isWindows; @@ -163,13 +185,15 @@ class FileModel extends ChangeNotifier { try { final fd = FileDirectory.fromJson(jsonDecode(evt['value'])); fd.format(_remoteOption.isWindows, sort: _sortStyle); - if (fd.id > 0){ + if (fd.id > 0) { final jobIndex = getJob(fd.id); - if (jobIndex != -1){ + if (jobIndex != -1) { final job = jobTable[jobIndex]; var totalSize = 0; var fileCount = fd.entries.length; - fd.entries.forEach((element) {totalSize += element.size;}); + fd.entries.forEach((element) { + totalSize += element.size; + }); job.totalSize = totalSize; job.fileCount = fileCount; debugPrint("update receive details:${fd.path}"); @@ -179,11 +203,11 @@ class FileModel extends ChangeNotifier { debugPrint("init remote home:${fd.path}"); _currentRemoteDir = fd; } - notifyListeners(); - return; - } finally {} + } + finally {} } _fileFetcher.tryCompleteTask(evt['value'], evt['is_local']); + notifyListeners(); } jobDone(Map evt) { @@ -307,10 +331,10 @@ class FileModel extends ChangeNotifier { _remoteOption.clear(); } - refresh() { + refresh({bool? isLocal}) { if (isDesktop) { - openDirectory(currentRemoteDir.path); - openDirectory(currentLocalDir.path); + isLocal = isLocal ?? _isLocal; + isLocal ? openDirectory(currentLocalDir.path) : openDirectory(currentRemoteDir.path); } else { openDirectory(currentDir.path); } @@ -344,7 +368,8 @@ class FileModel extends ChangeNotifier { } goHome({bool? isLocal}) { - openDirectory(currentHome, isLocal: isLocal); + isLocal = isLocal ?? _isLocal; + openDirectory(getCurrentHome(isLocal), isLocal: 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); } - createDir(String path) async { + createDir(String path, {bool? isLocal}) async { + isLocal = isLocal ?? this.isLocal; _jobId++; _ffi.target?.bind.sessionCreateDir(id: '${_ffi.target?.id}', actId: _jobId, path: path, isRemote: !isLocal); } @@ -608,16 +634,20 @@ class FileModel extends ChangeNotifier { jobReset(); } - changeSortStyle(SortBy sort, {bool? isLocal}) { + changeSortStyle(SortBy sort, {bool? isLocal, bool ascending = true}) { _sortStyle = sort; if (isLocal == null) { // compatible for mobile logic - _currentLocalDir.changeSortStyle(sort); - _currentRemoteDir.changeSortStyle(sort); + _currentLocalDir.changeSortStyle(sort, ascending: ascending); + _currentRemoteDir.changeSortStyle(sort, ascending: ascending); + _localSortStyle = sort; _localSortAscending = ascending; + _remoteSortStyle = sort; _remoteSortAscending = ascending; } else if (isLocal) { - _currentLocalDir.changeSortStyle(sort); + _currentLocalDir.changeSortStyle(sort, ascending: ascending); + _localSortStyle = sort; _localSortAscending = ascending; } else { - _currentRemoteDir.changeSortStyle(sort); + _currentRemoteDir.changeSortStyle(sort, ascending: ascending); + _remoteSortStyle = sort; _remoteSortAscending = ascending; } notifyListeners(); } @@ -640,6 +670,8 @@ class FileModel extends ChangeNotifier { } debugPrint("update folder files: ${info}"); } + + bool get remoteSortAscending => _remoteSortAscending; } class JobResultListener { @@ -809,8 +841,8 @@ class FileDirectory { } } - changeSortStyle(SortBy sort) { - entries = _sortList(entries, sort); + changeSortStyle(SortBy sort, {bool ascending = true}) { + entries = _sortList(entries, sort, ascending); } clear() { @@ -929,7 +961,7 @@ class DirectoryOption { } // code from file_manager pkg after edit -List _sortList(List list, SortBy sortType) { +List _sortList(List list, SortBy sortType, bool ascending) { if (sortType == SortBy.Name) { // making list of only folders. final dirs = list.where((element) => element.isDirectory).toList(); @@ -942,7 +974,7 @@ List _sortList(List list, SortBy sortType) { 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. - return [...dirs, ...files]; + return ascending ? [...dirs, ...files] : [...dirs.reversed.toList(), ...files.reversed.toList()]; } else if (sortType == SortBy.Modified) { // making the list of Path & DateTime List<_PathStat> _pathStat = []; @@ -957,7 +989,7 @@ List _sortList(List list, SortBy sortType) { list.sort((a, b) => _pathStat .indexWhere((element) => element.path == a.name) .compareTo(_pathStat.indexWhere((element) => element.path == b.name))); - return list; + return ascending ? list : list.reversed.toList(); } else if (sortType == SortBy.Type) { // making list of only folders. final dirs = list.where((element) => element.isDirectory).toList(); @@ -974,7 +1006,7 @@ List _sortList(List list, SortBy sortType) { .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) { // create list of path and size Map _sizeMap = {}; @@ -999,7 +1031,7 @@ List _sortList(List list, SortBy sortType) { .indexWhere((element) => element.key == a.name) .compareTo( _sizeMapList.indexWhere((element) => element.key == b.name))); - return [...dirs, ...files]; + return ascending ? [...dirs, ...files]: [...dirs.reversed.toList(), ...files.reversed.toList()]; } return []; } diff --git a/flutter/macos/Runner/bridge_generated.h b/flutter/macos/Runner/bridge_generated.h index 7f072e770..163ad91cd 100644 --- a/flutter/macos/Runner/bridge_generated.h +++ b/flutter/macos/Runner/bridge_generated.h @@ -102,6 +102,10 @@ void wire_session_peer_option(int64_t port_, struct wire_uint_8_list *name, 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_, struct wire_uint_8_list *id, 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, int32_t act_id, 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_, 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_mouse); 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_read_remote_dir); dummy_var ^= ((int64_t) (void*) wire_session_send_files); diff --git a/src/flutter.rs b/src/flutter.rs index 8514e7515..ff278f3d0 100644 --- a/src/flutter.rs +++ b/src/flutter.rs @@ -5,6 +5,8 @@ use std::{ use flutter_rust_bridge::{StreamSink, ZeroCopyBuffer}; +use hbb_common::config::PeerConfig; +use hbb_common::fs::TransferJobMeta; use hbb_common::{ allow_err, compress::decompress, @@ -464,6 +466,41 @@ impl Session { log::debug!("{:?}", 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 {} diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index 9bc533336..327e79ef5 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -339,6 +339,19 @@ pub fn session_read_local_dir_sync(id: String, path: String, show_hidden: bool) "".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. /// Return result in c string. ///