| 
									
										
										
										
											2022-10-17 22:26:18 +09:00
										 |  |  | import 'dart:async'; | 
					
						
							| 
									
										
										
										
											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
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-24 14:15:54 +08:00
										 |  |  | import 'package:flutter_hbb/desktop/widgets/dragable_divider.dart'; | 
					
						
							| 
									
										
										
										
											2023-02-22 22:13:21 +01:00
										 |  |  | import 'package:percent_indicator/percent_indicator.dart'; | 
					
						
							| 
									
										
										
										
											2022-08-16 13:28:48 +08:00
										 |  |  | import 'package:desktop_drop/desktop_drop.dart'; | 
					
						
							| 
									
										
										
										
											2022-10-20 11:20:41 +09:00
										 |  |  | import 'package:flutter/gestures.dart'; | 
					
						
							| 
									
										
										
										
											2022-06-17 22:57:41 +08:00
										 |  |  | import 'package:flutter/material.dart'; | 
					
						
							| 
									
										
										
										
											2022-10-17 22:26:18 +09:00
										 |  |  | import 'package:flutter/services.dart'; | 
					
						
							| 
									
										
										
										
											2022-08-16 11:46:51 +08:00
										 |  |  | import 'package:flutter_breadcrumb/flutter_breadcrumb.dart'; | 
					
						
							| 
									
										
										
										
											2022-12-13 12:55:41 +08:00
										 |  |  | import 'package:flutter_hbb/desktop/widgets/list_search_action_listener.dart'; | 
					
						
							| 
									
										
										
										
											2023-02-22 22:13:21 +01:00
										 |  |  | import 'package:flutter_hbb/desktop/widgets/menu_button.dart'; | 
					
						
							| 
									
										
										
										
											2022-10-18 23:56:36 +09:00
										 |  |  | import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart'; | 
					
						
							| 
									
										
										
										
											2022-06-17 22:57:41 +08:00
										 |  |  | import 'package:flutter_hbb/models/file_model.dart'; | 
					
						
							| 
									
										
										
										
											2023-02-22 22:13:21 +01:00
										 |  |  | import 'package:flutter_svg/flutter_svg.dart'; | 
					
						
							| 
									
										
										
										
											2022-06-17 22:57:41 +08:00
										 |  |  | import 'package:get/get.dart'; | 
					
						
							|  |  |  | import 'package:wakelock/wakelock.dart'; | 
					
						
							| 
									
										
										
										
											2023-02-22 22:13:21 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-13 20:22:11 +09:00
										 |  |  | import '../../consts.dart'; | 
					
						
							|  |  |  | import '../../desktop/widgets/material_mod_popup_menu.dart' as mod_menu; | 
					
						
							| 
									
										
										
										
											2022-06-17 22:57:41 +08:00
										 |  |  | import '../../common.dart'; | 
					
						
							|  |  |  | import '../../models/model.dart'; | 
					
						
							| 
									
										
										
										
											2022-08-03 22:03:31 +08:00
										 |  |  | import '../../models/platform_model.dart'; | 
					
						
							| 
									
										
										
										
											2022-10-13 20:22:11 +09:00
										 |  |  | import '../widgets/popup_menu.dart'; | 
					
						
							| 
									
										
										
										
											2022-06-17 22:57:41 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-13 10:17:20 +09:00
										 |  |  | /// status of location bar
 | 
					
						
							|  |  |  | enum LocationStatus { | 
					
						
							|  |  |  |   /// normal bread crumb bar
 | 
					
						
							|  |  |  |   bread, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /// show path text field
 | 
					
						
							|  |  |  |   pathLocation, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /// show file search bar text field
 | 
					
						
							|  |  |  |   fileSearchBar | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2022-08-16 11:46:51 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-12-13 12:55:41 +08:00
										 |  |  | /// The status of currently focused scope of the mouse
 | 
					
						
							|  |  |  | enum MouseFocusScope { | 
					
						
							|  |  |  |   /// Mouse is in local field.
 | 
					
						
							|  |  |  |   local, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /// Mouse is in remote field.
 | 
					
						
							|  |  |  |   remote, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /// Mouse is not in local field, remote neither.
 | 
					
						
							|  |  |  |   none | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-17 22:57:41 +08:00
										 |  |  | class FileManagerPage extends StatefulWidget { | 
					
						
							| 
									
										
										
										
											2023-02-13 16:40:24 +08:00
										 |  |  |   const FileManagerPage({Key? key, required this.id, this.forceRelay}) | 
					
						
							|  |  |  |       : super(key: key); | 
					
						
							| 
									
										
										
										
											2022-06-17 22:57:41 +08:00
										 |  |  |   final String id; | 
					
						
							| 
									
										
										
										
											2023-02-13 16:40:24 +08:00
										 |  |  |   final bool? forceRelay; | 
					
						
							| 
									
										
										
										
											2022-06-17 22:57:41 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   State<StatefulWidget> createState() => _FileManagerPageState(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class _FileManagerPageState extends State<FileManagerPage> | 
					
						
							|  |  |  |     with AutomaticKeepAliveClientMixin { | 
					
						
							| 
									
										
										
										
											2022-12-13 12:55:41 +08:00
										 |  |  |   final _mouseFocusScope = Rx<MouseFocusScope>(MouseFocusScope.none); | 
					
						
							| 
									
										
										
										
											2022-10-17 22:26:18 +09:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-13 10:17:20 +09:00
										 |  |  |   final _dropMaskVisible = false.obs; // TODO impl drop mask
 | 
					
						
							| 
									
										
										
										
											2023-02-08 22:01:15 +09:00
										 |  |  |   final _overlayKeyState = OverlayKeyState(); | 
					
						
							| 
									
										
										
										
											2022-08-16 13:28:48 +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; | 
					
						
							| 
									
										
										
										
											2023-03-08 22:32:55 +09:00
										 |  |  |   JobController get jobController => model.jobController; | 
					
						
							| 
									
										
										
										
											2022-06-17 22:57:41 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   void initState() { | 
					
						
							|  |  |  |     super.initState(); | 
					
						
							| 
									
										
										
										
											2022-08-12 18:42:02 +08:00
										 |  |  |     _ffi = FFI(); | 
					
						
							| 
									
										
										
										
											2023-02-13 16:40:24 +08:00
										 |  |  |     _ffi.start(widget.id, isFileTransfer: true, forceRelay: widget.forceRelay); | 
					
						
							| 
									
										
										
										
											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}"); | 
					
						
							| 
									
										
										
										
											2023-02-08 22:01:15 +09:00
										 |  |  |     _ffi.dialogManager.setOverlayState(_overlayKeyState); | 
					
						
							| 
									
										
										
										
											2022-06-17 22:57:41 +08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   void dispose() { | 
					
						
							| 
									
										
										
										
											2023-03-08 21:05:55 +09:00
										 |  |  |     model.close().whenComplete(() { | 
					
						
							| 
									
										
										
										
											2022-12-04 22:41:44 +09:00
										 |  |  |       _ffi.close(); | 
					
						
							|  |  |  |       _ffi.dialogManager.dismissAll(); | 
					
						
							|  |  |  |       if (!Platform.isLinux) { | 
					
						
							|  |  |  |         Wakelock.disable(); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       Get.delete<FFI>(tag: 'ft_${widget.id}'); | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2022-06-17 22:57:41 +08:00
										 |  |  |     super.dispose(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-08 00:49:14 +09:00
										 |  |  |   @override | 
					
						
							|  |  |  |   bool get wantKeepAlive => true; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-17 22:57:41 +08:00
										 |  |  |   @override | 
					
						
							|  |  |  |   Widget build(BuildContext context) { | 
					
						
							|  |  |  |     super.build(context); | 
					
						
							| 
									
										
										
										
											2023-02-08 22:01:15 +09:00
										 |  |  |     return Overlay(key: _overlayKeyState.key, initialEntries: [ | 
					
						
							|  |  |  |       OverlayEntry(builder: (_) { | 
					
						
							| 
									
										
										
										
											2023-03-08 22:32:55 +09:00
										 |  |  |         return Scaffold( | 
					
						
							|  |  |  |           backgroundColor: Theme.of(context).scaffoldBackgroundColor, | 
					
						
							|  |  |  |           body: Row( | 
					
						
							|  |  |  |             children: [ | 
					
						
							|  |  |  |               Flexible( | 
					
						
							|  |  |  |                   flex: 3, | 
					
						
							|  |  |  |                   child: dropArea(FileManagerView( | 
					
						
							|  |  |  |                       model.localController, _ffi, _mouseFocusScope))), | 
					
						
							|  |  |  |               Flexible( | 
					
						
							|  |  |  |                   flex: 3, | 
					
						
							|  |  |  |                   child: dropArea(FileManagerView( | 
					
						
							|  |  |  |                       model.remoteController, _ffi, _mouseFocusScope))), | 
					
						
							|  |  |  |               Flexible(flex: 2, child: statusList()) | 
					
						
							|  |  |  |             ], | 
					
						
							|  |  |  |           ), | 
					
						
							|  |  |  |         ); | 
					
						
							| 
									
										
										
										
											2022-08-19 12:44:35 +08:00
										 |  |  |       }) | 
					
						
							|  |  |  |     ]); | 
					
						
							| 
									
										
										
										
											2022-06-17 22:57:41 +08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-08 00:49:14 +09:00
										 |  |  |   Widget dropArea(FileManagerView fileView) { | 
					
						
							|  |  |  |     return DropTarget( | 
					
						
							|  |  |  |         onDragDone: (detail) => | 
					
						
							|  |  |  |             handleDragDone(detail, fileView.controller.isLocal), | 
					
						
							| 
									
										
										
										
											2022-08-16 13:28:48 +08:00
										 |  |  |         onDragEntered: (enter) { | 
					
						
							|  |  |  |           _dropMaskVisible.value = true; | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |         onDragExited: (exit) { | 
					
						
							|  |  |  |           _dropMaskVisible.value = false; | 
					
						
							|  |  |  |         }, | 
					
						
							| 
									
										
										
										
											2023-03-08 00:49:14 +09:00
										 |  |  |         child: fileView); | 
					
						
							| 
									
										
										
										
											2022-10-17 22:26:18 +09:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-26 09:13:42 +01:00
										 |  |  |   Widget generateCard(Widget child) { | 
					
						
							|  |  |  |     return Container( | 
					
						
							|  |  |  |       decoration: BoxDecoration( | 
					
						
							|  |  |  |         color: Theme.of(context).cardColor, | 
					
						
							|  |  |  |         borderRadius: BorderRadius.all( | 
					
						
							|  |  |  |           Radius.circular(15.0), | 
					
						
							|  |  |  |         ), | 
					
						
							|  |  |  |       ), | 
					
						
							|  |  |  |       child: child, | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-07-01 12:08:52 +08:00
										 |  |  |   /// transfer status list
 | 
					
						
							|  |  |  |   /// watch transfer status
 | 
					
						
							|  |  |  |   Widget statusList() { | 
					
						
							| 
									
										
										
										
											2023-03-09 11:40:06 +09:00
										 |  |  |     statusListView(List<JobProgress> jobs) => ListView.builder( | 
					
						
							| 
									
										
										
										
											2023-03-09 00:06:24 +09:00
										 |  |  |           controller: ScrollController(), | 
					
						
							|  |  |  |           itemBuilder: (BuildContext context, int index) { | 
					
						
							| 
									
										
										
										
											2023-03-09 11:40:06 +09:00
										 |  |  |             final item = jobs[index]; | 
					
						
							| 
									
										
										
										
											2023-03-09 00:06:24 +09:00
										 |  |  |             return Padding( | 
					
						
							|  |  |  |               padding: const EdgeInsets.only(bottom: 5), | 
					
						
							|  |  |  |               child: generateCard( | 
					
						
							|  |  |  |                 Column( | 
					
						
							|  |  |  |                   mainAxisSize: MainAxisSize.min, | 
					
						
							|  |  |  |                   children: [ | 
					
						
							|  |  |  |                     Row( | 
					
						
							|  |  |  |                       crossAxisAlignment: CrossAxisAlignment.center, | 
					
						
							|  |  |  |                       children: [ | 
					
						
							|  |  |  |                         Transform.rotate( | 
					
						
							|  |  |  |                           angle: item.isRemoteToLocal ? pi : 0, | 
					
						
							|  |  |  |                           child: SvgPicture.asset( | 
					
						
							|  |  |  |                             "assets/arrow.svg", | 
					
						
							|  |  |  |                             color: Theme.of(context).tabBarTheme.labelColor, | 
					
						
							|  |  |  |                           ), | 
					
						
							|  |  |  |                         ).paddingOnly(left: 15), | 
					
						
							|  |  |  |                         const SizedBox( | 
					
						
							|  |  |  |                           width: 16.0, | 
					
						
							|  |  |  |                         ), | 
					
						
							|  |  |  |                         Expanded( | 
					
						
							|  |  |  |                           child: Column( | 
					
						
							|  |  |  |                             mainAxisSize: MainAxisSize.min, | 
					
						
							|  |  |  |                             crossAxisAlignment: CrossAxisAlignment.start, | 
					
						
							|  |  |  |                             children: [ | 
					
						
							|  |  |  |                               Tooltip( | 
					
						
							|  |  |  |                                 waitDuration: Duration(milliseconds: 500), | 
					
						
							|  |  |  |                                 message: item.jobName, | 
					
						
							|  |  |  |                                 child: Text( | 
					
						
							|  |  |  |                                   item.fileName, | 
					
						
							|  |  |  |                                   maxLines: 1, | 
					
						
							|  |  |  |                                   overflow: TextOverflow.ellipsis, | 
					
						
							|  |  |  |                                 ).paddingSymmetric(vertical: 10), | 
					
						
							|  |  |  |                               ), | 
					
						
							|  |  |  |                               Text( | 
					
						
							|  |  |  |                                 '${translate("Total")} ${readableFileSize(item.totalSize.toDouble())}', | 
					
						
							|  |  |  |                                 style: TextStyle( | 
					
						
							|  |  |  |                                   fontSize: 12, | 
					
						
							|  |  |  |                                   color: MyTheme.darkGray, | 
					
						
							|  |  |  |                                 ), | 
					
						
							|  |  |  |                               ), | 
					
						
							|  |  |  |                               Offstage( | 
					
						
							|  |  |  |                                 offstage: item.state != JobState.inProgress, | 
					
						
							|  |  |  |                                 child: Text( | 
					
						
							|  |  |  |                                   '${translate("Speed")} ${readableFileSize(item.speed)}/s', | 
					
						
							|  |  |  |                                   style: TextStyle( | 
					
						
							|  |  |  |                                     fontSize: 12, | 
					
						
							|  |  |  |                                     color: MyTheme.darkGray, | 
					
						
							| 
									
										
										
										
											2023-02-25 09:44:23 +01:00
										 |  |  |                                   ), | 
					
						
							|  |  |  |                                 ), | 
					
						
							| 
									
										
										
										
											2023-03-09 00:06:24 +09:00
										 |  |  |                               ), | 
					
						
							|  |  |  |                               Offstage( | 
					
						
							|  |  |  |                                 offstage: item.state == JobState.inProgress, | 
					
						
							|  |  |  |                                 child: Text( | 
					
						
							|  |  |  |                                   translate( | 
					
						
							|  |  |  |                                     item.display(), | 
					
						
							|  |  |  |                                   ), | 
					
						
							|  |  |  |                                   style: TextStyle( | 
					
						
							|  |  |  |                                     fontSize: 12, | 
					
						
							|  |  |  |                                     color: MyTheme.darkGray, | 
					
						
							| 
									
										
										
										
											2023-02-22 22:13:21 +01:00
										 |  |  |                                   ), | 
					
						
							| 
									
										
										
										
											2023-02-25 09:44:23 +01:00
										 |  |  |                                 ), | 
					
						
							| 
									
										
										
										
											2023-03-09 00:06:24 +09:00
										 |  |  |                               ), | 
					
						
							|  |  |  |                               Offstage( | 
					
						
							|  |  |  |                                 offstage: item.state != JobState.inProgress, | 
					
						
							|  |  |  |                                 child: LinearPercentIndicator( | 
					
						
							|  |  |  |                                   padding: EdgeInsets.only(right: 15), | 
					
						
							|  |  |  |                                   animateFromLastPercent: true, | 
					
						
							|  |  |  |                                   center: Text( | 
					
						
							|  |  |  |                                     '${(item.finishedSize / item.totalSize * 100).toStringAsFixed(0)}%', | 
					
						
							|  |  |  |                                   ), | 
					
						
							|  |  |  |                                   barRadius: Radius.circular(15), | 
					
						
							|  |  |  |                                   percent: item.finishedSize / item.totalSize, | 
					
						
							|  |  |  |                                   progressColor: MyTheme.accent, | 
					
						
							|  |  |  |                                   backgroundColor: Theme.of(context).hoverColor, | 
					
						
							|  |  |  |                                   lineHeight: kDesktopFileTransferRowHeight, | 
					
						
							|  |  |  |                                 ).paddingSymmetric(vertical: 15), | 
					
						
							|  |  |  |                               ), | 
					
						
							|  |  |  |                             ], | 
					
						
							|  |  |  |                           ), | 
					
						
							|  |  |  |                         ), | 
					
						
							|  |  |  |                         Row( | 
					
						
							|  |  |  |                           mainAxisAlignment: MainAxisAlignment.end, | 
					
						
							|  |  |  |                           children: [ | 
					
						
							|  |  |  |                             Offstage( | 
					
						
							|  |  |  |                               offstage: item.state != JobState.paused, | 
					
						
							|  |  |  |                               child: MenuButton( | 
					
						
							|  |  |  |                                 onPressed: () { | 
					
						
							|  |  |  |                                   jobController.resumeJob(item.id); | 
					
						
							|  |  |  |                                 }, | 
					
						
							|  |  |  |                                 child: SvgPicture.asset( | 
					
						
							|  |  |  |                                   "assets/refresh.svg", | 
					
						
							|  |  |  |                                   color: Colors.white, | 
					
						
							| 
									
										
										
										
											2023-02-25 09:44:23 +01:00
										 |  |  |                                 ), | 
					
						
							| 
									
										
										
										
											2023-03-09 00:06:24 +09:00
										 |  |  |                                 color: MyTheme.accent, | 
					
						
							|  |  |  |                                 hoverColor: MyTheme.accent80, | 
					
						
							|  |  |  |                               ), | 
					
						
							|  |  |  |                             ), | 
					
						
							|  |  |  |                             MenuButton( | 
					
						
							|  |  |  |                               padding: EdgeInsets.only(right: 15), | 
					
						
							|  |  |  |                               child: SvgPicture.asset( | 
					
						
							|  |  |  |                                 "assets/close.svg", | 
					
						
							|  |  |  |                                 color: Colors.white, | 
					
						
							|  |  |  |                               ), | 
					
						
							|  |  |  |                               onPressed: () { | 
					
						
							|  |  |  |                                 jobController.jobTable.removeAt(index); | 
					
						
							|  |  |  |                                 jobController.cancelJob(item.id); | 
					
						
							|  |  |  |                               }, | 
					
						
							|  |  |  |                               color: MyTheme.accent, | 
					
						
							|  |  |  |                               hoverColor: MyTheme.accent80, | 
					
						
							| 
									
										
										
										
											2023-02-25 09:44:23 +01:00
										 |  |  |                             ), | 
					
						
							| 
									
										
										
										
											2023-02-26 09:13:42 +01:00
										 |  |  |                           ], | 
					
						
							| 
									
										
										
										
											2023-03-09 00:06:24 +09:00
										 |  |  |                         ), | 
					
						
							|  |  |  |                       ], | 
					
						
							|  |  |  |                     ), | 
					
						
							|  |  |  |                   ], | 
					
						
							|  |  |  |                 ).paddingSymmetric(vertical: 10), | 
					
						
							| 
									
										
										
										
											2023-02-26 09:13:42 +01:00
										 |  |  |               ), | 
					
						
							| 
									
										
										
										
											2023-03-09 00:06:24 +09:00
										 |  |  |             ); | 
					
						
							|  |  |  |           }, | 
					
						
							|  |  |  |           itemCount: jobController.jobTable.length, | 
					
						
							| 
									
										
										
										
											2023-03-09 11:40:06 +09:00
										 |  |  |         ); | 
					
						
							| 
									
										
										
										
											2023-03-09 00:06:24 +09:00
										 |  |  | 
 | 
					
						
							|  |  |  |     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), | 
					
						
							|  |  |  |           child: Obx( | 
					
						
							|  |  |  |             () => jobController.jobTable.isEmpty | 
					
						
							|  |  |  |                 ? generateCard( | 
					
						
							|  |  |  |                     Center( | 
					
						
							|  |  |  |                       child: Column( | 
					
						
							|  |  |  |                         mainAxisAlignment: MainAxisAlignment.center, | 
					
						
							|  |  |  |                         children: [ | 
					
						
							|  |  |  |                           SvgPicture.asset( | 
					
						
							|  |  |  |                             "assets/transfer.svg", | 
					
						
							|  |  |  |                             color: Theme.of(context).tabBarTheme.labelColor, | 
					
						
							|  |  |  |                             height: 40, | 
					
						
							|  |  |  |                           ).paddingOnly(bottom: 10), | 
					
						
							|  |  |  |                           Text( | 
					
						
							|  |  |  |                             translate("No transfers in progress"), | 
					
						
							|  |  |  |                             textAlign: TextAlign.center, | 
					
						
							|  |  |  |                             textScaleFactor: 1.20, | 
					
						
							|  |  |  |                             style: TextStyle( | 
					
						
							|  |  |  |                                 color: | 
					
						
							|  |  |  |                                     Theme.of(context).tabBarTheme.labelColor), | 
					
						
							|  |  |  |                           ), | 
					
						
							|  |  |  |                         ], | 
					
						
							|  |  |  |                       ), | 
					
						
							|  |  |  |                     ), | 
					
						
							|  |  |  |                   ) | 
					
						
							| 
									
										
										
										
											2023-03-09 11:40:06 +09:00
										 |  |  |                 : statusListView(jobController.jobTable), | 
					
						
							| 
									
										
										
										
											2023-03-09 00:06:24 +09:00
										 |  |  |           )), | 
					
						
							| 
									
										
										
										
											2023-02-25 09:44:23 +01:00
										 |  |  |     ); | 
					
						
							| 
									
										
										
										
											2022-07-01 12:08:52 +08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-08 00:49:14 +09:00
										 |  |  |   void handleDragDone(DropDoneDetails details, bool isLocal) { | 
					
						
							|  |  |  |     if (isLocal) { | 
					
						
							|  |  |  |       // ignore local
 | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2023-03-08 22:32:55 +09:00
										 |  |  |     final items = SelectedItems(isLocal: false); | 
					
						
							| 
									
										
										
										
											2023-03-08 00:49:14 +09:00
										 |  |  |     for (var file in details.files) { | 
					
						
							|  |  |  |       final f = File(file.path); | 
					
						
							| 
									
										
										
										
											2023-03-08 22:32:55 +09:00
										 |  |  |       items.add(Entry() | 
					
						
							|  |  |  |         ..path = file.path | 
					
						
							|  |  |  |         ..name = file.name | 
					
						
							|  |  |  |         ..size = FileSystemEntity.isDirectorySync(f.path) ? 0 : f.lengthSync()); | 
					
						
							| 
									
										
										
										
											2023-03-08 00:49:14 +09:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2023-03-08 23:06:34 +09:00
										 |  |  |     final otherSideData = model.localController.directoryData(); | 
					
						
							|  |  |  |     model.remoteController.sendFiles(items, otherSideData); | 
					
						
							| 
									
										
										
										
											2023-03-08 00:49:14 +09:00
										 |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class FileManagerView extends StatefulWidget { | 
					
						
							|  |  |  |   final FileController controller; | 
					
						
							|  |  |  |   final FFI _ffi; | 
					
						
							|  |  |  |   final Rx<MouseFocusScope> _mouseFocusScope; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   FileManagerView(this.controller, this._ffi, this._mouseFocusScope); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   State<StatefulWidget> createState() => _FileManagerViewState(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class _FileManagerViewState extends State<FileManagerView> { | 
					
						
							|  |  |  |   final _locationStatus = LocationStatus.bread.obs; | 
					
						
							|  |  |  |   final _locationNode = FocusNode(); | 
					
						
							|  |  |  |   final _locationBarKey = GlobalKey(); | 
					
						
							|  |  |  |   final _searchText = "".obs; | 
					
						
							|  |  |  |   final _breadCrumbScroller = ScrollController(); | 
					
						
							|  |  |  |   final _keyboardNode = FocusNode(); | 
					
						
							|  |  |  |   final _listSearchBuffer = TimeoutStringBuffer(); | 
					
						
							|  |  |  |   final _nameColWidth = kDesktopFileTransferNameColWidth.obs; | 
					
						
							|  |  |  |   final _modifiedColWidth = kDesktopFileTransferModifiedColWidth.obs; | 
					
						
							|  |  |  |   final _fileListScrollController = ScrollController(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /// [_lastClickTime], [_lastClickEntry] help to handle double click
 | 
					
						
							|  |  |  |   var _lastClickTime = | 
					
						
							|  |  |  |       DateTime.now().millisecondsSinceEpoch - bind.getDoubleClickTime() - 1000; | 
					
						
							|  |  |  |   Entry? _lastClickEntry; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   FileController get controller => widget.controller; | 
					
						
							|  |  |  |   bool get isLocal => widget.controller.isLocal; | 
					
						
							|  |  |  |   FFI get _ffi => widget._ffi; | 
					
						
							| 
									
										
										
										
											2023-03-09 18:05:09 +09:00
										 |  |  |   SelectedItems get selectedItems => controller.selectedItems; | 
					
						
							| 
									
										
										
										
											2023-03-08 00:49:14 +09:00
										 |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   void initState() { | 
					
						
							|  |  |  |     super.initState(); | 
					
						
							|  |  |  |     // register location listener
 | 
					
						
							|  |  |  |     _locationNode.addListener(onLocationFocusChanged); | 
					
						
							| 
									
										
										
										
											2023-03-08 22:32:55 +09:00
										 |  |  |     controller.directory.listen((e) => breadCrumbScrollToEnd()); | 
					
						
							| 
									
										
										
										
											2023-03-08 00:49:14 +09:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   void dispose() { | 
					
						
							|  |  |  |     _locationNode.removeListener(onLocationFocusChanged); | 
					
						
							|  |  |  |     _locationNode.dispose(); | 
					
						
							|  |  |  |     _keyboardNode.dispose(); | 
					
						
							|  |  |  |     _breadCrumbScroller.dispose(); | 
					
						
							|  |  |  |     _fileListScrollController.dispose(); | 
					
						
							|  |  |  |     super.dispose(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   Widget build(BuildContext context) { | 
					
						
							|  |  |  |     return Container( | 
					
						
							|  |  |  |       margin: const EdgeInsets.all(16.0), | 
					
						
							|  |  |  |       padding: const EdgeInsets.all(8.0), | 
					
						
							|  |  |  |       child: Column( | 
					
						
							|  |  |  |         crossAxisAlignment: CrossAxisAlignment.start, | 
					
						
							|  |  |  |         children: [ | 
					
						
							|  |  |  |           headTools(), | 
					
						
							|  |  |  |           Expanded( | 
					
						
							|  |  |  |             child: Row( | 
					
						
							|  |  |  |               crossAxisAlignment: CrossAxisAlignment.start, | 
					
						
							|  |  |  |               children: [ | 
					
						
							|  |  |  |                 Expanded( | 
					
						
							|  |  |  |                     child: MouseRegion( | 
					
						
							|  |  |  |                   onEnter: (evt) { | 
					
						
							|  |  |  |                     widget._mouseFocusScope.value = isLocal | 
					
						
							|  |  |  |                         ? MouseFocusScope.local | 
					
						
							|  |  |  |                         : MouseFocusScope.remote; | 
					
						
							|  |  |  |                     _keyboardNode.requestFocus(); | 
					
						
							|  |  |  |                   }, | 
					
						
							|  |  |  |                   onExit: (evt) => | 
					
						
							|  |  |  |                       widget._mouseFocusScope.value = MouseFocusScope.none, | 
					
						
							| 
									
										
										
										
											2023-03-09 00:06:24 +09:00
										 |  |  |                   child: _buildFileList(context, _fileListScrollController), | 
					
						
							| 
									
										
										
										
											2023-03-08 00:49:14 +09:00
										 |  |  |                 )) | 
					
						
							|  |  |  |               ], | 
					
						
							|  |  |  |             ), | 
					
						
							|  |  |  |           ), | 
					
						
							|  |  |  |         ], | 
					
						
							|  |  |  |       ), | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   void onLocationFocusChanged() { | 
					
						
							|  |  |  |     debugPrint("focus changed on local"); | 
					
						
							|  |  |  |     if (_locationNode.hasFocus) { | 
					
						
							|  |  |  |       // ignore
 | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |       // lost focus, change to bread
 | 
					
						
							|  |  |  |       if (_locationStatus.value != LocationStatus.fileSearchBar) { | 
					
						
							|  |  |  |         _locationStatus.value = LocationStatus.bread; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   Widget headTools() { | 
					
						
							|  |  |  |     return Container( | 
					
						
							|  |  |  |       child: Column( | 
					
						
							|  |  |  |         children: [ | 
					
						
							|  |  |  |           // symbols
 | 
					
						
							|  |  |  |           PreferredSize( | 
					
						
							|  |  |  |                   child: Row( | 
					
						
							|  |  |  |                     crossAxisAlignment: CrossAxisAlignment.center, | 
					
						
							|  |  |  |                     children: [ | 
					
						
							|  |  |  |                       Container( | 
					
						
							|  |  |  |                           width: 50, | 
					
						
							|  |  |  |                           height: 50, | 
					
						
							|  |  |  |                           decoration: BoxDecoration( | 
					
						
							|  |  |  |                             borderRadius: BorderRadius.all(Radius.circular(8)), | 
					
						
							|  |  |  |                             color: MyTheme.accent, | 
					
						
							|  |  |  |                           ), | 
					
						
							|  |  |  |                           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: Theme.of(context) | 
					
						
							|  |  |  |                                         .tabBarTheme | 
					
						
							|  |  |  |                                         .labelColor, | 
					
						
							|  |  |  |                                   ); | 
					
						
							|  |  |  |                                 } | 
					
						
							|  |  |  |                               })), | 
					
						
							|  |  |  |                       Text(isLocal | 
					
						
							|  |  |  |                               ? translate("Local Computer") | 
					
						
							|  |  |  |                               : translate("Remote Computer")) | 
					
						
							|  |  |  |                           .marginOnly(left: 8.0) | 
					
						
							|  |  |  |                     ], | 
					
						
							|  |  |  |                   ), | 
					
						
							|  |  |  |                   preferredSize: Size(double.infinity, 70)) | 
					
						
							| 
									
										
										
										
											2023-02-22 22:13:21 +01:00
										 |  |  |               .paddingOnly(bottom: 15), | 
					
						
							|  |  |  |           // buttons
 | 
					
						
							|  |  |  |           Row( | 
					
						
							|  |  |  |             children: [ | 
					
						
							|  |  |  |               Row( | 
					
						
							|  |  |  |                 children: [ | 
					
						
							|  |  |  |                   MenuButton( | 
					
						
							|  |  |  |                     padding: EdgeInsets.only( | 
					
						
							|  |  |  |                       right: 3, | 
					
						
							|  |  |  |                     ), | 
					
						
							| 
									
										
										
										
											2023-02-23 14:53:24 +01:00
										 |  |  |                     child: RotatedBox( | 
					
						
							|  |  |  |                       quarterTurns: 2, | 
					
						
							|  |  |  |                       child: SvgPicture.asset( | 
					
						
							|  |  |  |                         "assets/arrow.svg", | 
					
						
							|  |  |  |                         color: Theme.of(context).tabBarTheme.labelColor, | 
					
						
							|  |  |  |                       ), | 
					
						
							| 
									
										
										
										
											2023-02-22 22:13:21 +01:00
										 |  |  |                     ), | 
					
						
							|  |  |  |                     color: Theme.of(context).cardColor, | 
					
						
							|  |  |  |                     hoverColor: Theme.of(context).hoverColor, | 
					
						
							|  |  |  |                     onPressed: () { | 
					
						
							| 
									
										
										
										
											2023-03-09 18:05:09 +09:00
										 |  |  |                       selectedItems.clear(); | 
					
						
							| 
									
										
										
										
											2023-03-08 22:32:55 +09:00
										 |  |  |                       controller.goBack(); | 
					
						
							| 
									
										
										
										
											2023-02-22 22:13:21 +01:00
										 |  |  |                     }, | 
					
						
							|  |  |  |                   ), | 
					
						
							|  |  |  |                   MenuButton( | 
					
						
							|  |  |  |                     child: RotatedBox( | 
					
						
							|  |  |  |                       quarterTurns: 3, | 
					
						
							|  |  |  |                       child: SvgPicture.asset( | 
					
						
							|  |  |  |                         "assets/arrow.svg", | 
					
						
							|  |  |  |                         color: Theme.of(context).tabBarTheme.labelColor, | 
					
						
							|  |  |  |                       ), | 
					
						
							|  |  |  |                     ), | 
					
						
							|  |  |  |                     color: Theme.of(context).cardColor, | 
					
						
							|  |  |  |                     hoverColor: Theme.of(context).hoverColor, | 
					
						
							|  |  |  |                     onPressed: () { | 
					
						
							| 
									
										
										
										
											2023-03-09 18:05:09 +09:00
										 |  |  |                       selectedItems.clear(); | 
					
						
							| 
									
										
										
										
											2023-03-08 22:32:55 +09:00
										 |  |  |                       controller.goToParentDirectory(); | 
					
						
							| 
									
										
										
										
											2023-02-22 22:13:21 +01:00
										 |  |  |                     }, | 
					
						
							|  |  |  |                   ), | 
					
						
							|  |  |  |                 ], | 
					
						
							|  |  |  |               ), | 
					
						
							|  |  |  |               Expanded( | 
					
						
							|  |  |  |                 child: Padding( | 
					
						
							|  |  |  |                   padding: const EdgeInsets.symmetric(horizontal: 3.0), | 
					
						
							|  |  |  |                   child: Container( | 
					
						
							|  |  |  |                     decoration: BoxDecoration( | 
					
						
							|  |  |  |                       color: Theme.of(context).cardColor, | 
					
						
							|  |  |  |                       borderRadius: BorderRadius.all( | 
					
						
							|  |  |  |                         Radius.circular(8.0), | 
					
						
							|  |  |  |                       ), | 
					
						
							|  |  |  |                     ), | 
					
						
							|  |  |  |                     child: Padding( | 
					
						
							|  |  |  |                       padding: EdgeInsets.symmetric(vertical: 2.5), | 
					
						
							|  |  |  |                       child: GestureDetector( | 
					
						
							|  |  |  |                         onTap: () { | 
					
						
							| 
									
										
										
										
											2023-03-08 00:49:14 +09:00
										 |  |  |                           _locationStatus.value = | 
					
						
							|  |  |  |                               _locationStatus.value == LocationStatus.bread | 
					
						
							| 
									
										
										
										
											2023-02-22 22:13:21 +01:00
										 |  |  |                                   ? LocationStatus.pathLocation | 
					
						
							|  |  |  |                                   : LocationStatus.bread; | 
					
						
							|  |  |  |                           Future.delayed(Duration.zero, () { | 
					
						
							| 
									
										
										
										
											2023-03-08 00:49:14 +09:00
										 |  |  |                             if (_locationStatus.value == | 
					
						
							| 
									
										
										
										
											2023-02-22 22:13:21 +01:00
										 |  |  |                                 LocationStatus.pathLocation) { | 
					
						
							| 
									
										
										
										
											2023-03-08 00:49:14 +09:00
										 |  |  |                               _locationNode.requestFocus(); | 
					
						
							| 
									
										
										
										
											2023-02-22 22:13:21 +01:00
										 |  |  |                             } | 
					
						
							|  |  |  |                           }); | 
					
						
							|  |  |  |                         }, | 
					
						
							|  |  |  |                         child: Obx( | 
					
						
							|  |  |  |                           () => Container( | 
					
						
							|  |  |  |                             child: Row( | 
					
						
							|  |  |  |                               children: [ | 
					
						
							|  |  |  |                                 Expanded( | 
					
						
							| 
									
										
										
										
											2023-03-08 00:49:14 +09:00
										 |  |  |                                     child: _locationStatus.value == | 
					
						
							| 
									
										
										
										
											2023-02-22 22:13:21 +01:00
										 |  |  |                                             LocationStatus.bread | 
					
						
							| 
									
										
										
										
											2023-03-08 22:32:55 +09:00
										 |  |  |                                         ? buildBread() | 
					
						
							|  |  |  |                                         : buildPathLocation()), | 
					
						
							| 
									
										
										
										
											2023-02-22 22:13:21 +01:00
										 |  |  |                               ], | 
					
						
							|  |  |  |                             ), | 
					
						
							|  |  |  |                           ), | 
					
						
							|  |  |  |                         ), | 
					
						
							|  |  |  |                       ), | 
					
						
							|  |  |  |                     ), | 
					
						
							|  |  |  |                   ), | 
					
						
							|  |  |  |                 ), | 
					
						
							|  |  |  |               ), | 
					
						
							|  |  |  |               Obx(() { | 
					
						
							| 
									
										
										
										
											2023-03-08 00:49:14 +09:00
										 |  |  |                 switch (_locationStatus.value) { | 
					
						
							| 
									
										
										
										
											2023-02-22 22:13:21 +01:00
										 |  |  |                   case LocationStatus.bread: | 
					
						
							|  |  |  |                     return MenuButton( | 
					
						
							| 
									
										
										
										
											2022-10-13 10:17:20 +09:00
										 |  |  |                       onPressed: () { | 
					
						
							| 
									
										
										
										
											2023-03-08 00:49:14 +09:00
										 |  |  |                         _locationStatus.value = LocationStatus.fileSearchBar; | 
					
						
							| 
									
										
										
										
											2022-10-13 10:17:20 +09:00
										 |  |  |                         Future.delayed( | 
					
						
							| 
									
										
										
										
											2023-03-08 00:49:14 +09:00
										 |  |  |                             Duration.zero, () => _locationNode.requestFocus()); | 
					
						
							| 
									
										
										
										
											2022-10-13 10:17:20 +09:00
										 |  |  |                       }, | 
					
						
							| 
									
										
										
										
											2023-02-22 22:13:21 +01:00
										 |  |  |                       child: SvgPicture.asset( | 
					
						
							|  |  |  |                         "assets/search.svg", | 
					
						
							|  |  |  |                         color: Theme.of(context).tabBarTheme.labelColor, | 
					
						
							|  |  |  |                       ), | 
					
						
							|  |  |  |                       color: Theme.of(context).cardColor, | 
					
						
							|  |  |  |                       hoverColor: Theme.of(context).hoverColor, | 
					
						
							|  |  |  |                     ); | 
					
						
							|  |  |  |                   case LocationStatus.pathLocation: | 
					
						
							|  |  |  |                     return MenuButton( | 
					
						
							| 
									
										
										
										
											2022-10-13 10:17:20 +09:00
										 |  |  |                       onPressed: null, | 
					
						
							| 
									
										
										
										
											2023-02-22 22:13:21 +01:00
										 |  |  |                       child: SvgPicture.asset( | 
					
						
							|  |  |  |                         "assets/close.svg", | 
					
						
							|  |  |  |                         color: Theme.of(context).tabBarTheme.labelColor, | 
					
						
							|  |  |  |                       ), | 
					
						
							| 
									
										
										
										
											2022-10-13 10:17:20 +09:00
										 |  |  |                       color: Theme.of(context).disabledColor, | 
					
						
							| 
									
										
										
										
											2023-02-22 22:13:21 +01:00
										 |  |  |                       hoverColor: Theme.of(context).hoverColor, | 
					
						
							|  |  |  |                     ); | 
					
						
							|  |  |  |                   case LocationStatus.fileSearchBar: | 
					
						
							|  |  |  |                     return MenuButton( | 
					
						
							| 
									
										
										
										
											2022-10-13 10:17:20 +09:00
										 |  |  |                       onPressed: () { | 
					
						
							|  |  |  |                         onSearchText("", isLocal); | 
					
						
							| 
									
										
										
										
											2023-03-08 00:49:14 +09:00
										 |  |  |                         _locationStatus.value = LocationStatus.bread; | 
					
						
							| 
									
										
										
										
											2022-10-13 10:17:20 +09:00
										 |  |  |                       }, | 
					
						
							| 
									
										
										
										
											2023-02-22 22:13:21 +01:00
										 |  |  |                       child: SvgPicture.asset( | 
					
						
							|  |  |  |                         "assets/close.svg", | 
					
						
							|  |  |  |                         color: Theme.of(context).tabBarTheme.labelColor, | 
					
						
							|  |  |  |                       ), | 
					
						
							|  |  |  |                       color: Theme.of(context).cardColor, | 
					
						
							|  |  |  |                       hoverColor: Theme.of(context).hoverColor, | 
					
						
							|  |  |  |                     ); | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |               }), | 
					
						
							|  |  |  |               MenuButton( | 
					
						
							|  |  |  |                 padding: EdgeInsets.only( | 
					
						
							|  |  |  |                   left: 3, | 
					
						
							|  |  |  |                 ), | 
					
						
							| 
									
										
										
										
											2022-08-16 11:46:51 +08:00
										 |  |  |                 onPressed: () { | 
					
						
							| 
									
										
										
										
											2023-03-08 22:32:55 +09:00
										 |  |  |                   controller.refresh(); | 
					
						
							| 
									
										
										
										
											2022-08-16 11:46:51 +08:00
										 |  |  |                 }, | 
					
						
							| 
									
										
										
										
											2023-02-22 22:13:21 +01:00
										 |  |  |                 child: SvgPicture.asset( | 
					
						
							|  |  |  |                   "assets/refresh.svg", | 
					
						
							|  |  |  |                   color: Theme.of(context).tabBarTheme.labelColor, | 
					
						
							|  |  |  |                 ), | 
					
						
							|  |  |  |                 color: Theme.of(context).cardColor, | 
					
						
							|  |  |  |                 hoverColor: Theme.of(context).hoverColor, | 
					
						
							|  |  |  |               ), | 
					
						
							|  |  |  |             ], | 
					
						
							|  |  |  |           ), | 
					
						
							|  |  |  |           Row( | 
					
						
							|  |  |  |             textDirection: isLocal ? TextDirection.ltr : TextDirection.rtl, | 
					
						
							|  |  |  |             children: [ | 
					
						
							|  |  |  |               Expanded( | 
					
						
							|  |  |  |                 child: Row( | 
					
						
							|  |  |  |                   mainAxisAlignment: | 
					
						
							|  |  |  |                       isLocal ? MainAxisAlignment.start : MainAxisAlignment.end, | 
					
						
							|  |  |  |                   children: [ | 
					
						
							|  |  |  |                     MenuButton( | 
					
						
							|  |  |  |                       padding: EdgeInsets.only( | 
					
						
							|  |  |  |                         right: 3, | 
					
						
							|  |  |  |                       ), | 
					
						
							|  |  |  |                       onPressed: () { | 
					
						
							| 
									
										
										
										
											2023-03-08 22:32:55 +09:00
										 |  |  |                         controller.goToHomeDirectory(); | 
					
						
							| 
									
										
										
										
											2023-02-22 22:13:21 +01:00
										 |  |  |                       }, | 
					
						
							|  |  |  |                       child: SvgPicture.asset( | 
					
						
							|  |  |  |                         "assets/home.svg", | 
					
						
							|  |  |  |                         color: Theme.of(context).tabBarTheme.labelColor, | 
					
						
							|  |  |  |                       ), | 
					
						
							|  |  |  |                       color: Theme.of(context).cardColor, | 
					
						
							|  |  |  |                       hoverColor: Theme.of(context).hoverColor, | 
					
						
							|  |  |  |                     ), | 
					
						
							|  |  |  |                     MenuButton( | 
					
						
							| 
									
										
										
										
											2022-07-11 16:07:49 +08:00
										 |  |  |                       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) { | 
					
						
							| 
									
										
										
										
											2023-03-08 22:32:55 +09:00
										 |  |  |                               controller.createDir(PathUtil.join( | 
					
						
							|  |  |  |                                 controller.directory.value.path, | 
					
						
							|  |  |  |                                 name.value.text, | 
					
						
							|  |  |  |                                 controller.options.value.isWindows, | 
					
						
							|  |  |  |                               )); | 
					
						
							| 
									
										
										
										
											2022-09-03 18:19:50 +08:00
										 |  |  |                               close(); | 
					
						
							|  |  |  |                             } | 
					
						
							|  |  |  |                           } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                           cancel() => close(false); | 
					
						
							|  |  |  |                           return CustomAlertDialog( | 
					
						
							| 
									
										
										
										
											2023-02-27 09:44:52 +01:00
										 |  |  |                             title: Row( | 
					
						
							|  |  |  |                               mainAxisAlignment: MainAxisAlignment.center, | 
					
						
							|  |  |  |                               children: [ | 
					
						
							|  |  |  |                                 SvgPicture.asset("assets/folder_new.svg", | 
					
						
							|  |  |  |                                     color: MyTheme.accent), | 
					
						
							|  |  |  |                                 Text( | 
					
						
							|  |  |  |                                   translate("Create Folder"), | 
					
						
							|  |  |  |                                 ).paddingOnly( | 
					
						
							|  |  |  |                                   left: 10, | 
					
						
							|  |  |  |                                 ), | 
					
						
							|  |  |  |                               ], | 
					
						
							|  |  |  |                             ), | 
					
						
							| 
									
										
										
										
											2022-09-03 18:19:50 +08:00
										 |  |  |                             content: Column( | 
					
						
							|  |  |  |                               mainAxisSize: MainAxisSize.min, | 
					
						
							|  |  |  |                               children: [ | 
					
						
							| 
									
										
										
										
											2023-03-01 14:50:50 +01:00
										 |  |  |                                 TextFormField( | 
					
						
							|  |  |  |                                   decoration: InputDecoration( | 
					
						
							|  |  |  |                                     labelText: translate( | 
					
						
							|  |  |  |                                       "Please enter the folder name", | 
					
						
							| 
									
										
										
										
											2023-02-27 09:44:52 +01:00
										 |  |  |                                     ), | 
					
						
							| 
									
										
										
										
											2023-03-01 14:50:50 +01:00
										 |  |  |                                   ), | 
					
						
							|  |  |  |                                   controller: name, | 
					
						
							|  |  |  |                                   autofocus: true, | 
					
						
							| 
									
										
										
										
											2022-09-03 18:19:50 +08:00
										 |  |  |                                 ), | 
					
						
							|  |  |  |                               ], | 
					
						
							|  |  |  |                             ), | 
					
						
							| 
									
										
										
										
											2023-03-01 14:50:50 +01:00
										 |  |  |                             actions: [ | 
					
						
							|  |  |  |                               dialogButton( | 
					
						
							|  |  |  |                                 "Cancel", | 
					
						
							|  |  |  |                                 icon: Icon(Icons.close_rounded), | 
					
						
							|  |  |  |                                 onPressed: cancel, | 
					
						
							|  |  |  |                                 isOutline: true, | 
					
						
							|  |  |  |                               ), | 
					
						
							|  |  |  |                               dialogButton( | 
					
						
							|  |  |  |                                 "Ok", | 
					
						
							|  |  |  |                                 icon: Icon(Icons.done_rounded), | 
					
						
							|  |  |  |                                 onPressed: submit, | 
					
						
							|  |  |  |                               ), | 
					
						
							|  |  |  |                             ], | 
					
						
							| 
									
										
										
										
											2022-09-03 18:19:50 +08:00
										 |  |  |                             onSubmit: submit, | 
					
						
							|  |  |  |                             onCancel: cancel, | 
					
						
							|  |  |  |                           ); | 
					
						
							|  |  |  |                         }); | 
					
						
							| 
									
										
										
										
											2022-07-11 16:07:49 +08:00
										 |  |  |                       }, | 
					
						
							| 
									
										
										
										
											2023-02-22 22:13:21 +01:00
										 |  |  |                       child: SvgPicture.asset( | 
					
						
							|  |  |  |                         "assets/folder_new.svg", | 
					
						
							|  |  |  |                         color: Theme.of(context).tabBarTheme.labelColor, | 
					
						
							|  |  |  |                       ), | 
					
						
							|  |  |  |                       color: Theme.of(context).cardColor, | 
					
						
							|  |  |  |                       hoverColor: Theme.of(context).hoverColor, | 
					
						
							|  |  |  |                     ), | 
					
						
							| 
									
										
										
										
											2023-03-09 19:55:38 +09:00
										 |  |  |                     Obx(() => MenuButton( | 
					
						
							|  |  |  |                           onPressed: SelectedItems.valid(selectedItems.items) | 
					
						
							|  |  |  |                               ? () async { | 
					
						
							|  |  |  |                                   await (controller | 
					
						
							|  |  |  |                                       .removeAction(selectedItems)); | 
					
						
							|  |  |  |                                   selectedItems.clear(); | 
					
						
							|  |  |  |                                 } | 
					
						
							|  |  |  |                               : null, | 
					
						
							|  |  |  |                           child: SvgPicture.asset( | 
					
						
							|  |  |  |                             "assets/trash.svg", | 
					
						
							|  |  |  |                             color: Theme.of(context).tabBarTheme.labelColor, | 
					
						
							|  |  |  |                           ), | 
					
						
							|  |  |  |                           color: Theme.of(context).cardColor, | 
					
						
							|  |  |  |                           hoverColor: Theme.of(context).hoverColor, | 
					
						
							|  |  |  |                         )), | 
					
						
							| 
									
										
										
										
											2023-02-22 22:13:21 +01:00
										 |  |  |                     menu(isLocal: isLocal), | 
					
						
							|  |  |  |                   ], | 
					
						
							|  |  |  |                 ), | 
					
						
							| 
									
										
										
										
											2022-06-17 22:57:41 +08:00
										 |  |  |               ), | 
					
						
							| 
									
										
										
										
											2023-03-09 19:55:38 +09:00
										 |  |  |               Obx(() => ElevatedButton.icon( | 
					
						
							|  |  |  |                     style: ButtonStyle( | 
					
						
							|  |  |  |                       padding: MaterialStateProperty.all<EdgeInsetsGeometry>( | 
					
						
							|  |  |  |                           isLocal | 
					
						
							|  |  |  |                               ? EdgeInsets.only(left: 10) | 
					
						
							|  |  |  |                               : EdgeInsets.only(right: 10)), | 
					
						
							|  |  |  |                       backgroundColor: MaterialStateProperty.all( | 
					
						
							|  |  |  |                         selectedItems.items.isEmpty | 
					
						
							|  |  |  |                             ? MyTheme.accent80 | 
					
						
							|  |  |  |                             : MyTheme.accent, | 
					
						
							| 
									
										
										
										
											2023-02-22 22:13:21 +01:00
										 |  |  |                       ), | 
					
						
							| 
									
										
										
										
											2023-03-09 19:55:38 +09:00
										 |  |  |                     ), | 
					
						
							|  |  |  |                     onPressed: SelectedItems.valid(selectedItems.items) | 
					
						
							|  |  |  |                         ? () { | 
					
						
							|  |  |  |                             final otherSideData = | 
					
						
							|  |  |  |                                 controller.getOtherSideDirectoryData(); | 
					
						
							|  |  |  |                             controller.sendFiles(selectedItems, otherSideData); | 
					
						
							|  |  |  |                             selectedItems.clear(); | 
					
						
							|  |  |  |                           } | 
					
						
							|  |  |  |                         : null, | 
					
						
							|  |  |  |                     icon: isLocal | 
					
						
							|  |  |  |                         ? Text( | 
					
						
							|  |  |  |                             translate('Send'), | 
					
						
							|  |  |  |                             textAlign: TextAlign.right, | 
					
						
							|  |  |  |                             style: TextStyle( | 
					
						
							|  |  |  |                               color: selectedItems.items.isEmpty | 
					
						
							|  |  |  |                                   ? Theme.of(context).brightness == | 
					
						
							|  |  |  |                                           Brightness.light | 
					
						
							|  |  |  |                                       ? MyTheme.grayBg | 
					
						
							|  |  |  |                                       : MyTheme.darkGray | 
					
						
							|  |  |  |                                   : Colors.white, | 
					
						
							|  |  |  |                             ), | 
					
						
							|  |  |  |                           ) | 
					
						
							|  |  |  |                         : RotatedBox( | 
					
						
							|  |  |  |                             quarterTurns: 2, | 
					
						
							|  |  |  |                             child: SvgPicture.asset( | 
					
						
							|  |  |  |                               "assets/arrow.svg", | 
					
						
							|  |  |  |                               color: selectedItems.items.isEmpty | 
					
						
							|  |  |  |                                   ? Theme.of(context).brightness == | 
					
						
							|  |  |  |                                           Brightness.light | 
					
						
							|  |  |  |                                       ? MyTheme.grayBg | 
					
						
							|  |  |  |                                       : MyTheme.darkGray | 
					
						
							|  |  |  |                                   : Colors.white, | 
					
						
							|  |  |  |                               alignment: Alignment.bottomRight, | 
					
						
							|  |  |  |                             ), | 
					
						
							|  |  |  |                           ), | 
					
						
							|  |  |  |                     label: isLocal | 
					
						
							|  |  |  |                         ? SvgPicture.asset( | 
					
						
							|  |  |  |                             "assets/arrow.svg", | 
					
						
							|  |  |  |                             color: selectedItems.items.isEmpty | 
					
						
							|  |  |  |                                 ? Theme.of(context).brightness == | 
					
						
							|  |  |  |                                         Brightness.light | 
					
						
							|  |  |  |                                     ? MyTheme.grayBg | 
					
						
							|  |  |  |                                     : MyTheme.darkGray | 
					
						
							|  |  |  |                                 : Colors.white, | 
					
						
							|  |  |  |                           ) | 
					
						
							|  |  |  |                         : Text( | 
					
						
							|  |  |  |                             translate('Receive'), | 
					
						
							|  |  |  |                             style: TextStyle( | 
					
						
							|  |  |  |                               color: selectedItems.items.isEmpty | 
					
						
							|  |  |  |                                   ? Theme.of(context).brightness == | 
					
						
							|  |  |  |                                           Brightness.light | 
					
						
							|  |  |  |                                       ? MyTheme.grayBg | 
					
						
							|  |  |  |                                       : MyTheme.darkGray | 
					
						
							|  |  |  |                                   : Colors.white, | 
					
						
							|  |  |  |                             ), | 
					
						
							|  |  |  |                           ), | 
					
						
							|  |  |  |                   )), | 
					
						
							| 
									
										
										
										
											2023-02-22 22:13:21 +01:00
										 |  |  |             ], | 
					
						
							|  |  |  |           ).marginOnly(top: 8.0) | 
					
						
							|  |  |  |         ], | 
					
						
							|  |  |  |       ), | 
					
						
							|  |  |  |     ); | 
					
						
							| 
									
										
										
										
											2022-08-16 11:46:51 +08:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2022-06-17 22:57:41 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-08 00:49:14 +09:00
										 |  |  |   Widget menu({bool isLocal = false}) { | 
					
						
							|  |  |  |     var menuPos = RelativeRect.fill; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     final List<MenuEntryBase<String>> items = [ | 
					
						
							|  |  |  |       MenuEntrySwitch<String>( | 
					
						
							|  |  |  |         switchType: SwitchType.scheckbox, | 
					
						
							|  |  |  |         text: translate("Show Hidden Files"), | 
					
						
							|  |  |  |         getter: () async { | 
					
						
							| 
									
										
										
										
											2023-03-29 23:22:31 +02:00
										 |  |  |           return controller.options.value.showHidden; | 
					
						
							| 
									
										
										
										
											2023-03-08 00:49:14 +09:00
										 |  |  |         }, | 
					
						
							|  |  |  |         setter: (bool v) async { | 
					
						
							| 
									
										
										
										
											2023-03-08 22:32:55 +09:00
										 |  |  |           controller.toggleShowHidden(); | 
					
						
							| 
									
										
										
										
											2023-03-08 00:49:14 +09:00
										 |  |  |         }, | 
					
						
							|  |  |  |         padding: kDesktopMenuPadding, | 
					
						
							|  |  |  |         dismissOnClicked: true, | 
					
						
							|  |  |  |       ), | 
					
						
							|  |  |  |       MenuEntryButton( | 
					
						
							|  |  |  |           childBuilder: (style) => Text(translate("Select All"), style: style), | 
					
						
							|  |  |  |           proc: () => setState(() => | 
					
						
							| 
									
										
										
										
											2023-03-09 18:05:09 +09:00
										 |  |  |               selectedItems.selectAll(controller.directory.value.entries)), | 
					
						
							| 
									
										
										
										
											2023-03-08 00:49:14 +09:00
										 |  |  |           padding: kDesktopMenuPadding, | 
					
						
							|  |  |  |           dismissOnClicked: true), | 
					
						
							|  |  |  |       MenuEntryButton( | 
					
						
							|  |  |  |           childBuilder: (style) => | 
					
						
							|  |  |  |               Text(translate("Unselect All"), style: style), | 
					
						
							| 
									
										
										
										
											2023-03-09 19:55:38 +09:00
										 |  |  |           proc: () => selectedItems.clear(), | 
					
						
							| 
									
										
										
										
											2023-03-08 00:49:14 +09:00
										 |  |  |           padding: kDesktopMenuPadding, | 
					
						
							|  |  |  |           dismissOnClicked: true) | 
					
						
							|  |  |  |     ]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return Listener( | 
					
						
							|  |  |  |       onPointerDown: (e) { | 
					
						
							|  |  |  |         final x = e.position.dx; | 
					
						
							|  |  |  |         final y = e.position.dy; | 
					
						
							|  |  |  |         menuPos = RelativeRect.fromLTRB(x, y, x, y); | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |       child: MenuButton( | 
					
						
							|  |  |  |         onPressed: () => mod_menu.showMenu( | 
					
						
							|  |  |  |           context: context, | 
					
						
							|  |  |  |           position: menuPos, | 
					
						
							|  |  |  |           items: items | 
					
						
							|  |  |  |               .map( | 
					
						
							|  |  |  |                 (e) => e.build( | 
					
						
							|  |  |  |                   context, | 
					
						
							|  |  |  |                   MenuConfig( | 
					
						
							|  |  |  |                       commonColor: CustomPopupMenuTheme.commonColor, | 
					
						
							|  |  |  |                       height: CustomPopupMenuTheme.height, | 
					
						
							|  |  |  |                       dividerHeight: CustomPopupMenuTheme.dividerHeight), | 
					
						
							|  |  |  |                 ), | 
					
						
							|  |  |  |               ) | 
					
						
							|  |  |  |               .expand((i) => i) | 
					
						
							|  |  |  |               .toList(), | 
					
						
							|  |  |  |           elevation: 8, | 
					
						
							|  |  |  |         ), | 
					
						
							|  |  |  |         child: SvgPicture.asset( | 
					
						
							|  |  |  |           "assets/dots.svg", | 
					
						
							|  |  |  |           color: Theme.of(context).tabBarTheme.labelColor, | 
					
						
							|  |  |  |         ), | 
					
						
							|  |  |  |         color: Theme.of(context).cardColor, | 
					
						
							|  |  |  |         hoverColor: Theme.of(context).hoverColor, | 
					
						
							|  |  |  |       ), | 
					
						
							|  |  |  |     ); | 
					
						
							| 
									
										
										
										
											2022-10-19 10:52:29 +09:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-08 00:49:14 +09:00
										 |  |  |   Widget _buildFileList( | 
					
						
							| 
									
										
										
										
											2023-03-09 00:06:24 +09:00
										 |  |  |       BuildContext context, ScrollController scrollController) { | 
					
						
							| 
									
										
										
										
											2023-03-08 22:32:55 +09:00
										 |  |  |     final fd = controller.directory.value; | 
					
						
							| 
									
										
										
										
											2023-03-08 00:49:14 +09:00
										 |  |  |     final entries = fd.entries; | 
					
						
							| 
									
										
										
										
											2022-07-11 16:07:49 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-08 00:49:14 +09:00
										 |  |  |     return ListSearchActionListener( | 
					
						
							|  |  |  |       node: _keyboardNode, | 
					
						
							|  |  |  |       buffer: _listSearchBuffer, | 
					
						
							|  |  |  |       onNext: (buffer) { | 
					
						
							|  |  |  |         debugPrint("searching next for $buffer"); | 
					
						
							|  |  |  |         assert(buffer.length == 1); | 
					
						
							| 
									
										
										
										
											2023-03-09 19:55:38 +09:00
										 |  |  |         assert(selectedItems.items.length <= 1); | 
					
						
							| 
									
										
										
										
											2023-03-08 00:49:14 +09:00
										 |  |  |         var skipCount = 0; | 
					
						
							| 
									
										
										
										
											2023-03-09 18:05:09 +09:00
										 |  |  |         if (selectedItems.items.isNotEmpty) { | 
					
						
							|  |  |  |           final index = entries.indexOf(selectedItems.items.first); | 
					
						
							| 
									
										
										
										
											2023-03-08 00:49:14 +09:00
										 |  |  |           if (index < 0) { | 
					
						
							|  |  |  |             return; | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |           skipCount = index + 1; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         var searchResult = entries | 
					
						
							|  |  |  |             .skip(skipCount) | 
					
						
							|  |  |  |             .where((element) => element.name.toLowerCase().startsWith(buffer)); | 
					
						
							|  |  |  |         if (searchResult.isEmpty) { | 
					
						
							|  |  |  |           // cannot find next, lets restart search from head
 | 
					
						
							|  |  |  |           debugPrint("restart search from head"); | 
					
						
							|  |  |  |           searchResult = entries.where( | 
					
						
							|  |  |  |               (element) => element.name.toLowerCase().startsWith(buffer)); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         if (searchResult.isEmpty) { | 
					
						
							| 
									
										
										
										
											2023-03-09 19:55:38 +09:00
										 |  |  |           selectedItems.clear(); | 
					
						
							| 
									
										
										
										
											2023-03-08 00:49:14 +09:00
										 |  |  |           return; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         _jumpToEntry(isLocal, searchResult.first, scrollController, | 
					
						
							|  |  |  |             kDesktopFileTransferRowHeight); | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |       onSearch: (buffer) { | 
					
						
							|  |  |  |         debugPrint("searching for $buffer"); | 
					
						
							| 
									
										
										
										
											2023-03-09 18:05:09 +09:00
										 |  |  |         final selectedEntries = selectedItems; | 
					
						
							| 
									
										
										
										
											2023-03-08 00:49:14 +09:00
										 |  |  |         final searchResult = entries | 
					
						
							|  |  |  |             .where((element) => element.name.toLowerCase().startsWith(buffer)); | 
					
						
							|  |  |  |         selectedEntries.clear(); | 
					
						
							|  |  |  |         if (searchResult.isEmpty) { | 
					
						
							| 
									
										
										
										
											2023-03-09 19:55:38 +09:00
										 |  |  |           selectedItems.clear(); | 
					
						
							| 
									
										
										
										
											2023-03-08 00:49:14 +09:00
										 |  |  |           return; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         _jumpToEntry(isLocal, searchResult.first, scrollController, | 
					
						
							|  |  |  |             kDesktopFileTransferRowHeight); | 
					
						
							|  |  |  |       }, | 
					
						
							| 
									
										
										
										
											2023-03-08 22:32:55 +09:00
										 |  |  |       child: Obx(() { | 
					
						
							|  |  |  |         final entries = controller.directory.value.entries; | 
					
						
							|  |  |  |         final filteredEntries = _searchText.isNotEmpty | 
					
						
							|  |  |  |             ? entries.where((element) { | 
					
						
							|  |  |  |                 return element.name.contains(_searchText.value); | 
					
						
							|  |  |  |               }).toList(growable: false) | 
					
						
							|  |  |  |             : entries; | 
					
						
							|  |  |  |         final rows = filteredEntries.map((entry) { | 
					
						
							|  |  |  |           final sizeStr = | 
					
						
							|  |  |  |               entry.isFile ? readableFileSize(entry.size.toDouble()) : ""; | 
					
						
							|  |  |  |           final lastModifiedStr = entry.isDrive | 
					
						
							|  |  |  |               ? " " | 
					
						
							|  |  |  |               : "${entry.lastModified().toString().replaceAll(".000", "")}   "; | 
					
						
							|  |  |  |           return Padding( | 
					
						
							|  |  |  |             padding: EdgeInsets.symmetric(vertical: 1), | 
					
						
							| 
									
										
										
										
											2023-03-09 19:55:38 +09:00
										 |  |  |             child: Obx(() => Container( | 
					
						
							| 
									
										
										
										
											2023-03-08 22:32:55 +09:00
										 |  |  |                 decoration: BoxDecoration( | 
					
						
							| 
									
										
										
										
											2023-03-09 19:55:38 +09:00
										 |  |  |                   color: selectedItems.items.contains(entry) | 
					
						
							| 
									
										
										
										
											2023-03-08 22:32:55 +09:00
										 |  |  |                       ? Theme.of(context).hoverColor | 
					
						
							|  |  |  |                       : Theme.of(context).cardColor, | 
					
						
							|  |  |  |                   borderRadius: BorderRadius.all( | 
					
						
							|  |  |  |                     Radius.circular(5.0), | 
					
						
							| 
									
										
										
										
											2023-03-08 00:49:14 +09:00
										 |  |  |                   ), | 
					
						
							| 
									
										
										
										
											2023-03-08 22:32:55 +09:00
										 |  |  |                 ), | 
					
						
							|  |  |  |                 key: ValueKey(entry.name), | 
					
						
							|  |  |  |                 height: kDesktopFileTransferRowHeight, | 
					
						
							|  |  |  |                 child: Column( | 
					
						
							|  |  |  |                   mainAxisAlignment: MainAxisAlignment.spaceAround, | 
					
						
							|  |  |  |                   children: [ | 
					
						
							|  |  |  |                     Expanded( | 
					
						
							|  |  |  |                       child: InkWell( | 
					
						
							|  |  |  |                         child: Row( | 
					
						
							|  |  |  |                           children: [ | 
					
						
							|  |  |  |                             GestureDetector( | 
					
						
							|  |  |  |                               child: Obx( | 
					
						
							|  |  |  |                                 () => Container( | 
					
						
							|  |  |  |                                     width: _nameColWidth.value, | 
					
						
							| 
									
										
										
										
											2023-03-08 00:49:14 +09:00
										 |  |  |                                     child: Tooltip( | 
					
						
							| 
									
										
										
										
											2023-03-08 22:32:55 +09:00
										 |  |  |                                       waitDuration: Duration(milliseconds: 500), | 
					
						
							|  |  |  |                                       message: entry.name, | 
					
						
							|  |  |  |                                       child: Row(children: [ | 
					
						
							|  |  |  |                                         entry.isDrive | 
					
						
							|  |  |  |                                             ? Image( | 
					
						
							|  |  |  |                                                     image: iconHardDrive, | 
					
						
							|  |  |  |                                                     fit: BoxFit.scaleDown, | 
					
						
							|  |  |  |                                                     color: Theme.of(context) | 
					
						
							|  |  |  |                                                         .iconTheme | 
					
						
							|  |  |  |                                                         .color | 
					
						
							|  |  |  |                                                         ?.withOpacity(0.7)) | 
					
						
							|  |  |  |                                                 .paddingAll(4) | 
					
						
							|  |  |  |                                             : SvgPicture.asset( | 
					
						
							|  |  |  |                                                 entry.isFile | 
					
						
							|  |  |  |                                                     ? "assets/file.svg" | 
					
						
							|  |  |  |                                                     : "assets/folder.svg", | 
					
						
							|  |  |  |                                                 color: Theme.of(context) | 
					
						
							|  |  |  |                                                     .tabBarTheme | 
					
						
							|  |  |  |                                                     .labelColor, | 
					
						
							|  |  |  |                                               ), | 
					
						
							|  |  |  |                                         Expanded( | 
					
						
							|  |  |  |                                             child: Text(entry.name.nonBreaking, | 
					
						
							|  |  |  |                                                 overflow: | 
					
						
							|  |  |  |                                                     TextOverflow.ellipsis)) | 
					
						
							|  |  |  |                                       ]), | 
					
						
							|  |  |  |                                     )), | 
					
						
							| 
									
										
										
										
											2023-03-08 00:49:14 +09:00
										 |  |  |                               ), | 
					
						
							| 
									
										
										
										
											2023-03-08 22:32:55 +09:00
										 |  |  |                               onTap: () { | 
					
						
							| 
									
										
										
										
											2023-03-09 18:05:09 +09:00
										 |  |  |                                 final items = selectedItems; | 
					
						
							| 
									
										
										
										
											2023-03-08 22:32:55 +09:00
										 |  |  |                                 // handle double click
 | 
					
						
							|  |  |  |                                 if (_checkDoubleClick(entry)) { | 
					
						
							|  |  |  |                                   controller.openDirectory(entry.path); | 
					
						
							|  |  |  |                                   items.clear(); | 
					
						
							|  |  |  |                                   return; | 
					
						
							|  |  |  |                                 } | 
					
						
							|  |  |  |                                 _onSelectedChanged( | 
					
						
							|  |  |  |                                     items, filteredEntries, entry, isLocal); | 
					
						
							|  |  |  |                               }, | 
					
						
							|  |  |  |                             ), | 
					
						
							|  |  |  |                             SizedBox( | 
					
						
							|  |  |  |                               width: 2.0, | 
					
						
							|  |  |  |                             ), | 
					
						
							|  |  |  |                             GestureDetector( | 
					
						
							|  |  |  |                               child: Obx( | 
					
						
							|  |  |  |                                 () => SizedBox( | 
					
						
							|  |  |  |                                   width: _modifiedColWidth.value, | 
					
						
							| 
									
										
										
										
											2023-03-08 00:49:14 +09:00
										 |  |  |                                   child: Tooltip( | 
					
						
							| 
									
										
										
										
											2023-03-08 22:32:55 +09:00
										 |  |  |                                       waitDuration: Duration(milliseconds: 500), | 
					
						
							|  |  |  |                                       message: lastModifiedStr, | 
					
						
							|  |  |  |                                       child: Text( | 
					
						
							|  |  |  |                                         lastModifiedStr, | 
					
						
							|  |  |  |                                         overflow: TextOverflow.ellipsis, | 
					
						
							|  |  |  |                                         style: TextStyle( | 
					
						
							|  |  |  |                                           fontSize: 12, | 
					
						
							|  |  |  |                                           color: MyTheme.darkGray, | 
					
						
							|  |  |  |                                         ), | 
					
						
							|  |  |  |                                       )), | 
					
						
							|  |  |  |                                 ), | 
					
						
							|  |  |  |                               ), | 
					
						
							|  |  |  |                             ), | 
					
						
							|  |  |  |                             // Divider from header.
 | 
					
						
							|  |  |  |                             SizedBox( | 
					
						
							|  |  |  |                               width: 2.0, | 
					
						
							|  |  |  |                             ), | 
					
						
							|  |  |  |                             Expanded( | 
					
						
							|  |  |  |                               // width: 100,
 | 
					
						
							|  |  |  |                               child: GestureDetector( | 
					
						
							|  |  |  |                                 child: Tooltip( | 
					
						
							|  |  |  |                                   waitDuration: Duration(milliseconds: 500), | 
					
						
							|  |  |  |                                   message: sizeStr, | 
					
						
							|  |  |  |                                   child: Text( | 
					
						
							|  |  |  |                                     sizeStr, | 
					
						
							|  |  |  |                                     overflow: TextOverflow.ellipsis, | 
					
						
							|  |  |  |                                     style: TextStyle( | 
					
						
							|  |  |  |                                         fontSize: 10, color: MyTheme.darkGray), | 
					
						
							| 
									
										
										
										
											2023-03-08 00:49:14 +09:00
										 |  |  |                                   ), | 
					
						
							|  |  |  |                                 ), | 
					
						
							|  |  |  |                               ), | 
					
						
							| 
									
										
										
										
											2023-03-08 22:32:55 +09:00
										 |  |  |                             ), | 
					
						
							|  |  |  |                           ], | 
					
						
							| 
									
										
										
										
											2023-03-08 00:49:14 +09:00
										 |  |  |                         ), | 
					
						
							|  |  |  |                       ), | 
					
						
							| 
									
										
										
										
											2023-03-08 22:32:55 +09:00
										 |  |  |                     ), | 
					
						
							|  |  |  |                   ], | 
					
						
							| 
									
										
										
										
											2023-03-09 19:55:38 +09:00
										 |  |  |                 ))), | 
					
						
							| 
									
										
										
										
											2023-03-08 22:32:55 +09:00
										 |  |  |           ); | 
					
						
							|  |  |  |         }).toList(growable: false); | 
					
						
							| 
									
										
										
										
											2023-03-08 00:49:14 +09:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-08 22:32:55 +09:00
										 |  |  |         return Column( | 
					
						
							|  |  |  |           children: [ | 
					
						
							|  |  |  |             // Header
 | 
					
						
							|  |  |  |             Row( | 
					
						
							|  |  |  |               children: [ | 
					
						
							| 
									
										
										
										
											2023-03-09 00:06:24 +09:00
										 |  |  |                 Expanded(child: _buildFileBrowserHeader(context)), | 
					
						
							| 
									
										
										
										
											2023-03-08 22:32:55 +09:00
										 |  |  |               ], | 
					
						
							|  |  |  |             ), | 
					
						
							|  |  |  |             // Body
 | 
					
						
							|  |  |  |             Expanded( | 
					
						
							|  |  |  |               child: ListView.builder( | 
					
						
							|  |  |  |                 controller: scrollController, | 
					
						
							|  |  |  |                 itemExtent: kDesktopFileTransferRowHeight, | 
					
						
							|  |  |  |                 itemBuilder: (context, index) { | 
					
						
							|  |  |  |                   return rows[index]; | 
					
						
							|  |  |  |                 }, | 
					
						
							|  |  |  |                 itemCount: rows.length, | 
					
						
							| 
									
										
										
										
											2023-03-08 00:49:14 +09:00
										 |  |  |               ), | 
					
						
							| 
									
										
										
										
											2023-03-08 22:32:55 +09:00
										 |  |  |             ), | 
					
						
							|  |  |  |           ], | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  |       }), | 
					
						
							| 
									
										
										
										
											2023-03-08 00:49:14 +09:00
										 |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   onSearchText(String searchText, bool isLocal) { | 
					
						
							| 
									
										
										
										
											2023-03-09 18:05:09 +09:00
										 |  |  |     selectedItems.clear(); | 
					
						
							| 
									
										
										
										
											2023-03-08 00:49:14 +09:00
										 |  |  |     _searchText.value = searchText; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   void _jumpToEntry(bool isLocal, Entry entry, | 
					
						
							|  |  |  |       ScrollController scrollController, double rowHeight) { | 
					
						
							| 
									
										
										
										
											2023-03-08 22:32:55 +09:00
										 |  |  |     final entries = controller.directory.value.entries; | 
					
						
							| 
									
										
										
										
											2023-03-08 00:49:14 +09:00
										 |  |  |     final index = entries.indexOf(entry); | 
					
						
							|  |  |  |     if (index == -1) { | 
					
						
							|  |  |  |       debugPrint("entry is not valid: ${entry.path}"); | 
					
						
							| 
									
										
										
										
											2022-08-16 11:46:51 +08:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2023-03-09 18:05:09 +09:00
										 |  |  |     final selectedEntries = selectedItems; | 
					
						
							| 
									
										
										
										
											2023-03-08 00:49:14 +09:00
										 |  |  |     final searchResult = entries.where((element) => element == entry); | 
					
						
							|  |  |  |     selectedEntries.clear(); | 
					
						
							|  |  |  |     if (searchResult.isEmpty) { | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     final offset = min( | 
					
						
							|  |  |  |         max(scrollController.position.minScrollExtent, | 
					
						
							|  |  |  |             entries.indexOf(searchResult.first) * rowHeight), | 
					
						
							|  |  |  |         scrollController.position.maxScrollExtent); | 
					
						
							|  |  |  |     scrollController.jumpTo(offset); | 
					
						
							| 
									
										
										
										
											2023-03-09 19:55:38 +09:00
										 |  |  |     selectedEntries.add(searchResult.first); | 
					
						
							|  |  |  |     debugPrint("focused on ${searchResult.first.name}"); | 
					
						
							| 
									
										
										
										
											2022-08-16 11:46:51 +08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-08 00:49:14 +09:00
										 |  |  |   void _onSelectedChanged(SelectedItems selectedItems, List<Entry> entries, | 
					
						
							|  |  |  |       Entry entry, bool isLocal) { | 
					
						
							|  |  |  |     final isCtrlDown = RawKeyboard.instance.keysPressed | 
					
						
							|  |  |  |         .contains(LogicalKeyboardKey.controlLeft); | 
					
						
							|  |  |  |     final isShiftDown = | 
					
						
							|  |  |  |         RawKeyboard.instance.keysPressed.contains(LogicalKeyboardKey.shiftLeft); | 
					
						
							|  |  |  |     if (isCtrlDown) { | 
					
						
							| 
									
										
										
										
											2023-03-09 19:55:38 +09:00
										 |  |  |       if (selectedItems.items.contains(entry)) { | 
					
						
							| 
									
										
										
										
											2023-03-08 00:49:14 +09:00
										 |  |  |         selectedItems.remove(entry); | 
					
						
							|  |  |  |       } else { | 
					
						
							| 
									
										
										
										
											2023-03-08 22:32:55 +09:00
										 |  |  |         selectedItems.add(entry); | 
					
						
							| 
									
										
										
										
											2023-03-08 00:49:14 +09:00
										 |  |  |       } | 
					
						
							|  |  |  |     } else if (isShiftDown) { | 
					
						
							|  |  |  |       final List<int> indexGroup = []; | 
					
						
							|  |  |  |       for (var selected in selectedItems.items) { | 
					
						
							|  |  |  |         indexGroup.add(entries.indexOf(selected)); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       indexGroup.add(entries.indexOf(entry)); | 
					
						
							|  |  |  |       indexGroup.removeWhere((e) => e == -1); | 
					
						
							|  |  |  |       final maxIndex = indexGroup.reduce(max); | 
					
						
							|  |  |  |       final minIndex = indexGroup.reduce(min); | 
					
						
							|  |  |  |       selectedItems.clear(); | 
					
						
							|  |  |  |       entries | 
					
						
							|  |  |  |           .getRange(minIndex, maxIndex + 1) | 
					
						
							| 
									
										
										
										
											2023-03-08 22:32:55 +09:00
										 |  |  |           .forEach((e) => selectedItems.add(e)); | 
					
						
							| 
									
										
										
										
											2022-08-16 11:46:51 +08:00
										 |  |  |     } else { | 
					
						
							| 
									
										
										
										
											2023-03-08 00:49:14 +09:00
										 |  |  |       selectedItems.clear(); | 
					
						
							| 
									
										
										
										
											2023-03-08 22:32:55 +09:00
										 |  |  |       selectedItems.add(entry); | 
					
						
							| 
									
										
										
										
											2023-03-08 00:49:14 +09:00
										 |  |  |     } | 
					
						
							|  |  |  |     setState(() {}); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   bool _checkDoubleClick(Entry entry) { | 
					
						
							|  |  |  |     final current = DateTime.now().millisecondsSinceEpoch; | 
					
						
							|  |  |  |     final elapsed = current - _lastClickTime; | 
					
						
							|  |  |  |     _lastClickTime = current; | 
					
						
							|  |  |  |     if (_lastClickEntry == entry) { | 
					
						
							|  |  |  |       if (elapsed < bind.getDoubleClickTime()) { | 
					
						
							|  |  |  |         return true; | 
					
						
							| 
									
										
										
										
											2022-10-13 10:17:20 +09:00
										 |  |  |       } | 
					
						
							| 
									
										
										
										
											2023-03-08 00:49:14 +09:00
										 |  |  |     } else { | 
					
						
							|  |  |  |       _lastClickEntry = entry; | 
					
						
							| 
									
										
										
										
											2022-08-16 11:46:51 +08:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2023-03-08 00:49:14 +09:00
										 |  |  |     return false; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-09 00:06:24 +09:00
										 |  |  |   Widget _buildFileBrowserHeader(BuildContext context) { | 
					
						
							| 
									
										
										
										
											2023-03-08 00:49:14 +09:00
										 |  |  |     final padding = EdgeInsets.all(1.0); | 
					
						
							|  |  |  |     return SizedBox( | 
					
						
							|  |  |  |       height: kDesktopFileTransferHeaderHeight, | 
					
						
							|  |  |  |       child: Row( | 
					
						
							|  |  |  |         children: [ | 
					
						
							|  |  |  |           Obx( | 
					
						
							|  |  |  |             () => headerItemFunc( | 
					
						
							| 
									
										
										
										
											2023-03-09 00:06:24 +09:00
										 |  |  |                 _nameColWidth.value, SortBy.name, translate("Name")), | 
					
						
							| 
									
										
										
										
											2023-03-08 00:49:14 +09:00
										 |  |  |           ), | 
					
						
							|  |  |  |           DraggableDivider( | 
					
						
							|  |  |  |             axis: Axis.vertical, | 
					
						
							|  |  |  |             onPointerMove: (dx) { | 
					
						
							|  |  |  |               _nameColWidth.value += dx; | 
					
						
							|  |  |  |               _nameColWidth.value = min(kDesktopFileTransferMaximumWidth, | 
					
						
							|  |  |  |                   max(kDesktopFileTransferMinimumWidth, _nameColWidth.value)); | 
					
						
							|  |  |  |             }, | 
					
						
							|  |  |  |             padding: padding, | 
					
						
							|  |  |  |           ), | 
					
						
							|  |  |  |           Obx( | 
					
						
							|  |  |  |             () => headerItemFunc(_modifiedColWidth.value, SortBy.modified, | 
					
						
							| 
									
										
										
										
											2023-03-09 00:06:24 +09:00
										 |  |  |                 translate("Modified")), | 
					
						
							| 
									
										
										
										
											2023-03-08 00:49:14 +09:00
										 |  |  |           ), | 
					
						
							|  |  |  |           DraggableDivider( | 
					
						
							|  |  |  |               axis: Axis.vertical, | 
					
						
							|  |  |  |               onPointerMove: (dx) { | 
					
						
							|  |  |  |                 _modifiedColWidth.value += dx; | 
					
						
							|  |  |  |                 _modifiedColWidth.value = min( | 
					
						
							|  |  |  |                     kDesktopFileTransferMaximumWidth, | 
					
						
							|  |  |  |                     max(kDesktopFileTransferMinimumWidth, | 
					
						
							|  |  |  |                         _modifiedColWidth.value)); | 
					
						
							|  |  |  |               }, | 
					
						
							|  |  |  |               padding: padding), | 
					
						
							| 
									
										
										
										
											2023-03-09 00:06:24 +09:00
										 |  |  |           Expanded(child: headerItemFunc(null, SortBy.size, translate("Size"))) | 
					
						
							| 
									
										
										
										
											2023-03-08 00:49:14 +09:00
										 |  |  |         ], | 
					
						
							|  |  |  |       ), | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-09 00:06:24 +09:00
										 |  |  |   Widget headerItemFunc(double? width, SortBy sortBy, String name) { | 
					
						
							| 
									
										
										
										
											2023-03-08 00:49:14 +09:00
										 |  |  |     final headerTextStyle = | 
					
						
							|  |  |  |         Theme.of(context).dataTableTheme.headingTextStyle ?? TextStyle(); | 
					
						
							|  |  |  |     return ObxValue<Rx<bool?>>( | 
					
						
							|  |  |  |         (ascending) => InkWell( | 
					
						
							|  |  |  |               onTap: () { | 
					
						
							|  |  |  |                 if (ascending.value == null) { | 
					
						
							|  |  |  |                   ascending.value = true; | 
					
						
							|  |  |  |                 } else { | 
					
						
							|  |  |  |                   ascending.value = !ascending.value!; | 
					
						
							|  |  |  |                 } | 
					
						
							| 
									
										
										
										
											2023-03-08 22:32:55 +09:00
										 |  |  |                 controller.changeSortStyle(sortBy, | 
					
						
							| 
									
										
										
										
											2023-03-08 00:49:14 +09:00
										 |  |  |                     isLocal: isLocal, ascending: ascending.value!); | 
					
						
							|  |  |  |               }, | 
					
						
							|  |  |  |               child: SizedBox( | 
					
						
							|  |  |  |                 width: width, | 
					
						
							|  |  |  |                 height: kDesktopFileTransferHeaderHeight, | 
					
						
							|  |  |  |                 child: Row( | 
					
						
							|  |  |  |                   children: [ | 
					
						
							|  |  |  |                     Flexible( | 
					
						
							|  |  |  |                       flex: 2, | 
					
						
							|  |  |  |                       child: Text( | 
					
						
							|  |  |  |                         name, | 
					
						
							|  |  |  |                         style: headerTextStyle, | 
					
						
							|  |  |  |                         overflow: TextOverflow.ellipsis, | 
					
						
							|  |  |  |                       ).marginSymmetric(horizontal: 4), | 
					
						
							|  |  |  |                     ), | 
					
						
							|  |  |  |                     Flexible( | 
					
						
							|  |  |  |                         flex: 1, | 
					
						
							|  |  |  |                         child: ascending.value != null | 
					
						
							|  |  |  |                             ? Icon( | 
					
						
							|  |  |  |                                 ascending.value! | 
					
						
							|  |  |  |                                     ? Icons.keyboard_arrow_up_rounded | 
					
						
							|  |  |  |                                     : Icons.keyboard_arrow_down_rounded, | 
					
						
							|  |  |  |                               ) | 
					
						
							|  |  |  |                             : const Offstage()) | 
					
						
							|  |  |  |                   ], | 
					
						
							|  |  |  |                 ), | 
					
						
							|  |  |  |               ), | 
					
						
							|  |  |  |             ), () { | 
					
						
							| 
									
										
										
										
											2023-03-08 22:32:55 +09:00
										 |  |  |       if (controller.sortBy.value == sortBy) { | 
					
						
							| 
									
										
										
										
											2023-03-09 00:06:24 +09:00
										 |  |  |         return controller.sortAscending.obs; | 
					
						
							| 
									
										
										
										
											2023-03-08 00:49:14 +09:00
										 |  |  |       } else { | 
					
						
							|  |  |  |         return Rx<bool?>(null); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     }()); | 
					
						
							| 
									
										
										
										
											2022-08-16 11:46:51 +08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-08 22:32:55 +09:00
										 |  |  |   Widget buildBread() { | 
					
						
							| 
									
										
										
										
											2022-08-16 12:50:08 +08:00
										 |  |  |     final items = getPathBreadCrumbItems(isLocal, (list) { | 
					
						
							|  |  |  |       var path = ""; | 
					
						
							|  |  |  |       for (var item in list) { | 
					
						
							| 
									
										
										
										
											2023-03-08 22:32:55 +09:00
										 |  |  |         path = PathUtil.join(path, item, controller.options.value.isWindows); | 
					
						
							| 
									
										
										
										
											2022-08-16 12:50:08 +08:00
										 |  |  |       } | 
					
						
							| 
									
										
										
										
											2023-03-08 22:32:55 +09:00
										 |  |  |       controller.openDirectory(path); | 
					
						
							| 
									
										
										
										
											2022-08-16 12:50:08 +08:00
										 |  |  |     }); | 
					
						
							| 
									
										
										
										
											2022-10-18 23:56:36 +09:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-16 12:50:08 +08:00
										 |  |  |     return items.isEmpty | 
					
						
							|  |  |  |         ? Offstage() | 
					
						
							| 
									
										
										
										
											2022-10-18 23:56:36 +09:00
										 |  |  |         : Row( | 
					
						
							| 
									
										
										
										
											2023-03-08 00:49:14 +09:00
										 |  |  |             key: _locationBarKey, | 
					
						
							| 
									
										
										
										
											2022-10-18 23:56:36 +09:00
										 |  |  |             mainAxisAlignment: MainAxisAlignment.spaceBetween, | 
					
						
							|  |  |  |             children: [ | 
					
						
							|  |  |  |                 Expanded( | 
					
						
							| 
									
										
										
										
											2023-02-22 22:13:21 +01:00
										 |  |  |                   child: Listener( | 
					
						
							|  |  |  |                     // handle mouse wheel
 | 
					
						
							|  |  |  |                     onPointerSignal: (e) { | 
					
						
							|  |  |  |                       if (e is PointerScrollEvent) { | 
					
						
							| 
									
										
										
										
											2023-03-08 00:49:14 +09:00
										 |  |  |                         final sc = _breadCrumbScroller; | 
					
						
							| 
									
										
										
										
											2023-02-22 22:13:21 +01:00
										 |  |  |                         final scale = Platform.isWindows ? 2 : 4; | 
					
						
							|  |  |  |                         sc.jumpTo(sc.offset + e.scrollDelta.dy / scale); | 
					
						
							|  |  |  |                       } | 
					
						
							|  |  |  |                     }, | 
					
						
							|  |  |  |                     child: BreadCrumb( | 
					
						
							|  |  |  |                       items: items, | 
					
						
							|  |  |  |                       divider: const Icon(Icons.keyboard_arrow_right_rounded), | 
					
						
							|  |  |  |                       overflow: ScrollableOverflow( | 
					
						
							| 
									
										
										
										
											2023-03-08 00:49:14 +09:00
										 |  |  |                         controller: _breadCrumbScroller, | 
					
						
							| 
									
										
										
										
											2023-02-22 22:13:21 +01:00
										 |  |  |                       ), | 
					
						
							|  |  |  |                     ), | 
					
						
							|  |  |  |                   ), | 
					
						
							|  |  |  |                 ), | 
					
						
							| 
									
										
										
										
											2022-10-18 23:56:36 +09:00
										 |  |  |                 ActionIcon( | 
					
						
							|  |  |  |                   message: "", | 
					
						
							| 
									
										
										
										
											2023-02-22 22:13:21 +01:00
										 |  |  |                   icon: Icons.keyboard_arrow_down_rounded, | 
					
						
							| 
									
										
										
										
											2022-10-18 23:56:36 +09:00
										 |  |  |                   onTap: () async { | 
					
						
							| 
									
										
										
										
											2023-03-08 00:49:14 +09:00
										 |  |  |                     final renderBox = _locationBarKey.currentContext | 
					
						
							| 
									
										
										
										
											2022-10-18 23:56:36 +09:00
										 |  |  |                         ?.findRenderObject() as RenderBox; | 
					
						
							| 
									
										
										
										
											2023-03-08 00:49:14 +09:00
										 |  |  |                     _locationBarKey.currentContext?.size; | 
					
						
							| 
									
										
										
										
											2022-10-18 23:56:36 +09:00
										 |  |  | 
 | 
					
						
							|  |  |  |                     final size = renderBox.size; | 
					
						
							|  |  |  |                     final offset = renderBox.localToGlobal(Offset.zero); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                     final x = offset.dx; | 
					
						
							|  |  |  |                     final y = offset.dy + size.height + 1; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-08 22:32:55 +09:00
										 |  |  |                     final isPeerWindows = controller.options.value.isWindows; | 
					
						
							| 
									
										
										
										
											2022-10-19 11:24:44 +09:00
										 |  |  |                     final List<MenuEntryBase> menuItems = [ | 
					
						
							|  |  |  |                       MenuEntryButton( | 
					
						
							| 
									
										
										
										
											2022-11-02 23:48:02 +09:00
										 |  |  |                           childBuilder: (TextStyle? style) => isPeerWindows | 
					
						
							| 
									
										
										
										
											2023-03-08 00:49:14 +09:00
										 |  |  |                               ? buildWindowsThisPC(context, style) | 
					
						
							| 
									
										
										
										
											2022-11-02 23:48:02 +09:00
										 |  |  |                               : Text( | 
					
						
							|  |  |  |                                   '/', | 
					
						
							|  |  |  |                                   style: style, | 
					
						
							|  |  |  |                                 ), | 
					
						
							| 
									
										
										
										
											2022-10-19 11:24:44 +09:00
										 |  |  |                           proc: () { | 
					
						
							| 
									
										
										
										
											2023-03-08 22:32:55 +09:00
										 |  |  |                             controller.openDirectory('/'); | 
					
						
							| 
									
										
										
										
											2022-10-19 11:24:44 +09:00
										 |  |  |                           }, | 
					
						
							|  |  |  |                           dismissOnClicked: true), | 
					
						
							|  |  |  |                       MenuEntryDivider() | 
					
						
							|  |  |  |                     ]; | 
					
						
							| 
									
										
										
										
											2022-11-02 23:48:02 +09:00
										 |  |  |                     if (isPeerWindows) { | 
					
						
							| 
									
										
										
										
											2022-10-19 10:52:29 +09:00
										 |  |  |                       var loadingTag = ""; | 
					
						
							|  |  |  |                       if (!isLocal) { | 
					
						
							|  |  |  |                         loadingTag = _ffi.dialogManager.showLoading("Waiting"); | 
					
						
							|  |  |  |                       } | 
					
						
							| 
									
										
										
										
											2022-10-18 23:56:36 +09:00
										 |  |  |                       try { | 
					
						
							| 
									
										
										
										
											2023-03-08 22:32:55 +09:00
										 |  |  |                         final showHidden = controller.options.value.showHidden; | 
					
						
							|  |  |  |                         final fd = await controller.fileFetcher | 
					
						
							|  |  |  |                             .fetchDirectory("/", isLocal, showHidden); | 
					
						
							| 
									
										
										
										
											2022-10-18 23:56:36 +09:00
										 |  |  |                         for (var entry in fd.entries) { | 
					
						
							|  |  |  |                           menuItems.add(MenuEntryButton( | 
					
						
							| 
									
										
										
										
											2022-10-19 23:59:02 +09:00
										 |  |  |                               childBuilder: (TextStyle? style) => | 
					
						
							|  |  |  |                                   Row(children: [ | 
					
						
							| 
									
										
										
										
											2022-10-20 10:31:31 +09:00
										 |  |  |                                     Image( | 
					
						
							|  |  |  |                                         image: iconHardDrive, | 
					
						
							|  |  |  |                                         fit: BoxFit.scaleDown, | 
					
						
							|  |  |  |                                         color: Theme.of(context) | 
					
						
							|  |  |  |                                             .iconTheme | 
					
						
							|  |  |  |                                             .color | 
					
						
							|  |  |  |                                             ?.withOpacity(0.7)), | 
					
						
							| 
									
										
										
										
											2022-10-19 23:59:02 +09:00
										 |  |  |                                     SizedBox(width: 10), | 
					
						
							|  |  |  |                                     Text( | 
					
						
							|  |  |  |                                       entry.name, | 
					
						
							|  |  |  |                                       style: style, | 
					
						
							|  |  |  |                                     ) | 
					
						
							|  |  |  |                                   ]), | 
					
						
							| 
									
										
										
										
											2022-10-18 23:56:36 +09:00
										 |  |  |                               proc: () { | 
					
						
							| 
									
										
										
										
											2023-03-08 22:32:55 +09:00
										 |  |  |                                 controller.openDirectory('${entry.name}\\'); | 
					
						
							| 
									
										
										
										
											2022-10-19 10:52:29 +09:00
										 |  |  |                               }, | 
					
						
							|  |  |  |                               dismissOnClicked: true)); | 
					
						
							| 
									
										
										
										
											2022-10-18 23:56:36 +09:00
										 |  |  |                         } | 
					
						
							| 
									
										
										
										
											2023-03-16 19:39:37 +09:00
										 |  |  |                         menuItems.add(MenuEntryDivider()); | 
					
						
							| 
									
										
										
										
											2022-12-20 23:55:54 +09:00
										 |  |  |                       } catch (e) { | 
					
						
							|  |  |  |                         debugPrint("buildBread fetchDirectory err=$e"); | 
					
						
							| 
									
										
										
										
											2022-10-18 23:56:36 +09:00
										 |  |  |                       } finally { | 
					
						
							| 
									
										
										
										
											2022-10-19 10:52:29 +09:00
										 |  |  |                         if (!isLocal) { | 
					
						
							|  |  |  |                           _ffi.dialogManager.dismissByTag(loadingTag); | 
					
						
							|  |  |  |                         } | 
					
						
							| 
									
										
										
										
											2022-10-18 23:56:36 +09:00
										 |  |  |                       } | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                     mod_menu.showMenu( | 
					
						
							|  |  |  |                         context: context, | 
					
						
							|  |  |  |                         position: RelativeRect.fromLTRB(x, y, x, y), | 
					
						
							|  |  |  |                         elevation: 4, | 
					
						
							|  |  |  |                         items: menuItems | 
					
						
							|  |  |  |                             .map((e) => e.build( | 
					
						
							|  |  |  |                                 context, | 
					
						
							|  |  |  |                                 MenuConfig( | 
					
						
							|  |  |  |                                     commonColor: | 
					
						
							|  |  |  |                                         CustomPopupMenuTheme.commonColor, | 
					
						
							|  |  |  |                                     height: CustomPopupMenuTheme.height, | 
					
						
							|  |  |  |                                     dividerHeight: | 
					
						
							|  |  |  |                                         CustomPopupMenuTheme.dividerHeight, | 
					
						
							|  |  |  |                                     boxWidth: size.width))) | 
					
						
							|  |  |  |                             .expand((i) => i) | 
					
						
							|  |  |  |                             .toList()); | 
					
						
							|  |  |  |                   }, | 
					
						
							|  |  |  |                   iconSize: 20, | 
					
						
							|  |  |  |                 ) | 
					
						
							|  |  |  |               ]); | 
					
						
							| 
									
										
										
										
											2022-08-16 11:46:51 +08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   List<BreadCrumbItem> getPathBreadCrumbItems( | 
					
						
							|  |  |  |       bool isLocal, void Function(List<String>) onPressed) { | 
					
						
							| 
									
										
										
										
											2023-03-08 22:32:55 +09:00
										 |  |  |     final path = controller.directory.value.path; | 
					
						
							| 
									
										
										
										
											2022-08-16 11:46:51 +08:00
										 |  |  |     final breadCrumbList = List<BreadCrumbItem>.empty(growable: true); | 
					
						
							| 
									
										
										
										
											2023-03-08 22:32:55 +09:00
										 |  |  |     final isWindows = controller.options.value.isWindows; | 
					
						
							| 
									
										
										
										
											2022-12-04 23:44:03 +09:00
										 |  |  |     if (isWindows && path == '/') { | 
					
						
							| 
									
										
										
										
											2022-11-02 23:48:02 +09:00
										 |  |  |       breadCrumbList.add(BreadCrumbItem( | 
					
						
							|  |  |  |           content: TextButton( | 
					
						
							| 
									
										
										
										
											2023-03-08 00:49:14 +09:00
										 |  |  |                   child: buildWindowsThisPC(context), | 
					
						
							| 
									
										
										
										
											2022-11-02 23:48:02 +09:00
										 |  |  |                   style: ButtonStyle( | 
					
						
							|  |  |  |                       minimumSize: MaterialStateProperty.all(Size(0, 0))), | 
					
						
							|  |  |  |                   onPressed: () => onPressed(['/'])) | 
					
						
							|  |  |  |               .marginSymmetric(horizontal: 4))); | 
					
						
							|  |  |  |     } else { | 
					
						
							| 
									
										
										
										
											2022-12-04 23:44:03 +09:00
										 |  |  |       final list = PathUtil.split(path, isWindows); | 
					
						
							| 
									
										
										
										
											2023-02-22 22:13:21 +01:00
										 |  |  |       breadCrumbList.addAll( | 
					
						
							|  |  |  |         list.asMap().entries.map( | 
					
						
							|  |  |  |               (e) => BreadCrumbItem( | 
					
						
							|  |  |  |                 content: TextButton( | 
					
						
							| 
									
										
										
										
											2022-11-02 23:48:02 +09:00
										 |  |  |                   child: Text(e.value), | 
					
						
							|  |  |  |                   style: ButtonStyle( | 
					
						
							| 
									
										
										
										
											2023-02-22 22:13:21 +01:00
										 |  |  |                     minimumSize: MaterialStateProperty.all( | 
					
						
							|  |  |  |                       Size(0, 0), | 
					
						
							|  |  |  |                     ), | 
					
						
							|  |  |  |                   ), | 
					
						
							|  |  |  |                   onPressed: () => onPressed( | 
					
						
							|  |  |  |                     list.sublist(0, e.key + 1), | 
					
						
							|  |  |  |                   ), | 
					
						
							|  |  |  |                 ).marginSymmetric(horizontal: 4), | 
					
						
							|  |  |  |               ), | 
					
						
							|  |  |  |             ), | 
					
						
							|  |  |  |       ); | 
					
						
							| 
									
										
										
										
											2022-11-02 23:48:02 +09:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-08-16 11:46:51 +08:00
										 |  |  |     return breadCrumbList; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-08 22:32:55 +09:00
										 |  |  |   breadCrumbScrollToEnd() { | 
					
						
							| 
									
										
										
										
											2022-08-16 12:28:12 +08:00
										 |  |  |     Future.delayed(Duration(milliseconds: 200), () { | 
					
						
							| 
									
										
										
										
											2023-03-08 00:49:14 +09:00
										 |  |  |       if (_breadCrumbScroller.hasClients) { | 
					
						
							|  |  |  |         _breadCrumbScroller.animateTo( | 
					
						
							|  |  |  |             _breadCrumbScroller.position.maxScrollExtent, | 
					
						
							| 
									
										
										
										
											2022-10-13 20:22:11 +09:00
										 |  |  |             duration: Duration(milliseconds: 200), | 
					
						
							|  |  |  |             curve: Curves.fastLinearToSlowEaseIn); | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2022-08-16 12:28:12 +08:00
										 |  |  |     }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-08 22:32:55 +09:00
										 |  |  |   Widget buildPathLocation() { | 
					
						
							| 
									
										
										
										
											2023-03-08 00:49:14 +09:00
										 |  |  |     final text = _locationStatus.value == LocationStatus.pathLocation | 
					
						
							| 
									
										
										
										
											2023-03-08 22:32:55 +09:00
										 |  |  |         ? controller.directory.value.path | 
					
						
							| 
									
										
										
										
											2023-03-08 00:49:14 +09:00
										 |  |  |         : _searchText.value; | 
					
						
							| 
									
										
										
										
											2022-10-13 19:57:59 +09:00
										 |  |  |     final textController = TextEditingController(text: text) | 
					
						
							|  |  |  |       ..selection = TextSelection.collapsed(offset: text.length); | 
					
						
							| 
									
										
										
										
											2023-02-22 22:13:21 +01:00
										 |  |  |     return Row( | 
					
						
							|  |  |  |       children: [ | 
					
						
							|  |  |  |         SvgPicture.asset( | 
					
						
							| 
									
										
										
										
											2023-03-08 00:49:14 +09:00
										 |  |  |           _locationStatus.value == LocationStatus.pathLocation | 
					
						
							| 
									
										
										
										
											2023-02-22 22:13:21 +01:00
										 |  |  |               ? "assets/folder.svg" | 
					
						
							|  |  |  |               : "assets/search.svg", | 
					
						
							|  |  |  |           color: Theme.of(context).tabBarTheme.labelColor, | 
					
						
							|  |  |  |         ), | 
					
						
							|  |  |  |         Expanded( | 
					
						
							| 
									
										
										
										
											2022-10-13 10:17:20 +09:00
										 |  |  |           child: TextField( | 
					
						
							| 
									
										
										
										
											2023-03-08 00:49:14 +09:00
										 |  |  |             focusNode: _locationNode, | 
					
						
							| 
									
										
										
										
											2023-02-22 22:13:21 +01:00
										 |  |  |             decoration: InputDecoration( | 
					
						
							|  |  |  |               border: InputBorder.none, | 
					
						
							|  |  |  |               isDense: true, | 
					
						
							|  |  |  |               prefix: Padding( | 
					
						
							|  |  |  |                 padding: EdgeInsets.only(left: 4.0), | 
					
						
							|  |  |  |               ), | 
					
						
							|  |  |  |             ), | 
					
						
							|  |  |  |             controller: textController, | 
					
						
							|  |  |  |             onSubmitted: (path) { | 
					
						
							| 
									
										
										
										
											2023-03-08 22:32:55 +09:00
										 |  |  |               controller.openDirectory(path); | 
					
						
							| 
									
										
										
										
											2023-02-22 22:13:21 +01:00
										 |  |  |             }, | 
					
						
							| 
									
										
										
										
											2023-03-08 00:49:14 +09:00
										 |  |  |             onChanged: _locationStatus.value == LocationStatus.fileSearchBar | 
					
						
							| 
									
										
										
										
											2023-02-22 22:13:21 +01:00
										 |  |  |                 ? (searchText) => onSearchText(searchText, isLocal) | 
					
						
							|  |  |  |                 : null, | 
					
						
							|  |  |  |           ), | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |       ], | 
					
						
							|  |  |  |     ); | 
					
						
							| 
									
										
										
										
											2022-08-16 11:46:51 +08:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2022-08-16 12:06:54 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-08 00:49:14 +09:00
										 |  |  |   // openDirectory(String path, {bool isLocal = false}) {
 | 
					
						
							|  |  |  |   //   model.openDirectory(path, isLocal: isLocal);
 | 
					
						
							|  |  |  |   // }
 | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2022-12-13 12:55:41 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-08 00:49:14 +09:00
										 |  |  | Widget buildWindowsThisPC(BuildContext context, [TextStyle? textStyle]) { | 
					
						
							|  |  |  |   final color = Theme.of(context).iconTheme.color?.withOpacity(0.7); | 
					
						
							|  |  |  |   return Row(children: [ | 
					
						
							|  |  |  |     Icon(Icons.computer, size: 20, color: color), | 
					
						
							|  |  |  |     SizedBox(width: 10), | 
					
						
							|  |  |  |     Text(translate('This PC'), style: textStyle) | 
					
						
							|  |  |  |   ]); | 
					
						
							| 
									
										
										
										
											2022-06-17 22:57:41 +08:00
										 |  |  | } |