Merge pull request #2540 from Kingtous/master
feat: add list file search listener and simulate behaviors of the listview on desktop
This commit is contained in:
		
						commit
						bb85e994e1
					
				| @ -7,6 +7,7 @@ import 'package:flutter/gestures.dart'; | |||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
| import 'package:flutter/services.dart'; | import 'package:flutter/services.dart'; | ||||||
| import 'package:flutter_breadcrumb/flutter_breadcrumb.dart'; | import 'package:flutter_breadcrumb/flutter_breadcrumb.dart'; | ||||||
|  | import 'package:flutter_hbb/desktop/widgets/list_search_action_listener.dart'; | ||||||
| import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart'; | import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart'; | ||||||
| import 'package:flutter_hbb/models/file_model.dart'; | import 'package:flutter_hbb/models/file_model.dart'; | ||||||
| import 'package:get/get.dart'; | import 'package:get/get.dart'; | ||||||
| @ -32,6 +33,18 @@ enum LocationStatus { | |||||||
|   fileSearchBar |   fileSearchBar | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | /// 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 | ||||||
|  | } | ||||||
|  | 
 | ||||||
| class FileManagerPage extends StatefulWidget { | class FileManagerPage extends StatefulWidget { | ||||||
|   const FileManagerPage({Key? key, required this.id}) : super(key: key); |   const FileManagerPage({Key? key, required this.id}) : super(key: key); | ||||||
|   final String id; |   final String id; | ||||||
| @ -55,6 +68,11 @@ class _FileManagerPageState extends State<FileManagerPage> | |||||||
|   final _searchTextRemote = "".obs; |   final _searchTextRemote = "".obs; | ||||||
|   final _breadCrumbScrollerLocal = ScrollController(); |   final _breadCrumbScrollerLocal = ScrollController(); | ||||||
|   final _breadCrumbScrollerRemote = ScrollController(); |   final _breadCrumbScrollerRemote = ScrollController(); | ||||||
|  |   final _mouseFocusScope = Rx<MouseFocusScope>(MouseFocusScope.none); | ||||||
|  |   final _keyboardNodeLocal = FocusNode(debugLabel: "keyboardNodeLocal"); | ||||||
|  |   final _keyboardNodeRemote = FocusNode(debugLabel: "keyboardNodeRemote"); | ||||||
|  |   final _listSearchBufferLocal = TimeoutStringBuffer(); | ||||||
|  |   final _listSearchBufferRemote = TimeoutStringBuffer(); | ||||||
| 
 | 
 | ||||||
|   /// [_lastClickTime], [_lastClickEntry] help to handle double click |   /// [_lastClickTime], [_lastClickEntry] help to handle double click | ||||||
|   int _lastClickTime = |   int _lastClickTime = | ||||||
| @ -197,6 +215,7 @@ class _FileManagerPageState extends State<FileManagerPage> | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   Widget body({bool isLocal = false}) { |   Widget body({bool isLocal = false}) { | ||||||
|  |     final scrollController = ScrollController(); | ||||||
|     return Container( |     return Container( | ||||||
|       decoration: BoxDecoration(border: Border.all(color: Colors.black26)), |       decoration: BoxDecoration(border: Border.all(color: Colors.black26)), | ||||||
|       margin: const EdgeInsets.all(16.0), |       margin: const EdgeInsets.all(16.0), | ||||||
| @ -217,8 +236,8 @@ class _FileManagerPageState extends State<FileManagerPage> | |||||||
|             children: [ |             children: [ | ||||||
|               Expanded( |               Expanded( | ||||||
|                 child: SingleChildScrollView( |                 child: SingleChildScrollView( | ||||||
|                   controller: ScrollController(), |                   controller: scrollController, | ||||||
|                   child: _buildDataTable(context, isLocal), |                   child: _buildDataTable(context, isLocal, scrollController), | ||||||
|                 ), |                 ), | ||||||
|               ) |               ) | ||||||
|             ], |             ], | ||||||
| @ -228,7 +247,9 @@ class _FileManagerPageState extends State<FileManagerPage> | |||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   Widget _buildDataTable(BuildContext context, bool isLocal) { |   Widget _buildDataTable( | ||||||
|  |       BuildContext context, bool isLocal, ScrollController scrollController) { | ||||||
|  |     const rowHeight = 25.0; | ||||||
|     final fd = model.getCurrentDir(isLocal); |     final fd = model.getCurrentDir(isLocal); | ||||||
|     final entries = fd.entries; |     final entries = fd.entries; | ||||||
|     final sortIndex = (SortBy style) { |     final sortIndex = (SortBy style) { | ||||||
| @ -246,130 +267,219 @@ class _FileManagerPageState extends State<FileManagerPage> | |||||||
|     final sortAscending = |     final sortAscending = | ||||||
|         isLocal ? model.localSortAscending : model.remoteSortAscending; |         isLocal ? model.localSortAscending : model.remoteSortAscending; | ||||||
| 
 | 
 | ||||||
|     return ObxValue<RxString>( |     return MouseRegion( | ||||||
|       (searchText) { |       onEnter: (evt) { | ||||||
|         final filteredEntries = searchText.isNotEmpty |         _mouseFocusScope.value = | ||||||
|             ? entries.where((element) { |             isLocal ? MouseFocusScope.local : MouseFocusScope.remote; | ||||||
|                 return element.name.contains(searchText.value); |         if (isLocal) { | ||||||
|               }).toList(growable: false) |           _keyboardNodeLocal.requestFocus(); | ||||||
|             : entries; |         } else { | ||||||
|         return DataTable( |           _keyboardNodeRemote.requestFocus(); | ||||||
|           key: ValueKey(isLocal ? 0 : 1), |         } | ||||||
|           showCheckboxColumn: false, |       }, | ||||||
|           dataRowHeight: 25, |       onExit: (evt) { | ||||||
|           headingRowHeight: 30, |         _mouseFocusScope.value = MouseFocusScope.none; | ||||||
|           horizontalMargin: 8, |       }, | ||||||
|           columnSpacing: 8, |       child: ListSearchActionListener( | ||||||
|           showBottomBorder: true, |         node: isLocal ? _keyboardNodeLocal : _keyboardNodeRemote, | ||||||
|           sortColumnIndex: sortIndex, |         buffer: isLocal ? _listSearchBufferLocal : _listSearchBufferRemote, | ||||||
|           sortAscending: sortAscending, |         onNext: (buffer) { | ||||||
|           columns: [ |           debugPrint("searching next for $buffer"); | ||||||
|             DataColumn( |           assert(buffer.length == 1); | ||||||
|                 label: Text( |           final selectedEntries = getSelectedItems(isLocal); | ||||||
|                   translate("Name"), |           assert(selectedEntries.length <= 1); | ||||||
|                 ).marginSymmetric(horizontal: 4), |           var skipCount = 0; | ||||||
|                 onSort: (columnIndex, ascending) { |           if (selectedEntries.items.isNotEmpty) { | ||||||
|                   model.changeSortStyle(SortBy.name, |             final index = entries.indexOf(selectedEntries.items.first); | ||||||
|                       isLocal: isLocal, ascending: ascending); |             if (index < 0) { | ||||||
|                 }), |               return; | ||||||
|             DataColumn( |             } | ||||||
|                 label: Text( |             skipCount = index + 1; | ||||||
|                   translate("Modified"), |           } | ||||||
|                 ), |           var searchResult = entries | ||||||
|                 onSort: (columnIndex, ascending) { |               .skip(skipCount) | ||||||
|                   model.changeSortStyle(SortBy.modified, |               .where((element) => element.name.startsWith(buffer)); | ||||||
|                       isLocal: isLocal, ascending: ascending); |           if (searchResult.isEmpty) { | ||||||
|                 }), |             // cannot find next, lets restart search from head | ||||||
|             DataColumn( |             searchResult = | ||||||
|                 label: Text(translate("Size")), |                 entries.where((element) => element.name.startsWith(buffer)); | ||||||
|                 onSort: (columnIndex, ascending) { |           } | ||||||
|                   model.changeSortStyle(SortBy.size, |           if (searchResult.isEmpty) { | ||||||
|                       isLocal: isLocal, ascending: ascending); |             setState(() { | ||||||
|                 }), |               getSelectedItems(isLocal).clear(); | ||||||
|           ], |             }); | ||||||
|           rows: filteredEntries.map((entry) { |             return; | ||||||
|             final sizeStr = |           } | ||||||
|                 entry.isFile ? readableFileSize(entry.size.toDouble()) : ""; |           _jumpToEntry( | ||||||
|             final lastModifiedStr = entry.isDrive |               isLocal, searchResult.first, scrollController, rowHeight, buffer); | ||||||
|                 ? " " |         }, | ||||||
|                 : "${entry.lastModified().toString().replaceAll(".000", "")}   "; |         onSearch: (buffer) { | ||||||
|             return DataRow( |           debugPrint("searching for $buffer"); | ||||||
|                 key: ValueKey(entry.name), |           final selectedEntries = getSelectedItems(isLocal); | ||||||
|                 onSelectChanged: (s) { |           final searchResult = | ||||||
|                   _onSelectedChanged(getSelectedItems(isLocal), filteredEntries, |               entries.where((element) => element.name.startsWith(buffer)); | ||||||
|                       entry, isLocal); |           selectedEntries.clear(); | ||||||
|                 }, |           if (searchResult.isEmpty) { | ||||||
|                 selected: getSelectedItems(isLocal).contains(entry), |             setState(() { | ||||||
|                 cells: [ |               getSelectedItems(isLocal).clear(); | ||||||
|                   DataCell( |             }); | ||||||
|                     Container( |             return; | ||||||
|                         width: 200, |           } | ||||||
|                         child: Tooltip( |           _jumpToEntry( | ||||||
|                           waitDuration: Duration(milliseconds: 500), |               isLocal, searchResult.first, scrollController, rowHeight, buffer); | ||||||
|                           message: entry.name, |         }, | ||||||
|                           child: Row(children: [ |         child: ObxValue<RxString>( | ||||||
|                             entry.isDrive |           (searchText) { | ||||||
|                                 ? Image( |             final filteredEntries = searchText.isNotEmpty | ||||||
|                                         image: iconHardDrive, |                 ? entries.where((element) { | ||||||
|                                         fit: BoxFit.scaleDown, |                     return element.name.contains(searchText.value); | ||||||
|  |                   }).toList(growable: false) | ||||||
|  |                 : entries; | ||||||
|  |             return DataTable( | ||||||
|  |               key: ValueKey(isLocal ? 0 : 1), | ||||||
|  |               showCheckboxColumn: false, | ||||||
|  |               dataRowHeight: rowHeight, | ||||||
|  |               headingRowHeight: 30, | ||||||
|  |               horizontalMargin: 8, | ||||||
|  |               columnSpacing: 8, | ||||||
|  |               showBottomBorder: true, | ||||||
|  |               sortColumnIndex: sortIndex, | ||||||
|  |               sortAscending: sortAscending, | ||||||
|  |               columns: [ | ||||||
|  |                 DataColumn( | ||||||
|  |                     label: Text( | ||||||
|  |                       translate("Name"), | ||||||
|  |                     ).marginSymmetric(horizontal: 4), | ||||||
|  |                     onSort: (columnIndex, ascending) { | ||||||
|  |                       model.changeSortStyle(SortBy.name, | ||||||
|  |                           isLocal: isLocal, ascending: ascending); | ||||||
|  |                     }), | ||||||
|  |                 DataColumn( | ||||||
|  |                     label: Text( | ||||||
|  |                       translate("Modified"), | ||||||
|  |                     ), | ||||||
|  |                     onSort: (columnIndex, ascending) { | ||||||
|  |                       model.changeSortStyle(SortBy.modified, | ||||||
|  |                           isLocal: isLocal, ascending: ascending); | ||||||
|  |                     }), | ||||||
|  |                 DataColumn( | ||||||
|  |                     label: Text(translate("Size")), | ||||||
|  |                     onSort: (columnIndex, ascending) { | ||||||
|  |                       model.changeSortStyle(SortBy.size, | ||||||
|  |                           isLocal: isLocal, ascending: ascending); | ||||||
|  |                     }), | ||||||
|  |               ], | ||||||
|  |               rows: filteredEntries.map((entry) { | ||||||
|  |                 final sizeStr = | ||||||
|  |                     entry.isFile ? readableFileSize(entry.size.toDouble()) : ""; | ||||||
|  |                 final lastModifiedStr = entry.isDrive | ||||||
|  |                     ? " " | ||||||
|  |                     : "${entry.lastModified().toString().replaceAll(".000", "")}   "; | ||||||
|  |                 return DataRow( | ||||||
|  |                     key: ValueKey(entry.name), | ||||||
|  |                     onSelectChanged: (s) { | ||||||
|  |                       _onSelectedChanged(getSelectedItems(isLocal), | ||||||
|  |                           filteredEntries, entry, isLocal); | ||||||
|  |                     }, | ||||||
|  |                     selected: getSelectedItems(isLocal).contains(entry), | ||||||
|  |                     cells: [ | ||||||
|  |                       DataCell( | ||||||
|  |                         Container( | ||||||
|  |                             width: 200, | ||||||
|  |                             child: Tooltip( | ||||||
|  |                               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) | ||||||
|  |                                     : Icon( | ||||||
|  |                                         entry.isFile | ||||||
|  |                                             ? Icons.feed_outlined | ||||||
|  |                                             : Icons.folder, | ||||||
|  |                                         size: 20, | ||||||
|                                         color: Theme.of(context) |                                         color: Theme.of(context) | ||||||
|                                             .iconTheme |                                             .iconTheme | ||||||
|                                             .color |                                             .color | ||||||
|                                             ?.withOpacity(0.7)) |                                             ?.withOpacity(0.7), | ||||||
|                                     .paddingAll(4) |                                       ).marginSymmetric(horizontal: 2), | ||||||
|                                 : Icon( |                                 Expanded( | ||||||
|                                     entry.isFile |                                     child: Text(entry.name, | ||||||
|                                         ? Icons.feed_outlined |                                         overflow: TextOverflow.ellipsis)) | ||||||
|                                         : Icons.folder, |                               ]), | ||||||
|                                     size: 20, |                             )), | ||||||
|                                     color: Theme.of(context) |                         onTap: () { | ||||||
|                                         .iconTheme |                           final items = getSelectedItems(isLocal); | ||||||
|                                         .color |  | ||||||
|                                         ?.withOpacity(0.7), |  | ||||||
|                                   ).marginSymmetric(horizontal: 2), |  | ||||||
|                             Expanded( |  | ||||||
|                                 child: Text(entry.name, |  | ||||||
|                                     overflow: TextOverflow.ellipsis)) |  | ||||||
|                           ]), |  | ||||||
|                         )), |  | ||||||
|                     onTap: () { |  | ||||||
|                       final items = getSelectedItems(isLocal); |  | ||||||
| 
 | 
 | ||||||
|                       // handle double click |                           // handle double click | ||||||
|                       if (_checkDoubleClick(entry)) { |                           if (_checkDoubleClick(entry)) { | ||||||
|                         openDirectory(entry.path, isLocal: isLocal); |                             openDirectory(entry.path, isLocal: isLocal); | ||||||
|                         items.clear(); |                             items.clear(); | ||||||
|                         return; |                             return; | ||||||
|                       } |                           } | ||||||
|                       _onSelectedChanged( |                           _onSelectedChanged( | ||||||
|                           items, filteredEntries, entry, isLocal); |                               items, filteredEntries, entry, isLocal); | ||||||
|                     }, |                         }, | ||||||
|                   ), |                       ), | ||||||
|                   DataCell(FittedBox( |                       DataCell(FittedBox( | ||||||
|                       child: Tooltip( |                           child: Tooltip( | ||||||
|  |                               waitDuration: Duration(milliseconds: 500), | ||||||
|  |                               message: lastModifiedStr, | ||||||
|  |                               child: Text( | ||||||
|  |                                 lastModifiedStr, | ||||||
|  |                                 style: TextStyle( | ||||||
|  |                                     fontSize: 12, color: MyTheme.darkGray), | ||||||
|  |                               )))), | ||||||
|  |                       DataCell(Tooltip( | ||||||
|                           waitDuration: Duration(milliseconds: 500), |                           waitDuration: Duration(milliseconds: 500), | ||||||
|                           message: lastModifiedStr, |                           message: sizeStr, | ||||||
|                           child: Text( |                           child: Text( | ||||||
|                             lastModifiedStr, |                             sizeStr, | ||||||
|  |                             overflow: TextOverflow.ellipsis, | ||||||
|                             style: TextStyle( |                             style: TextStyle( | ||||||
|                                 fontSize: 12, color: MyTheme.darkGray), |                                 fontSize: 10, color: MyTheme.darkGray), | ||||||
|                           )))), |                           ))), | ||||||
|                   DataCell(Tooltip( |                     ]); | ||||||
|                       waitDuration: Duration(milliseconds: 500), |               }).toList(growable: false), | ||||||
|                       message: sizeStr, |             ); | ||||||
|                       child: Text( |           }, | ||||||
|                         sizeStr, |           isLocal ? _searchTextLocal : _searchTextRemote, | ||||||
|                         overflow: TextOverflow.ellipsis, |         ), | ||||||
|                         style: TextStyle(fontSize: 10, color: MyTheme.darkGray), |       ), | ||||||
|                       ))), |  | ||||||
|                 ]); |  | ||||||
|           }).toList(growable: false), |  | ||||||
|         ); |  | ||||||
|       }, |  | ||||||
|       isLocal ? _searchTextLocal : _searchTextRemote, |  | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   void _jumpToEntry(bool isLocal, Entry entry, | ||||||
|  |       ScrollController scrollController, double rowHeight, String buffer) { | ||||||
|  |     final entries = model.getCurrentDir(isLocal).entries; | ||||||
|  |     final index = entries.indexOf(entry); | ||||||
|  |     if (index == -1) { | ||||||
|  |       debugPrint("entry is not valid: ${entry.path}"); | ||||||
|  |     } | ||||||
|  |     final selectedEntries = getSelectedItems(isLocal); | ||||||
|  |     final searchResult = | ||||||
|  |         entries.where((element) => element.name.startsWith(buffer)); | ||||||
|  |     selectedEntries.clear(); | ||||||
|  |     if (searchResult.isEmpty) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     final offset = min( | ||||||
|  |         max(scrollController.position.minScrollExtent, | ||||||
|  |             entries.indexOf(searchResult.first) * rowHeight), | ||||||
|  |         scrollController.position.maxScrollExtent); | ||||||
|  |     scrollController.jumpTo(offset); | ||||||
|  |     setState(() { | ||||||
|  |       selectedEntries.add(isLocal, searchResult.first); | ||||||
|  |       debugPrint("focused on ${searchResult.first.name}"); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   void _onSelectedChanged(SelectedItems selectedItems, List<Entry> entries, |   void _onSelectedChanged(SelectedItems selectedItems, List<Entry> entries, | ||||||
|       Entry entry, bool isLocal) { |       Entry entry, bool isLocal) { | ||||||
|     final isCtrlDown = RawKeyboard.instance.keysPressed |     final isCtrlDown = RawKeyboard.instance.keysPressed | ||||||
| @ -1015,4 +1125,14 @@ class _FileManagerPageState extends State<FileManagerPage> | |||||||
|     } |     } | ||||||
|     model.sendFiles(items, isRemote: false); |     model.sendFiles(items, isRemote: false); | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|  |   void refocusKeyboardListener(bool isLocal) { | ||||||
|  |     Future.delayed(Duration.zero, () { | ||||||
|  |       if (isLocal) { | ||||||
|  |         _keyboardNodeLocal.requestFocus(); | ||||||
|  |       } else { | ||||||
|  |         _keyboardNodeRemote.requestFocus(); | ||||||
|  |       } | ||||||
|  |     }); | ||||||
|  |   } | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										75
									
								
								flutter/lib/desktop/widgets/list_search_action_listener.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								flutter/lib/desktop/widgets/list_search_action_listener.dart
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,75 @@ | |||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | 
 | ||||||
|  | class ListSearchActionListener extends StatelessWidget { | ||||||
|  |   final FocusNode node; | ||||||
|  |   final TimeoutStringBuffer buffer; | ||||||
|  |   final Widget child; | ||||||
|  |   final Function(String) onNext; | ||||||
|  |   final Function(String) onSearch; | ||||||
|  | 
 | ||||||
|  |   const ListSearchActionListener( | ||||||
|  |       {super.key, | ||||||
|  |       required this.node, | ||||||
|  |       required this.buffer, | ||||||
|  |       required this.child, | ||||||
|  |       required this.onNext, | ||||||
|  |       required this.onSearch}); | ||||||
|  | 
 | ||||||
|  |   @mustCallSuper | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context) { | ||||||
|  |     return KeyboardListener( | ||||||
|  |         autofocus: true, | ||||||
|  |         onKeyEvent: (kv) { | ||||||
|  |           final ch = kv.character; | ||||||
|  |           if (ch == null) { | ||||||
|  |             return; | ||||||
|  |           } | ||||||
|  |           final action = buffer.input(ch); | ||||||
|  |           switch (action) { | ||||||
|  |             case ListSearchAction.search: | ||||||
|  |               onSearch(buffer.buffer); | ||||||
|  |               break; | ||||||
|  |             case ListSearchAction.next: | ||||||
|  |               onNext(buffer.buffer); | ||||||
|  |               break; | ||||||
|  |           } | ||||||
|  |         }, | ||||||
|  |         focusNode: node, | ||||||
|  |         child: child); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | enum ListSearchAction { search, next } | ||||||
|  | 
 | ||||||
|  | class TimeoutStringBuffer { | ||||||
|  |   var _buffer = ""; | ||||||
|  |   late DateTime _duration; | ||||||
|  | 
 | ||||||
|  |   static int timeoutMilliSec = 1500; | ||||||
|  | 
 | ||||||
|  |   String get buffer => _buffer; | ||||||
|  | 
 | ||||||
|  |   TimeoutStringBuffer() { | ||||||
|  |     _duration = DateTime.now(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   ListSearchAction input(String ch) { | ||||||
|  |     final curr = DateTime.now(); | ||||||
|  |     try { | ||||||
|  |       if (curr.difference(_duration).inMilliseconds > timeoutMilliSec) { | ||||||
|  |         _buffer = ch; | ||||||
|  |         return ListSearchAction.search; | ||||||
|  |       } else { | ||||||
|  |         if (ch == _buffer) { | ||||||
|  |           return ListSearchAction.next; | ||||||
|  |         } else { | ||||||
|  |           _buffer += ch; | ||||||
|  |           return ListSearchAction.search; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } finally { | ||||||
|  |       _duration = curr; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
| @ -213,7 +213,7 @@ class FileModel extends ChangeNotifier { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   receiveFileDir(Map<String, dynamic> evt) { |   receiveFileDir(Map<String, dynamic> evt) { | ||||||
|     debugPrint("recv file dir:$evt"); |     // debugPrint("recv file dir:$evt"); | ||||||
|     if (evt['is_local'] == "false") { |     if (evt['is_local'] == "false") { | ||||||
|       // init remote home, the connection will automatic read remote home when established, |       // init remote home, the connection will automatic read remote home when established, | ||||||
|       try { |       try { | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user