diff --git a/flutter/lib/desktop/pages/file_manager_page.dart b/flutter/lib/desktop/pages/file_manager_page.dart index b13f40a5f..be0fedc5c 100644 --- a/flutter/lib/desktop/pages/file_manager_page.dart +++ b/flutter/lib/desktop/pages/file_manager_page.dart @@ -59,8 +59,11 @@ class _FileManagerPageState extends State super.initState(); _ffi = FFI(); _ffi.connect(widget.id, isFileTransfer: true); + WidgetsBinding.instance.addPostFrameCallback((_) { + _ffi.dialogManager + .showLoading(translate('Connecting...'), onCancel: closeConnection); + }); Get.put(_ffi, tag: 'ft_${widget.id}'); - // _ffi.ffiModel.updateEventListener(widget.id); if (!Platform.isLinux) { Wakelock.enable(); } @@ -117,7 +120,8 @@ class _FileManagerPageState extends State Widget menu({bool isLocal = false}) { return PopupMenuButton( - icon: Icon(Icons.more_vert), + icon: const Icon(Icons.more_vert), + splashRadius: 20, itemBuilder: (context) { return [ PopupMenuItem( @@ -413,6 +417,7 @@ class _FileManagerPageState extends State /// watch transfer status Widget statusList() { return PreferredSize( + preferredSize: const Size(200, double.infinity), child: Container( margin: const EdgeInsets.only(top: 16.0, bottom: 16.0, right: 16.0), padding: const EdgeInsets.all(8.0), @@ -429,8 +434,8 @@ class _FileManagerPageState extends State children: [ Transform.rotate( angle: item.isRemote ? pi : 0, - child: Icon(Icons.send)), - SizedBox( + child: const Icon(Icons.send)), + const SizedBox( width: 16.0, ), Expanded( @@ -441,7 +446,7 @@ class _FileManagerPageState extends State Tooltip( message: item.jobName, child: Text( - '${item.jobName}', + item.jobName, maxLines: 1, overflow: TextOverflow.ellipsis, )), @@ -455,7 +460,7 @@ class _FileManagerPageState extends State offstage: item.state != JobState.inProgress, child: Text( - '${readableFileSize(item.speed) + "/s"} ')), + '${"${readableFileSize(item.speed)}/s"} ')), Offstage( offstage: item.totalSize <= 0, child: Text( @@ -475,10 +480,12 @@ class _FileManagerPageState extends State onPressed: () { model.resumeJob(item.id); }, - icon: Icon(Icons.restart_alt_rounded)), + splashRadius: 20, + icon: const Icon(Icons.restart_alt_rounded)), ), IconButton( - icon: Icon(Icons.delete), + icon: const Icon(Icons.delete), + splashRadius: 20, onPressed: () { model.jobTable.removeAt(index); model.cancelJob(item.id); @@ -500,8 +507,7 @@ class _FileManagerPageState extends State itemCount: model.jobTable.length, ), ), - ), - preferredSize: Size(200, double.infinity)); + )); } goBack({bool? isLocal}) { @@ -551,12 +557,15 @@ class _FileManagerPageState extends State Row( children: [ IconButton( - onPressed: () { - model.goHome(isLocal: isLocal); - }, - icon: Icon(Icons.home_outlined)), + onPressed: () { + model.goHome(isLocal: isLocal); + }, + icon: const Icon(Icons.home_outlined), + splashRadius: 20, + ), IconButton( - icon: Icon(Icons.arrow_upward), + icon: const Icon(Icons.arrow_upward), + splashRadius: 20, onPressed: () { goBack(isLocal: isLocal); }, @@ -622,13 +631,15 @@ class _FileManagerPageState extends State ), )) ], - child: Icon(Icons.search), + splashRadius: 20, + child: const Icon(Icons.search), ), IconButton( onPressed: () { model.refresh(isLocal: isLocal); }, - icon: Icon(Icons.refresh)), + splashRadius: 20, + icon: const Icon(Icons.refresh)), ], ), Row( @@ -686,6 +697,7 @@ class _FileManagerPageState extends State ); }); }, + splashRadius: 20, icon: const Icon(Icons.create_new_folder_outlined)), IconButton( onPressed: () async { @@ -695,7 +707,8 @@ class _FileManagerPageState extends State await (model.removeAction(items, isLocal: isLocal)); items.clear(); }, - icon: Icon(Icons.delete_forever_outlined)), + splashRadius: 20, + icon: const Icon(Icons.delete_forever_outlined)), ], ), ), @@ -707,7 +720,7 @@ class _FileManagerPageState extends State }, icon: Transform.rotate( angle: isLocal ? 0 : pi, - child: Icon( + child: const Icon( Icons.send, ), ), diff --git a/flutter/lib/models/file_model.dart b/flutter/lib/models/file_model.dart index dedca5efa..3246346be 100644 --- a/flutter/lib/models/file_model.dart +++ b/flutter/lib/models/file_model.dart @@ -360,9 +360,9 @@ class FileModel extends ChangeNotifier { Future refresh({bool? isLocal}) async { if (isDesktop) { isLocal = isLocal ?? _isLocal; - await isLocal - ? openDirectory(currentLocalDir.path, isLocal: isLocal) - : openDirectory(currentRemoteDir.path, isLocal: isLocal); + isLocal + ? await openDirectory(currentLocalDir.path, isLocal: isLocal) + : await openDirectory(currentRemoteDir.path, isLocal: isLocal); } else { await openDirectory(currentDir.path); } @@ -393,7 +393,7 @@ class FileModel extends ChangeNotifier { } notifyListeners(); } catch (e) { - debugPrint("Failed to openDirectory ${path} :$e"); + debugPrint("Failed to openDirectory $path: $e"); } } diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index 1993145e7..7d8cdc203 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -326,8 +326,8 @@ class FfiModel with ChangeNotifier { await bind.sessionGetOption(id: peerId, arg: "touch-mode") != ''; } - if (evt['is_file_transfer'] == "true") { - // TODO is file transfer + if (parent.target != null && + parent.target!.connType == ConnType.fileTransfer) { parent.target?.fileModel.onReady(); } else { _pi.displays = []; @@ -916,6 +916,8 @@ extension ToString on MouseButtons { } } +enum ConnType { defaultConn, fileTransfer, portForward, rdp } + /// FFI class for communicating with the Rust core. class FFI { var id = ""; @@ -924,6 +926,7 @@ class FFI { var alt = false; var command = false; var version = ""; + var connType = ConnType.defaultConn; /// dialogManager use late to ensure init after main page binding [globalKey] late final dialogManager = OverlayDialogManager(); @@ -1055,9 +1058,11 @@ class FFI { double tabBarHeight = 0.0}) { assert(!(isFileTransfer && isPortForward), "more than one connect type"); if (isFileTransfer) { - id = 'ft_${id}'; + connType = ConnType.fileTransfer; + id = 'ft_$id'; } else if (isPortForward) { - id = 'pf_${id}'; + connType = ConnType.portForward; + id = 'pf_$id'; } else { chatModel.resetClientMode(); canvasModel.id = id; @@ -1086,7 +1091,7 @@ class FFI { // every instance will bind a stream this.id = id; if (isFileTransfer) { - this.fileModel.initFileFetcher(); + fileModel.initFileFetcher(); } } diff --git a/src/client.rs b/src/client.rs index 32c0003fd..a4a864846 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1665,6 +1665,7 @@ pub async fn handle_login_from_ui( /// Interface for client to send data and commands. #[async_trait] pub trait Interface: Send + Clone + 'static + Sized { + /// Send message data to remote peer. fn send(&self, data: Data); fn msgbox(&self, msgtype: &str, title: &str, text: &str); fn handle_login_error(&mut self, err: &str) -> bool; diff --git a/src/client/io_loop.rs b/src/client/io_loop.rs index 552fea7a8..f33f740a3 100644 --- a/src/client/io_loop.rs +++ b/src/client/io_loop.rs @@ -269,33 +269,6 @@ impl Remote { Some(tx) } - // TODO - fn load_last_jobs(&mut self) { - self.handler.clear_all_jobs(); - let pc = self.handler.load_config(); - if pc.transfer.write_jobs.is_empty() && pc.transfer.read_jobs.is_empty() { - // no last jobs - return; - } - // TODO: can add a confirm dialog - let mut cnt = 1; - for job_str in pc.transfer.read_jobs.iter() { - if !job_str.is_empty() { - self.handler.load_last_job(cnt, job_str); - cnt += 1; - log::info!("restore read_job: {:?}", job_str); - } - } - for job_str in pc.transfer.write_jobs.iter() { - if !job_str.is_empty() { - self.handler.load_last_job(cnt, job_str); - cnt += 1; - log::info!("restore write_job: {:?}", job_str); - } - } - self.handler.update_transfer_list(); - } - async fn handle_msg_from_ui(&mut self, data: Data, peer: &mut Stream) -> bool { match data { Data::Close => { @@ -768,7 +741,7 @@ impl Remote { } if self.handler.is_file_transfer() { - self.load_last_jobs(); + self.handler.load_last_jobs(); } } _ => {} @@ -827,7 +800,7 @@ impl Remote { &entries, fd.path, false, - fd.id > 0, + false, ); if let Some(job) = fs::get_job(fd.id, &mut self.write_jobs) { log::info!("job set_files: {:?}", entries); diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index 5da94c3c1..ef2aaeaa1 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -366,7 +366,7 @@ pub fn session_get_platform(id: String, is_remote: bool) -> String { pub fn session_load_last_transfer_jobs(id: String) { if let Some(session) = SESSIONS.read().unwrap().get(&id) { - // return session.load_last_jobs(); + return session.load_last_jobs(); } else { // a tip for flutter dev eprintln!( diff --git a/src/ui_session_interface.rs b/src/ui_session_interface.rs index 717963561..f1444f4c3 100644 --- a/src/ui_session_interface.rs +++ b/src/ui_session_interface.rs @@ -532,6 +532,32 @@ impl Session { pub fn close(&self) { self.send(Data::Close); } + + pub fn load_last_jobs(&self) { + self.clear_all_jobs(); + let pc = self.load_config(); + if pc.transfer.write_jobs.is_empty() && pc.transfer.read_jobs.is_empty() { + // no last jobs + return; + } + // TODO: can add a confirm dialog + let mut cnt = 1; + for job_str in pc.transfer.read_jobs.iter() { + if !job_str.is_empty() { + self.load_last_job(cnt, job_str); + cnt += 1; + log::info!("restore read_job: {:?}", job_str); + } + } + for job_str in pc.transfer.write_jobs.iter() { + if !job_str.is_empty() { + self.load_last_job(cnt, job_str); + cnt += 1; + log::info!("restore write_job: {:?}", job_str); + } + } + self.update_transfer_list(); + } } pub trait InvokeUiSession: Send + Sync + Clone + 'static + Sized + Default {