| 
									
										
										
										
											2022-06-17 22:57:41 +08:00
										 |  |  | import 'dart:io'; | 
					
						
							| 
									
										
										
										
											2022-07-09 19:14:40 +08:00
										 |  |  | import 'dart:math'; | 
					
						
							| 
									
										
										
										
											2022-06-17 22:57:41 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-16 13:28:48 +08:00
										 |  |  | import 'package:desktop_drop/desktop_drop.dart'; | 
					
						
							| 
									
										
										
										
											2022-06-17 22:57:41 +08:00
										 |  |  | import 'package:flutter/material.dart'; | 
					
						
							| 
									
										
										
										
											2022-08-16 11:46:51 +08:00
										 |  |  | import 'package:flutter_breadcrumb/flutter_breadcrumb.dart'; | 
					
						
							| 
									
										
										
										
											2022-06-17 22:57:41 +08:00
										 |  |  | import 'package:flutter_hbb/mobile/pages/file_manager_page.dart'; | 
					
						
							|  |  |  | import 'package:flutter_hbb/models/file_model.dart'; | 
					
						
							|  |  |  | import 'package:get/get.dart'; | 
					
						
							|  |  |  | import 'package:provider/provider.dart'; | 
					
						
							|  |  |  | import 'package:wakelock/wakelock.dart'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import '../../common.dart'; | 
					
						
							|  |  |  | import '../../models/model.dart'; | 
					
						
							| 
									
										
										
										
											2022-08-03 22:03:31 +08:00
										 |  |  | import '../../models/platform_model.dart'; | 
					
						
							| 
									
										
										
										
											2022-06-17 22:57:41 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-16 11:46:51 +08:00
										 |  |  | enum LocationStatus { bread, textField } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-17 22:57:41 +08:00
										 |  |  | class FileManagerPage extends StatefulWidget { | 
					
						
							| 
									
										
										
										
											2022-09-06 02:08:59 -07:00
										 |  |  |   const FileManagerPage({Key? key, required this.id}) : super(key: key); | 
					
						
							| 
									
										
										
										
											2022-06-17 22:57:41 +08:00
										 |  |  |   final String id; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   State<StatefulWidget> createState() => _FileManagerPageState(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class _FileManagerPageState extends State<FileManagerPage> | 
					
						
							|  |  |  |     with AutomaticKeepAliveClientMixin { | 
					
						
							| 
									
										
										
										
											2022-07-09 19:14:40 +08:00
										 |  |  |   final _localSelectedItems = SelectedItems(); | 
					
						
							|  |  |  |   final _remoteSelectedItems = SelectedItems(); | 
					
						
							| 
									
										
										
										
											2022-06-17 22:57:41 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-16 11:46:51 +08:00
										 |  |  |   final _locationStatusLocal = LocationStatus.bread.obs; | 
					
						
							|  |  |  |   final _locationStatusRemote = LocationStatus.bread.obs; | 
					
						
							|  |  |  |   final FocusNode _locationNodeLocal = | 
					
						
							|  |  |  |       FocusNode(debugLabel: "locationNodeLocal"); | 
					
						
							|  |  |  |   final FocusNode _locationNodeRemote = | 
					
						
							|  |  |  |       FocusNode(debugLabel: "locationNodeRemote"); | 
					
						
							| 
									
										
										
										
											2022-08-16 12:28:12 +08:00
										 |  |  |   final _searchTextLocal = "".obs; | 
					
						
							|  |  |  |   final _searchTextRemote = "".obs; | 
					
						
							|  |  |  |   final _breadCrumbScrollerLocal = ScrollController(); | 
					
						
							|  |  |  |   final _breadCrumbScrollerRemote = ScrollController(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-16 13:28:48 +08:00
										 |  |  |   final _dropMaskVisible = false.obs; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-16 12:28:12 +08:00
										 |  |  |   ScrollController getBreadCrumbScrollController(bool isLocal) { | 
					
						
							|  |  |  |     return isLocal ? _breadCrumbScrollerLocal : _breadCrumbScrollerRemote; | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2022-08-16 11:46:51 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-12 18:42:02 +08:00
										 |  |  |   late FFI _ffi; | 
					
						
							| 
									
										
										
										
											2022-06-17 22:57:41 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |   FileModel get model => _ffi.fileModel; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-07-09 19:14:40 +08:00
										 |  |  |   SelectedItems getSelectedItem(bool isLocal) { | 
					
						
							|  |  |  |     return isLocal ? _localSelectedItems : _remoteSelectedItems; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-17 22:57:41 +08:00
										 |  |  |   @override | 
					
						
							|  |  |  |   void initState() { | 
					
						
							|  |  |  |     super.initState(); | 
					
						
							| 
									
										
										
										
											2022-08-12 18:42:02 +08:00
										 |  |  |     _ffi = FFI(); | 
					
						
							| 
									
										
										
										
											2022-09-27 20:35:02 +08:00
										 |  |  |     _ffi.start(widget.id, isFileTransfer: true); | 
					
						
							| 
									
										
										
										
											2022-09-06 19:08:45 +08:00
										 |  |  |     WidgetsBinding.instance.addPostFrameCallback((_) { | 
					
						
							|  |  |  |       _ffi.dialogManager | 
					
						
							|  |  |  |           .showLoading(translate('Connecting...'), onCancel: closeConnection); | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2022-08-12 18:42:02 +08:00
										 |  |  |     Get.put(_ffi, tag: 'ft_${widget.id}'); | 
					
						
							| 
									
										
										
										
											2022-06-17 22:57:41 +08:00
										 |  |  |     if (!Platform.isLinux) { | 
					
						
							|  |  |  |       Wakelock.enable(); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-09-13 21:36:38 +08:00
										 |  |  |     debugPrint("File manager page init success with id ${widget.id}"); | 
					
						
							| 
									
										
										
										
											2022-08-16 11:46:51 +08:00
										 |  |  |     // register location listener
 | 
					
						
							|  |  |  |     _locationNodeLocal.addListener(onLocalLocationFocusChanged); | 
					
						
							|  |  |  |     _locationNodeRemote.addListener(onRemoteLocationFocusChanged); | 
					
						
							| 
									
										
										
										
											2022-06-17 22:57:41 +08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   void dispose() { | 
					
						
							|  |  |  |     model.onClose(); | 
					
						
							|  |  |  |     _ffi.close(); | 
					
						
							| 
									
										
										
										
											2022-08-12 18:42:02 +08:00
										 |  |  |     _ffi.dialogManager.dismissAll(); | 
					
						
							| 
									
										
										
										
											2022-06-17 22:57:41 +08:00
										 |  |  |     if (!Platform.isLinux) { | 
					
						
							|  |  |  |       Wakelock.disable(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     Get.delete<FFI>(tag: 'ft_${widget.id}'); | 
					
						
							| 
									
										
										
										
											2022-08-16 11:46:51 +08:00
										 |  |  |     _locationNodeLocal.removeListener(onLocalLocationFocusChanged); | 
					
						
							|  |  |  |     _locationNodeRemote.removeListener(onRemoteLocationFocusChanged); | 
					
						
							| 
									
										
										
										
											2022-06-17 22:57:41 +08:00
										 |  |  |     super.dispose(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   Widget build(BuildContext context) { | 
					
						
							|  |  |  |     super.build(context); | 
					
						
							| 
									
										
										
										
											2022-08-19 12:44:35 +08:00
										 |  |  |     return Overlay(initialEntries: [ | 
					
						
							|  |  |  |       OverlayEntry(builder: (context) { | 
					
						
							|  |  |  |         _ffi.dialogManager.setOverlayState(Overlay.of(context)); | 
					
						
							|  |  |  |         return ChangeNotifierProvider.value( | 
					
						
							|  |  |  |             value: _ffi.fileModel, | 
					
						
							|  |  |  |             child: Consumer<FileModel>(builder: (_context, _model, _child) { | 
					
						
							|  |  |  |               return WillPopScope( | 
					
						
							|  |  |  |                   onWillPop: () async { | 
					
						
							|  |  |  |                     if (model.selectMode) { | 
					
						
							|  |  |  |                       model.toggleSelectMode(); | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                     return false; | 
					
						
							|  |  |  |                   }, | 
					
						
							|  |  |  |                   child: Scaffold( | 
					
						
							| 
									
										
										
										
											2022-09-23 16:31:50 +08:00
										 |  |  |                     backgroundColor: Theme.of(context).backgroundColor, | 
					
						
							| 
									
										
										
										
											2022-08-19 12:44:35 +08:00
										 |  |  |                     body: Row( | 
					
						
							|  |  |  |                       children: [ | 
					
						
							|  |  |  |                         Flexible(flex: 3, child: body(isLocal: true)), | 
					
						
							|  |  |  |                         Flexible(flex: 3, child: body(isLocal: false)), | 
					
						
							|  |  |  |                         Flexible(flex: 2, child: statusList()) | 
					
						
							|  |  |  |                       ], | 
					
						
							|  |  |  |                     ), | 
					
						
							|  |  |  |                   )); | 
					
						
							|  |  |  |             })); | 
					
						
							|  |  |  |       }) | 
					
						
							|  |  |  |     ]); | 
					
						
							| 
									
										
										
										
											2022-06-17 22:57:41 +08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-27 16:44:34 +08:00
										 |  |  |   Widget menu({bool isLocal = false}) { | 
					
						
							|  |  |  |     return PopupMenuButton<String>( | 
					
						
							| 
									
										
										
										
											2022-09-06 19:08:45 +08:00
										 |  |  |         icon: const Icon(Icons.more_vert), | 
					
						
							|  |  |  |         splashRadius: 20, | 
					
						
							| 
									
										
										
										
											2022-06-27 16:44:34 +08:00
										 |  |  |         itemBuilder: (context) { | 
					
						
							|  |  |  |           return [ | 
					
						
							|  |  |  |             PopupMenuItem( | 
					
						
							|  |  |  |               child: Row( | 
					
						
							|  |  |  |                 children: [ | 
					
						
							|  |  |  |                   Icon( | 
					
						
							| 
									
										
										
										
											2022-07-11 16:07:49 +08:00
										 |  |  |                       model.getCurrentShowHidden(isLocal) | 
					
						
							| 
									
										
										
										
											2022-06-27 16:44:34 +08:00
										 |  |  |                           ? Icons.check_box_outlined | 
					
						
							|  |  |  |                           : Icons.check_box_outline_blank, | 
					
						
							|  |  |  |                       color: Colors.black), | 
					
						
							|  |  |  |                   SizedBox(width: 5), | 
					
						
							|  |  |  |                   Text(translate("Show Hidden Files")) | 
					
						
							|  |  |  |                 ], | 
					
						
							|  |  |  |               ), | 
					
						
							|  |  |  |               value: "hidden", | 
					
						
							|  |  |  |             ) | 
					
						
							|  |  |  |           ]; | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |         onSelected: (v) { | 
					
						
							| 
									
										
										
										
											2022-07-11 16:07:49 +08:00
										 |  |  |           if (v == "hidden") { | 
					
						
							| 
									
										
										
										
											2022-06-27 16:44:34 +08:00
										 |  |  |             model.toggleShowHidden(local: isLocal); | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-21 18:14:44 +08:00
										 |  |  |   Widget body({bool isLocal = false}) { | 
					
						
							| 
									
										
										
										
											2022-08-16 12:06:54 +08:00
										 |  |  |     final fd = model.getCurrentDir(isLocal); | 
					
						
							| 
									
										
										
										
											2022-06-17 22:57:41 +08:00
										 |  |  |     final entries = fd.entries; | 
					
						
							| 
									
										
										
										
											2022-07-11 16:07:49 +08:00
										 |  |  |     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; | 
					
						
							| 
									
										
										
										
											2022-07-01 17:17:25 +08:00
										 |  |  |     return Container( | 
					
						
							| 
									
										
										
										
											2022-08-16 11:46:51 +08:00
										 |  |  |       decoration: BoxDecoration(border: Border.all(color: Colors.black26)), | 
					
						
							| 
									
										
										
										
											2022-07-01 17:17:25 +08:00
										 |  |  |       margin: const EdgeInsets.all(16.0), | 
					
						
							|  |  |  |       padding: const EdgeInsets.all(8.0), | 
					
						
							| 
									
										
										
										
											2022-08-16 13:28:48 +08:00
										 |  |  |       child: DropTarget( | 
					
						
							|  |  |  |         onDragDone: (detail) => handleDragDone(detail, isLocal), | 
					
						
							|  |  |  |         onDragEntered: (enter) { | 
					
						
							|  |  |  |           _dropMaskVisible.value = true; | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |         onDragExited: (exit) { | 
					
						
							|  |  |  |           _dropMaskVisible.value = false; | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |         child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ | 
					
						
							|  |  |  |           headTools(isLocal), | 
					
						
							|  |  |  |           Expanded( | 
					
						
							|  |  |  |               child: Row( | 
					
						
							|  |  |  |             crossAxisAlignment: CrossAxisAlignment.start, | 
					
						
							|  |  |  |             children: [ | 
					
						
							|  |  |  |               Expanded( | 
					
						
							|  |  |  |                 child: SingleChildScrollView( | 
					
						
							| 
									
										
										
										
											2022-09-12 11:23:45 +08:00
										 |  |  |                   controller: ScrollController(), | 
					
						
							| 
									
										
										
										
											2022-08-16 13:28:48 +08:00
										 |  |  |                   child: ObxValue<RxString>( | 
					
						
							|  |  |  |                     (searchText) { | 
					
						
							|  |  |  |                       final filteredEntries = searchText.isEmpty | 
					
						
							|  |  |  |                           ? entries.where((element) { | 
					
						
							|  |  |  |                               if (searchText.isEmpty) { | 
					
						
							|  |  |  |                                 return true; | 
					
						
							|  |  |  |                               } else { | 
					
						
							|  |  |  |                                 return element.name.contains(searchText.value); | 
					
						
							| 
									
										
										
										
											2022-07-09 19:14:40 +08:00
										 |  |  |                               } | 
					
						
							| 
									
										
										
										
											2022-08-16 13:28:48 +08:00
										 |  |  |                             }).toList(growable: false) | 
					
						
							|  |  |  |                           : entries; | 
					
						
							|  |  |  |                       return DataTable( | 
					
						
							|  |  |  |                         key: ValueKey(isLocal ? 0 : 1), | 
					
						
							|  |  |  |                         showCheckboxColumn: true, | 
					
						
							|  |  |  |                         dataRowHeight: 25, | 
					
						
							|  |  |  |                         headingRowHeight: 30, | 
					
						
							|  |  |  |                         columnSpacing: 8, | 
					
						
							|  |  |  |                         showBottomBorder: true, | 
					
						
							|  |  |  |                         sortColumnIndex: sortIndex, | 
					
						
							|  |  |  |                         sortAscending: sortAscending, | 
					
						
							|  |  |  |                         columns: [ | 
					
						
							|  |  |  |                           DataColumn(label: Text(translate(" "))), // icon
 | 
					
						
							|  |  |  |                           DataColumn( | 
					
						
							|  |  |  |                               label: Text( | 
					
						
							|  |  |  |                                 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: filteredEntries.map((entry) { | 
					
						
							|  |  |  |                           final sizeStr = entry.isFile | 
					
						
							|  |  |  |                               ? readableFileSize(entry.size.toDouble()) | 
					
						
							|  |  |  |                               : ""; | 
					
						
							|  |  |  |                           return DataRow( | 
					
						
							|  |  |  |                               key: ValueKey(entry.name), | 
					
						
							|  |  |  |                               onSelectChanged: (s) { | 
					
						
							|  |  |  |                                 if (s != null) { | 
					
						
							|  |  |  |                                   if (s) { | 
					
						
							|  |  |  |                                     getSelectedItem(isLocal) | 
					
						
							|  |  |  |                                         .add(isLocal, entry); | 
					
						
							| 
									
										
										
										
											2022-08-16 12:06:54 +08:00
										 |  |  |                                   } else { | 
					
						
							| 
									
										
										
										
											2022-08-16 13:28:48 +08:00
										 |  |  |                                     getSelectedItem(isLocal).remove(entry); | 
					
						
							| 
									
										
										
										
											2022-08-16 12:06:54 +08:00
										 |  |  |                                   } | 
					
						
							|  |  |  |                                   setState(() {}); | 
					
						
							|  |  |  |                                 } | 
					
						
							| 
									
										
										
										
											2022-08-16 13:28:48 +08:00
										 |  |  |                               }, | 
					
						
							|  |  |  |                               selected: | 
					
						
							|  |  |  |                                   getSelectedItem(isLocal).contains(entry), | 
					
						
							|  |  |  |                               cells: [ | 
					
						
							|  |  |  |                                 DataCell(Icon( | 
					
						
							|  |  |  |                                     entry.isFile | 
					
						
							|  |  |  |                                         ? Icons.feed_outlined | 
					
						
							|  |  |  |                                         : Icons.folder, | 
					
						
							|  |  |  |                                     size: 25)), | 
					
						
							|  |  |  |                                 DataCell( | 
					
						
							|  |  |  |                                     ConstrainedBox( | 
					
						
							|  |  |  |                                         constraints: | 
					
						
							|  |  |  |                                             BoxConstraints(maxWidth: 100), | 
					
						
							|  |  |  |                                         child: Tooltip( | 
					
						
							|  |  |  |                                           message: entry.name, | 
					
						
							|  |  |  |                                           child: Text(entry.name, | 
					
						
							|  |  |  |                                               overflow: TextOverflow.ellipsis), | 
					
						
							|  |  |  |                                         )), onTap: () { | 
					
						
							|  |  |  |                                   if (entry.isDirectory) { | 
					
						
							|  |  |  |                                     openDirectory(entry.path, isLocal: isLocal); | 
					
						
							|  |  |  |                                     if (isLocal) { | 
					
						
							|  |  |  |                                       _localSelectedItems.clear(); | 
					
						
							|  |  |  |                                     } else { | 
					
						
							|  |  |  |                                       _remoteSelectedItems.clear(); | 
					
						
							|  |  |  |                                     } | 
					
						
							|  |  |  |                                   } else { | 
					
						
							|  |  |  |                                     // Perform file-related tasks.
 | 
					
						
							|  |  |  |                                     final _selectedItems = | 
					
						
							|  |  |  |                                         getSelectedItem(isLocal); | 
					
						
							|  |  |  |                                     if (_selectedItems.contains(entry)) { | 
					
						
							|  |  |  |                                       _selectedItems.remove(entry); | 
					
						
							|  |  |  |                                     } else { | 
					
						
							|  |  |  |                                       _selectedItems.add(isLocal, entry); | 
					
						
							|  |  |  |                                     } | 
					
						
							|  |  |  |                                     setState(() {}); | 
					
						
							|  |  |  |                                   } | 
					
						
							|  |  |  |                                 }), | 
					
						
							|  |  |  |                                 DataCell(Text( | 
					
						
							|  |  |  |                                   entry | 
					
						
							|  |  |  |                                           .lastModified() | 
					
						
							|  |  |  |                                           .toString() | 
					
						
							|  |  |  |                                           .replaceAll(".000", "") + | 
					
						
							|  |  |  |                                       "   ", | 
					
						
							|  |  |  |                                   style: TextStyle( | 
					
						
							|  |  |  |                                       fontSize: 12, color: MyTheme.darkGray), | 
					
						
							|  |  |  |                                 )), | 
					
						
							|  |  |  |                                 DataCell(Text( | 
					
						
							|  |  |  |                                   sizeStr, | 
					
						
							|  |  |  |                                   style: TextStyle( | 
					
						
							|  |  |  |                                       fontSize: 12, color: MyTheme.darkGray), | 
					
						
							|  |  |  |                                 )), | 
					
						
							|  |  |  |                               ]); | 
					
						
							|  |  |  |                         }).toList(growable: false), | 
					
						
							|  |  |  |                       ); | 
					
						
							|  |  |  |                     }, | 
					
						
							|  |  |  |                     isLocal ? _searchTextLocal : _searchTextRemote, | 
					
						
							|  |  |  |                   ), | 
					
						
							| 
									
										
										
										
											2022-07-01 17:17:25 +08:00
										 |  |  |                 ), | 
					
						
							| 
									
										
										
										
											2022-08-16 13:28:48 +08:00
										 |  |  |               ) | 
					
						
							|  |  |  |             ], | 
					
						
							|  |  |  |           )), | 
					
						
							|  |  |  |         ]), | 
					
						
							|  |  |  |       ), | 
					
						
							| 
									
										
										
										
											2022-07-01 17:17:25 +08:00
										 |  |  |     ); | 
					
						
							| 
									
										
										
										
											2022-06-17 22:57:41 +08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-07-01 12:08:52 +08:00
										 |  |  |   /// transfer status list
 | 
					
						
							|  |  |  |   /// watch transfer status
 | 
					
						
							|  |  |  |   Widget statusList() { | 
					
						
							| 
									
										
										
										
											2022-07-01 17:17:25 +08:00
										 |  |  |     return PreferredSize( | 
					
						
							| 
									
										
										
										
											2022-09-06 19:08:45 +08:00
										 |  |  |         preferredSize: const Size(200, double.infinity), | 
					
						
							| 
									
										
										
										
											2022-07-01 17:17:25 +08:00
										 |  |  |         child: Container( | 
					
						
							| 
									
										
										
										
											2022-07-11 16:07:49 +08:00
										 |  |  |           margin: const EdgeInsets.only(top: 16.0, bottom: 16.0, right: 16.0), | 
					
						
							| 
									
										
										
										
											2022-07-01 17:17:25 +08:00
										 |  |  |           padding: const EdgeInsets.all(8.0), | 
					
						
							| 
									
										
										
										
											2022-08-16 11:46:51 +08:00
										 |  |  |           decoration: BoxDecoration(border: Border.all(color: Colors.grey)), | 
					
						
							| 
									
										
										
										
											2022-07-09 19:14:40 +08:00
										 |  |  |           child: Obx( | 
					
						
							|  |  |  |             () => ListView.builder( | 
					
						
							| 
									
										
										
										
											2022-09-12 11:23:45 +08:00
										 |  |  |               controller: ScrollController(), | 
					
						
							| 
									
										
										
										
											2022-07-11 10:30:45 +08:00
										 |  |  |               itemBuilder: (BuildContext context, int index) { | 
					
						
							|  |  |  |                 final item = model.jobTable[index]; | 
					
						
							|  |  |  |                 return Column( | 
					
						
							|  |  |  |                   mainAxisSize: MainAxisSize.min, | 
					
						
							| 
									
										
										
										
											2022-07-09 19:14:40 +08:00
										 |  |  |                   children: [ | 
					
						
							| 
									
										
										
										
											2022-07-11 10:30:45 +08:00
										 |  |  |                     Row( | 
					
						
							|  |  |  |                       crossAxisAlignment: CrossAxisAlignment.center, | 
					
						
							|  |  |  |                       children: [ | 
					
						
							|  |  |  |                         Transform.rotate( | 
					
						
							|  |  |  |                             angle: item.isRemote ? pi : 0, | 
					
						
							| 
									
										
										
										
											2022-09-06 19:08:45 +08:00
										 |  |  |                             child: const Icon(Icons.send)), | 
					
						
							|  |  |  |                         const SizedBox( | 
					
						
							| 
									
										
										
										
											2022-07-11 16:07:49 +08:00
										 |  |  |                           width: 16.0, | 
					
						
							|  |  |  |                         ), | 
					
						
							| 
									
										
										
										
											2022-07-11 10:30:45 +08:00
										 |  |  |                         Expanded( | 
					
						
							|  |  |  |                           child: Column( | 
					
						
							|  |  |  |                             mainAxisSize: MainAxisSize.min, | 
					
						
							|  |  |  |                             crossAxisAlignment: CrossAxisAlignment.start, | 
					
						
							|  |  |  |                             children: [ | 
					
						
							|  |  |  |                               Tooltip( | 
					
						
							|  |  |  |                                   message: item.jobName, | 
					
						
							| 
									
										
										
										
											2022-07-11 16:07:49 +08:00
										 |  |  |                                   child: Text( | 
					
						
							| 
									
										
										
										
											2022-09-06 19:08:45 +08:00
										 |  |  |                                     item.jobName, | 
					
						
							| 
									
										
										
										
											2022-07-11 10:30:45 +08:00
										 |  |  |                                     maxLines: 1, | 
					
						
							| 
									
										
										
										
											2022-07-11 16:07:49 +08:00
										 |  |  |                                     overflow: TextOverflow.ellipsis, | 
					
						
							|  |  |  |                                   )), | 
					
						
							| 
									
										
										
										
											2022-07-11 10:30:45 +08:00
										 |  |  |                               Wrap( | 
					
						
							|  |  |  |                                 children: [ | 
					
						
							| 
									
										
										
										
											2022-07-11 16:07:49 +08:00
										 |  |  |                                   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( | 
					
						
							| 
									
										
										
										
											2022-09-06 19:08:45 +08:00
										 |  |  |                                           '${"${readableFileSize(item.speed)}/s"} ')), | 
					
						
							| 
									
										
										
										
											2022-07-11 16:07:49 +08:00
										 |  |  |                                   Offstage( | 
					
						
							|  |  |  |                                     offstage: item.totalSize <= 0, | 
					
						
							|  |  |  |                                     child: Text( | 
					
						
							|  |  |  |                                         '${(item.finishedSize.toDouble() * 100 / item.totalSize.toDouble()).toStringAsFixed(2)}%'), | 
					
						
							|  |  |  |                                   ), | 
					
						
							| 
									
										
										
										
											2022-07-11 10:30:45 +08:00
										 |  |  |                                 ], | 
					
						
							|  |  |  |                               ), | 
					
						
							|  |  |  |                             ], | 
					
						
							|  |  |  |                           ), | 
					
						
							|  |  |  |                         ), | 
					
						
							|  |  |  |                         Row( | 
					
						
							|  |  |  |                           mainAxisAlignment: MainAxisAlignment.end, | 
					
						
							|  |  |  |                           children: [ | 
					
						
							| 
									
										
										
										
											2022-07-11 18:23:58 +08:00
										 |  |  |                             Offstage( | 
					
						
							|  |  |  |                               offstage: item.state != JobState.paused, | 
					
						
							|  |  |  |                               child: IconButton( | 
					
						
							|  |  |  |                                   onPressed: () { | 
					
						
							|  |  |  |                                     model.resumeJob(item.id); | 
					
						
							|  |  |  |                                   }, | 
					
						
							| 
									
										
										
										
											2022-09-06 19:08:45 +08:00
										 |  |  |                                   splashRadius: 20, | 
					
						
							|  |  |  |                                   icon: const Icon(Icons.restart_alt_rounded)), | 
					
						
							| 
									
										
										
										
											2022-07-11 18:23:58 +08:00
										 |  |  |                             ), | 
					
						
							| 
									
										
										
										
											2022-07-11 16:07:49 +08:00
										 |  |  |                             IconButton( | 
					
						
							| 
									
										
										
										
											2022-09-06 19:08:45 +08:00
										 |  |  |                               icon: const Icon(Icons.delete), | 
					
						
							|  |  |  |                               splashRadius: 20, | 
					
						
							| 
									
										
										
										
											2022-07-11 16:07:49 +08:00
										 |  |  |                               onPressed: () { | 
					
						
							|  |  |  |                                 model.jobTable.removeAt(index); | 
					
						
							|  |  |  |                                 model.cancelJob(item.id); | 
					
						
							|  |  |  |                               }, | 
					
						
							|  |  |  |                             ), | 
					
						
							| 
									
										
										
										
											2022-07-11 10:30:45 +08:00
										 |  |  |                           ], | 
					
						
							|  |  |  |                         ) | 
					
						
							|  |  |  |                       ], | 
					
						
							|  |  |  |                     ), | 
					
						
							| 
									
										
										
										
											2022-07-11 16:07:49 +08:00
										 |  |  |                     SizedBox( | 
					
						
							|  |  |  |                       height: 8.0, | 
					
						
							|  |  |  |                     ), | 
					
						
							|  |  |  |                     Divider( | 
					
						
							|  |  |  |                       height: 2.0, | 
					
						
							|  |  |  |                     ) | 
					
						
							| 
									
										
										
										
											2022-07-09 19:14:40 +08:00
										 |  |  |                   ], | 
					
						
							|  |  |  |                 ); | 
					
						
							| 
									
										
										
										
											2022-07-11 16:07:49 +08:00
										 |  |  |               }, | 
					
						
							| 
									
										
										
										
											2022-07-09 19:14:40 +08:00
										 |  |  |               itemCount: model.jobTable.length, | 
					
						
							|  |  |  |             ), | 
					
						
							|  |  |  |           ), | 
					
						
							| 
									
										
										
										
											2022-09-06 19:08:45 +08:00
										 |  |  |         )); | 
					
						
							| 
									
										
										
										
											2022-07-01 12:08:52 +08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-27 16:44:34 +08:00
										 |  |  |   goBack({bool? isLocal}) { | 
					
						
							|  |  |  |     model.goToParentDirectory(isLocal: isLocal); | 
					
						
							| 
									
										
										
										
											2022-06-17 22:57:41 +08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-16 11:46:51 +08:00
										 |  |  |   Widget headTools(bool isLocal) { | 
					
						
							|  |  |  |     final _locationStatus = | 
					
						
							|  |  |  |         isLocal ? _locationStatusLocal : _locationStatusRemote; | 
					
						
							|  |  |  |     final _locationFocus = isLocal ? _locationNodeLocal : _locationNodeRemote; | 
					
						
							| 
									
										
										
										
											2022-08-16 12:28:12 +08:00
										 |  |  |     final _searchTextObs = isLocal ? _searchTextLocal : _searchTextRemote; | 
					
						
							| 
									
										
										
										
											2022-08-16 11:46:51 +08:00
										 |  |  |     return Container( | 
					
						
							|  |  |  |         child: Column( | 
					
						
							|  |  |  |       children: [ | 
					
						
							|  |  |  |         // 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<String>( | 
					
						
							|  |  |  |                         future: 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) | 
					
						
							|  |  |  |               ], | 
					
						
							|  |  |  |             ), | 
					
						
							|  |  |  |             preferredSize: Size(double.infinity, 70)), | 
					
						
							|  |  |  |         // buttons
 | 
					
						
							|  |  |  |         Row( | 
					
						
							|  |  |  |           children: [ | 
					
						
							|  |  |  |             Row( | 
					
						
							|  |  |  |               children: [ | 
					
						
							|  |  |  |                 IconButton( | 
					
						
							| 
									
										
										
										
											2022-09-06 19:08:45 +08:00
										 |  |  |                   onPressed: () { | 
					
						
							|  |  |  |                     model.goHome(isLocal: isLocal); | 
					
						
							|  |  |  |                   }, | 
					
						
							|  |  |  |                   icon: const Icon(Icons.home_outlined), | 
					
						
							|  |  |  |                   splashRadius: 20, | 
					
						
							|  |  |  |                 ), | 
					
						
							| 
									
										
										
										
											2022-08-16 11:46:51 +08:00
										 |  |  |                 IconButton( | 
					
						
							| 
									
										
										
										
											2022-09-06 19:08:45 +08:00
										 |  |  |                   icon: const Icon(Icons.arrow_upward), | 
					
						
							|  |  |  |                   splashRadius: 20, | 
					
						
							| 
									
										
										
										
											2022-08-16 11:46:51 +08:00
										 |  |  |                   onPressed: () { | 
					
						
							|  |  |  |                     goBack(isLocal: isLocal); | 
					
						
							|  |  |  |                   }, | 
					
						
							|  |  |  |                 ), | 
					
						
							|  |  |  |                 menu(isLocal: isLocal), | 
					
						
							|  |  |  |               ], | 
					
						
							|  |  |  |             ), | 
					
						
							|  |  |  |             Expanded( | 
					
						
							|  |  |  |                 child: GestureDetector( | 
					
						
							|  |  |  |               onTap: () { | 
					
						
							|  |  |  |                 _locationStatus.value = | 
					
						
							|  |  |  |                     _locationStatus.value == LocationStatus.bread | 
					
						
							|  |  |  |                         ? LocationStatus.textField | 
					
						
							|  |  |  |                         : LocationStatus.bread; | 
					
						
							|  |  |  |                 Future.delayed(Duration.zero, () { | 
					
						
							|  |  |  |                   if (_locationStatus.value == LocationStatus.textField) { | 
					
						
							|  |  |  |                     _locationFocus.requestFocus(); | 
					
						
							|  |  |  |                   } | 
					
						
							|  |  |  |                 }); | 
					
						
							|  |  |  |               }, | 
					
						
							|  |  |  |               child: Container( | 
					
						
							|  |  |  |                   decoration: | 
					
						
							|  |  |  |                       BoxDecoration(border: Border.all(color: Colors.black12)), | 
					
						
							|  |  |  |                   child: Row( | 
					
						
							|  |  |  |                     children: [ | 
					
						
							|  |  |  |                       Expanded( | 
					
						
							|  |  |  |                           child: Obx(() => | 
					
						
							|  |  |  |                               _locationStatus.value == LocationStatus.bread | 
					
						
							|  |  |  |                                   ? buildBread(isLocal) | 
					
						
							|  |  |  |                                   : buildPathLocation(isLocal))), | 
					
						
							|  |  |  |                       DropdownButton<String>( | 
					
						
							|  |  |  |                           isDense: true, | 
					
						
							|  |  |  |                           underline: Offstage(), | 
					
						
							|  |  |  |                           items: [ | 
					
						
							|  |  |  |                             // TODO: favourite
 | 
					
						
							|  |  |  |                             DropdownMenuItem( | 
					
						
							|  |  |  |                               child: Text('/'), | 
					
						
							|  |  |  |                               value: '/', | 
					
						
							|  |  |  |                             ) | 
					
						
							|  |  |  |                           ], | 
					
						
							|  |  |  |                           onChanged: (path) { | 
					
						
							|  |  |  |                             if (path is String && path.isNotEmpty) { | 
					
						
							| 
									
										
										
										
											2022-08-16 12:28:12 +08:00
										 |  |  |                               openDirectory(path, isLocal: isLocal); | 
					
						
							| 
									
										
										
										
											2022-07-11 16:07:49 +08:00
										 |  |  |                             } | 
					
						
							| 
									
										
										
										
											2022-08-16 11:46:51 +08:00
										 |  |  |                           }) | 
					
						
							|  |  |  |                     ], | 
					
						
							|  |  |  |                   )), | 
					
						
							|  |  |  |             )), | 
					
						
							|  |  |  |             PopupMenuButton( | 
					
						
							|  |  |  |               itemBuilder: (context) => [ | 
					
						
							|  |  |  |                 PopupMenuItem( | 
					
						
							|  |  |  |                     enabled: false, | 
					
						
							|  |  |  |                     child: ConstrainedBox( | 
					
						
							|  |  |  |                       constraints: BoxConstraints(minWidth: 200), | 
					
						
							|  |  |  |                       child: TextField( | 
					
						
							| 
									
										
										
										
											2022-08-16 12:06:54 +08:00
										 |  |  |                         controller: | 
					
						
							|  |  |  |                             TextEditingController(text: _searchTextObs.value), | 
					
						
							|  |  |  |                         autofocus: true, | 
					
						
							|  |  |  |                         decoration: | 
					
						
							|  |  |  |                             InputDecoration(prefixIcon: Icon(Icons.search)), | 
					
						
							|  |  |  |                         onChanged: (searchText) => | 
					
						
							|  |  |  |                             onSearchText(searchText, isLocal), | 
					
						
							| 
									
										
										
										
											2022-08-16 11:46:51 +08:00
										 |  |  |                       ), | 
					
						
							|  |  |  |                     )) | 
					
						
							|  |  |  |               ], | 
					
						
							| 
									
										
										
										
											2022-09-06 19:08:45 +08:00
										 |  |  |               splashRadius: 20, | 
					
						
							|  |  |  |               child: const Icon(Icons.search), | 
					
						
							| 
									
										
										
										
											2022-08-16 11:46:51 +08:00
										 |  |  |             ), | 
					
						
							|  |  |  |             IconButton( | 
					
						
							|  |  |  |                 onPressed: () { | 
					
						
							|  |  |  |                   model.refresh(isLocal: isLocal); | 
					
						
							|  |  |  |                 }, | 
					
						
							| 
									
										
										
										
											2022-09-06 19:08:45 +08:00
										 |  |  |                 splashRadius: 20, | 
					
						
							|  |  |  |                 icon: const Icon(Icons.refresh)), | 
					
						
							| 
									
										
										
										
											2022-08-16 11:46:51 +08:00
										 |  |  |           ], | 
					
						
							|  |  |  |         ), | 
					
						
							|  |  |  |         Row( | 
					
						
							|  |  |  |           textDirection: isLocal ? TextDirection.ltr : TextDirection.rtl, | 
					
						
							|  |  |  |           children: [ | 
					
						
							|  |  |  |             Expanded( | 
					
						
							|  |  |  |               child: Row( | 
					
						
							|  |  |  |                 mainAxisAlignment: | 
					
						
							|  |  |  |                     isLocal ? MainAxisAlignment.start : MainAxisAlignment.end, | 
					
						
							| 
									
										
										
										
											2022-07-11 16:07:49 +08:00
										 |  |  |                 children: [ | 
					
						
							|  |  |  |                   IconButton( | 
					
						
							|  |  |  |                       onPressed: () { | 
					
						
							| 
									
										
										
										
											2022-08-16 11:46:51 +08:00
										 |  |  |                         final name = TextEditingController(); | 
					
						
							| 
									
										
										
										
											2022-09-03 18:19:50 +08:00
										 |  |  |                         _ffi.dialogManager.show((setState, close) { | 
					
						
							|  |  |  |                           submit() { | 
					
						
							|  |  |  |                             if (name.value.text.isNotEmpty) { | 
					
						
							|  |  |  |                               model.createDir( | 
					
						
							|  |  |  |                                   PathUtil.join( | 
					
						
							|  |  |  |                                       model.getCurrentDir(isLocal).path, | 
					
						
							|  |  |  |                                       name.value.text, | 
					
						
							|  |  |  |                                       model.getCurrentIsWindows(isLocal)), | 
					
						
							|  |  |  |                                   isLocal: isLocal); | 
					
						
							|  |  |  |                               close(); | 
					
						
							|  |  |  |                             } | 
					
						
							|  |  |  |                           } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                           cancel() => close(false); | 
					
						
							|  |  |  |                           return CustomAlertDialog( | 
					
						
							|  |  |  |                             title: Text(translate("Create Folder")), | 
					
						
							|  |  |  |                             content: Column( | 
					
						
							|  |  |  |                               mainAxisSize: MainAxisSize.min, | 
					
						
							|  |  |  |                               children: [ | 
					
						
							|  |  |  |                                 TextFormField( | 
					
						
							|  |  |  |                                   decoration: InputDecoration( | 
					
						
							|  |  |  |                                     labelText: translate( | 
					
						
							|  |  |  |                                         "Please enter the folder name"), | 
					
						
							|  |  |  |                                   ), | 
					
						
							|  |  |  |                                   controller: name, | 
					
						
							|  |  |  |                                   focusNode: FocusNode()..requestFocus(), | 
					
						
							|  |  |  |                                 ), | 
					
						
							|  |  |  |                               ], | 
					
						
							|  |  |  |                             ), | 
					
						
							|  |  |  |                             actions: [ | 
					
						
							|  |  |  |                               TextButton( | 
					
						
							|  |  |  |                                   style: flatButtonStyle, | 
					
						
							|  |  |  |                                   onPressed: cancel, | 
					
						
							|  |  |  |                                   child: Text(translate("Cancel"))), | 
					
						
							|  |  |  |                               ElevatedButton( | 
					
						
							|  |  |  |                                   style: flatButtonStyle, | 
					
						
							|  |  |  |                                   onPressed: submit, | 
					
						
							|  |  |  |                                   child: Text(translate("OK"))) | 
					
						
							|  |  |  |                             ], | 
					
						
							|  |  |  |                             onSubmit: submit, | 
					
						
							|  |  |  |                             onCancel: cancel, | 
					
						
							|  |  |  |                           ); | 
					
						
							|  |  |  |                         }); | 
					
						
							| 
									
										
										
										
											2022-07-11 16:07:49 +08:00
										 |  |  |                       }, | 
					
						
							| 
									
										
										
										
											2022-09-06 19:08:45 +08:00
										 |  |  |                       splashRadius: 20, | 
					
						
							| 
									
										
										
										
											2022-09-03 18:19:50 +08:00
										 |  |  |                       icon: const Icon(Icons.create_new_folder_outlined)), | 
					
						
							| 
									
										
										
										
											2022-07-11 16:07:49 +08:00
										 |  |  |                   IconButton( | 
					
						
							| 
									
										
										
										
											2022-08-16 11:46:51 +08:00
										 |  |  |                       onPressed: () async { | 
					
						
							|  |  |  |                         final items = isLocal | 
					
						
							|  |  |  |                             ? _localSelectedItems | 
					
						
							|  |  |  |                             : _remoteSelectedItems; | 
					
						
							|  |  |  |                         await (model.removeAction(items, isLocal: isLocal)); | 
					
						
							|  |  |  |                         items.clear(); | 
					
						
							|  |  |  |                       }, | 
					
						
							| 
									
										
										
										
											2022-09-06 19:08:45 +08:00
										 |  |  |                       splashRadius: 20, | 
					
						
							|  |  |  |                       icon: const Icon(Icons.delete_forever_outlined)), | 
					
						
							| 
									
										
										
										
											2022-07-11 16:07:49 +08:00
										 |  |  |                 ], | 
					
						
							| 
									
										
										
										
											2022-06-17 22:57:41 +08:00
										 |  |  |               ), | 
					
						
							| 
									
										
										
										
											2022-08-16 11:46:51 +08:00
										 |  |  |             ), | 
					
						
							|  |  |  |             TextButton.icon( | 
					
						
							|  |  |  |                 onPressed: () { | 
					
						
							|  |  |  |                   final items = getSelectedItem(isLocal); | 
					
						
							|  |  |  |                   model.sendFiles(items, isRemote: !isLocal); | 
					
						
							|  |  |  |                   items.clear(); | 
					
						
							|  |  |  |                 }, | 
					
						
							|  |  |  |                 icon: Transform.rotate( | 
					
						
							|  |  |  |                   angle: isLocal ? 0 : pi, | 
					
						
							| 
									
										
										
										
											2022-09-06 19:08:45 +08:00
										 |  |  |                   child: const Icon( | 
					
						
							| 
									
										
										
										
											2022-08-16 11:46:51 +08:00
										 |  |  |                     Icons.send, | 
					
						
							| 
									
										
										
										
											2022-07-11 16:07:49 +08:00
										 |  |  |                   ), | 
					
						
							| 
									
										
										
										
											2022-08-16 11:46:51 +08:00
										 |  |  |                 ), | 
					
						
							|  |  |  |                 label: Text( | 
					
						
							|  |  |  |                   isLocal ? translate('Send') : translate('Receive'), | 
					
						
							|  |  |  |                 )), | 
					
						
							|  |  |  |           ], | 
					
						
							|  |  |  |         ).marginOnly(top: 8.0) | 
					
						
							|  |  |  |       ], | 
					
						
							|  |  |  |     )); | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2022-06-17 22:57:41 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   bool get wantKeepAlive => true; | 
					
						
							| 
									
										
										
										
											2022-07-11 16:07:49 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-16 11:46:51 +08:00
										 |  |  |   void onLocalLocationFocusChanged() { | 
					
						
							|  |  |  |     debugPrint("focus changed on local"); | 
					
						
							|  |  |  |     if (_locationNodeLocal.hasFocus) { | 
					
						
							|  |  |  |       // ignore
 | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |       // lost focus, change to bread
 | 
					
						
							|  |  |  |       _locationStatusLocal.value = LocationStatus.bread; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   void onRemoteLocationFocusChanged() { | 
					
						
							|  |  |  |     debugPrint("focus changed on remote"); | 
					
						
							|  |  |  |     if (_locationNodeRemote.hasFocus) { | 
					
						
							|  |  |  |       // ignore
 | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |       // lost focus, change to bread
 | 
					
						
							|  |  |  |       _locationStatusRemote.value = LocationStatus.bread; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   Widget buildBread(bool isLocal) { | 
					
						
							| 
									
										
										
										
											2022-08-16 12:50:08 +08:00
										 |  |  |     final items = getPathBreadCrumbItems(isLocal, (list) { | 
					
						
							|  |  |  |       var path = ""; | 
					
						
							|  |  |  |       for (var item in list) { | 
					
						
							|  |  |  |         path = PathUtil.join(path, item, model.getCurrentIsWindows(isLocal)); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       openDirectory(path, isLocal: isLocal); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |     return items.isEmpty | 
					
						
							|  |  |  |         ? Offstage() | 
					
						
							|  |  |  |         : BreadCrumb( | 
					
						
							|  |  |  |             items: items, | 
					
						
							|  |  |  |             divider: Text("/").paddingSymmetric(horizontal: 4.0), | 
					
						
							|  |  |  |             overflow: ScrollableOverflow( | 
					
						
							|  |  |  |                 controller: getBreadCrumbScrollController(isLocal)), | 
					
						
							|  |  |  |           ); | 
					
						
							| 
									
										
										
										
											2022-08-16 11:46:51 +08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   List<BreadCrumbItem> getPathBreadCrumbItems( | 
					
						
							|  |  |  |       bool isLocal, void Function(List<String>) onPressed) { | 
					
						
							|  |  |  |     final path = model.getCurrentDir(isLocal).path; | 
					
						
							|  |  |  |     final list = PathUtil.split(path, model.getCurrentIsWindows(isLocal)); | 
					
						
							|  |  |  |     final breadCrumbList = List<BreadCrumbItem>.empty(growable: true); | 
					
						
							|  |  |  |     breadCrumbList.addAll(list.asMap().entries.map((e) => BreadCrumbItem( | 
					
						
							|  |  |  |         content: TextButton( | 
					
						
							|  |  |  |             child: Text(e.value), | 
					
						
							|  |  |  |             style: | 
					
						
							|  |  |  |                 ButtonStyle(minimumSize: MaterialStateProperty.all(Size(0, 0))), | 
					
						
							|  |  |  |             onPressed: () => onPressed(list.sublist(0, e.key + 1)))))); | 
					
						
							|  |  |  |     return breadCrumbList; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-16 12:28:12 +08:00
										 |  |  |   breadCrumbScrollToEnd(bool isLocal) { | 
					
						
							|  |  |  |     Future.delayed(Duration(milliseconds: 200), () { | 
					
						
							|  |  |  |       final _breadCrumbScroller = getBreadCrumbScrollController(isLocal); | 
					
						
							|  |  |  |       _breadCrumbScroller.animateTo( | 
					
						
							|  |  |  |           _breadCrumbScroller.position.maxScrollExtent, | 
					
						
							|  |  |  |           duration: Duration(milliseconds: 200), | 
					
						
							|  |  |  |           curve: Curves.fastLinearToSlowEaseIn); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-16 11:46:51 +08:00
										 |  |  |   Widget buildPathLocation(bool isLocal) { | 
					
						
							|  |  |  |     return TextField( | 
					
						
							|  |  |  |       focusNode: isLocal ? _locationNodeLocal : _locationNodeRemote, | 
					
						
							|  |  |  |       decoration: InputDecoration( | 
					
						
							|  |  |  |         border: InputBorder.none, | 
					
						
							|  |  |  |         isDense: true, | 
					
						
							|  |  |  |         prefix: Padding(padding: EdgeInsets.only(left: 4.0)), | 
					
						
							|  |  |  |       ), | 
					
						
							|  |  |  |       controller: | 
					
						
							|  |  |  |           TextEditingController(text: model.getCurrentDir(isLocal).path), | 
					
						
							|  |  |  |       onSubmitted: (path) { | 
					
						
							| 
									
										
										
										
											2022-08-16 12:28:12 +08:00
										 |  |  |         openDirectory(path, isLocal: isLocal); | 
					
						
							| 
									
										
										
										
											2022-08-16 11:46:51 +08:00
										 |  |  |       }, | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2022-08-16 12:06:54 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |   onSearchText(String searchText, bool isLocal) { | 
					
						
							|  |  |  |     if (isLocal) { | 
					
						
							| 
									
										
										
										
											2022-08-16 12:28:12 +08:00
										 |  |  |       _searchTextLocal.value = searchText; | 
					
						
							| 
									
										
										
										
											2022-08-16 12:06:54 +08:00
										 |  |  |     } else { | 
					
						
							| 
									
										
										
										
											2022-08-16 12:28:12 +08:00
										 |  |  |       _searchTextRemote.value = searchText; | 
					
						
							| 
									
										
										
										
											2022-08-16 12:06:54 +08:00
										 |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2022-08-16 12:28:12 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |   openDirectory(String path, {bool isLocal = false}) { | 
					
						
							|  |  |  |     model.openDirectory(path, isLocal: isLocal).then((_) { | 
					
						
							|  |  |  |       breadCrumbScrollToEnd(isLocal); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2022-08-16 13:28:48 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |   void handleDragDone(DropDoneDetails details, bool isLocal) { | 
					
						
							|  |  |  |     if (isLocal) { | 
					
						
							|  |  |  |       // ignore local
 | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     var items = SelectedItems(); | 
					
						
							|  |  |  |     details.files.forEach((file) { | 
					
						
							|  |  |  |       final f = File(file.path); | 
					
						
							|  |  |  |       items.add( | 
					
						
							|  |  |  |           true, | 
					
						
							|  |  |  |           Entry() | 
					
						
							|  |  |  |             ..path = file.path | 
					
						
							|  |  |  |             ..name = file.name | 
					
						
							|  |  |  |             ..size = | 
					
						
							|  |  |  |                 FileSystemEntity.isDirectorySync(f.path) ? 0 : f.lengthSync()); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |     model.sendFiles(items, isRemote: false); | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2022-06-17 22:57:41 +08:00
										 |  |  | } |