From 1db7fee6fbb1feaa0106e8e26cb21155b678a586 Mon Sep 17 00:00:00 2001 From: Kingtous Date: Sat, 9 Jul 2022 19:14:40 +0800 Subject: [PATCH] opt: dual selected items & send/receive action icon --- .../lib/desktop/pages/file_manager_page.dart | 130 +++++++++++++----- flutter/lib/models/file_model.dart | 43 ++++-- 2 files changed, 128 insertions(+), 45 deletions(-) diff --git a/flutter/lib/desktop/pages/file_manager_page.dart b/flutter/lib/desktop/pages/file_manager_page.dart index fc9f0994d..e9f4ed29c 100644 --- a/flutter/lib/desktop/pages/file_manager_page.dart +++ b/flutter/lib/desktop/pages/file_manager_page.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'dart:io'; +import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter_breadcrumb/flutter_breadcrumb.dart'; @@ -23,7 +24,8 @@ class FileManagerPage extends StatefulWidget { class _FileManagerPageState extends State with AutomaticKeepAliveClientMixin { - final _selectedItems = SelectedItems(); + final _localSelectedItems = SelectedItems(); + final _remoteSelectedItems = SelectedItems(); final _breadCrumbLocalScroller = ScrollController(); final _breadCrumbRemoteScroller = ScrollController(); @@ -32,6 +34,10 @@ class _FileManagerPageState extends State FileModel get model => _ffi.fileModel; + SelectedItems getSelectedItem(bool isLocal) { + return isLocal ? _localSelectedItems : _remoteSelectedItems; + } + @override void initState() { super.initState(); @@ -83,13 +89,6 @@ class _FileManagerPageState extends State })); } - bool needShowCheckBox() { - if (!model.selectMode) { - return false; - } - return !_selectedItems.isOtherPage(model.isLocal); - } - Widget menu({bool isLocal = false}) { return PopupMenuButton( icon: Icon(Icons.more_vert), @@ -145,7 +144,7 @@ class _FileManagerPageState extends State if (v == "refresh") { model.refresh(); } else if (v == "select") { - _selectedItems.clear(); + _localSelectedItems.clear(); model.toggleSelectMode(); } else if (v == "folder") { final name = TextEditingController(); @@ -223,8 +222,16 @@ class _FileManagerPageState extends State return DataRow( key: ValueKey(entry.name), onSelectChanged: (s) { - // TODO + if (s != null) { + if (s) { + getSelectedItem(isLocal).add(isLocal, entry); + } else { + getSelectedItem(isLocal).remove(entry); + } + setState((){}); + } }, + selected: getSelectedItem(isLocal).contains(entry), cells: [ // TODO: icon DataCell(Icon( @@ -240,6 +247,13 @@ class _FileManagerPageState extends State model.openDirectory(entry.path, isLocal: isLocal); } else { // Perform file-related tasks. + final _selectedItems = getSelectedItem(isLocal); + if (_selectedItems.contains(entry)) { + _selectedItems.remove(entry); + } else { + _selectedItems.add(isLocal, entry); + } + setState((){}); } }), DataCell(Text( @@ -377,6 +391,21 @@ class _FileManagerPageState extends State margin: const EdgeInsets.only(top: 16.0,bottom: 16.0, right: 16.0), padding: const EdgeInsets.all(8.0), decoration: BoxDecoration(color: Colors.white70,border: Border.all(color: Colors.grey)), + child: Obx( + () => ListView.builder( + itemExtent: 100, itemBuilder: (BuildContext context, int index) { + final item = model.jobTable[index + 1]; + return Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text('${item.id}'), + Icon(Icons.delete) + ], + ); + }, + itemCount: model.jobTable.length, + ), + ), ), preferredSize: Size(200, double.infinity)); } @@ -398,29 +427,46 @@ class _FileManagerPageState extends State Widget headTools(bool isLocal) => Container( child: Row( children: [ + Offstage( + offstage: isLocal, + child: TextButton.icon( + onPressed: (){}, icon: Transform.rotate( + angle: isLocal ? 0 : pi, + child: Icon( + Icons.send + ), + ), label: Text(isLocal ? translate('Send') : translate('Receive'))), + ), Expanded( - child: BreadCrumb( - items: getPathBreadCrumbItems(() => model.goHome(), (list) { - var path = ""; - if (model.currentHome.startsWith(list[0])) { - // absolute path - for (var item in list) { - path = PathUtil.join(path, item, model.currentIsWindows); + child: Container( + decoration: BoxDecoration( + border: Border.all(color: Colors.black12) + ), + child: BreadCrumb( + items: getPathBreadCrumbItems(() => model.goHome(isLocal: isLocal), (list) { + var path = ""; + final currentHome = model.getCurrentHome(isLocal); + final currentIsWindows = model.getCurrentIsWindows(isLocal); + if (currentHome.startsWith(list[0])) { + // absolute path + for (var item in list) { + path = PathUtil.join(path, item, currentIsWindows); + } + } else { + path += currentHome; + for (var item in list) { + path = PathUtil.join(path, item, currentIsWindows); + } } - } else { - path += model.currentHome; - for (var item in list) { - path = PathUtil.join(path, item, model.currentIsWindows); - } - } - model.openDirectory(path, isLocal: isLocal); + model.openDirectory(path, isLocal: isLocal); }, isLocal), divider: Icon(Icons.chevron_right), overflow: ScrollableOverflow( - controller: isLocal - ? _breadCrumbLocalScroller - : _breadCrumbRemoteScroller), - )), + controller: isLocal + ? _breadCrumbLocalScroller + : _breadCrumbRemoteScroller), + ), + )), Row( children: [ IconButton( @@ -443,8 +489,18 @@ class _FileManagerPageState extends State onSelected: (sort) { model.changeSortStyle(sort, isLocal: isLocal); }), - menu(isLocal: isLocal) + menu(isLocal: isLocal), ], + ), + Offstage( + offstage: !isLocal, + child: TextButton.icon( + onPressed: (){}, icon: Transform.rotate( + angle: isLocal ? 0 : pi, + child: Icon( + Icons.send + ), + ), label: Text(isLocal ? translate('Send') : translate('Receive'))), ) ], )); @@ -476,14 +532,14 @@ class _FileManagerPageState extends State Widget? bottomSheet() { final state = model.jobState; - final isOtherPage = _selectedItems.isOtherPage(model.isLocal); - final selectedItemsLen = "${_selectedItems.length} ${translate("items")}"; - final local = _selectedItems.isLocal == null + final isOtherPage = _localSelectedItems.isOtherPage(model.isLocal); + final selectedItemsLen = "${_localSelectedItems.length} ${translate("items")}"; + final local = _localSelectedItems.isLocal == null ? "" - : " [${_selectedItems.isLocal! ? translate("Local") : translate("Remote")}]"; + : " [${_localSelectedItems.isLocal! ? translate("Local") : translate("Remote")}]"; if (model.selectMode) { - if (_selectedItems.length == 0 || !isOtherPage) { + if (_localSelectedItems.length == 0 || !isOtherPage) { return BottomSheetBody( leading: Icon(Icons.check), title: translate("Selected"), @@ -497,8 +553,8 @@ class _FileManagerPageState extends State IconButton( icon: Icon(Icons.delete_forever), onPressed: () { - if (_selectedItems.length > 0) { - model.removeAction(_selectedItems); + if (_localSelectedItems.length > 0) { + model.removeAction(_localSelectedItems); } }, ) @@ -518,7 +574,7 @@ class _FileManagerPageState extends State icon: Icon(Icons.paste), onPressed: () { model.toggleSelectMode(); - model.sendFiles(_selectedItems); + model.sendFiles(_localSelectedItems); }, ) ]); diff --git a/flutter/lib/models/file_model.dart b/flutter/lib/models/file_model.dart index 7b2456585..af5e5db4b 100644 --- a/flutter/lib/models/file_model.dart +++ b/flutter/lib/models/file_model.dart @@ -5,6 +5,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_hbb/common.dart'; import 'package:flutter_hbb/mobile/pages/file_manager_page.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:get/get.dart'; import 'package:path/path.dart' as Path; import 'model.dart'; @@ -22,6 +23,11 @@ class FileModel extends ChangeNotifier { var _jobProgress = JobProgress(); // from rust update + /// JobTable + final _jobTable = List.empty(growable: true).obs; + + RxList get jobTable => _jobTable; + bool get isLocal => _isLocal; bool get selectMode => _selectMode; @@ -46,6 +52,10 @@ class FileModel extends ChangeNotifier { String get currentHome => _isLocal ? _localOption.home : _remoteOption.home; + String getCurrentHome(bool isLocal) { + return isLocal ? _localOption.home : _remoteOption.home; + } + String get currentShortPath { if (currentDir.path.startsWith(currentHome)) { var path = currentDir.path.replaceFirst(currentHome, ""); @@ -81,6 +91,10 @@ class FileModel extends ChangeNotifier { bool get currentIsWindows => _isLocal ? _localOption.isWindows : _remoteOption.isWindows; + bool getCurrentIsWindows(bool isLocal) { + return isLocal ? _localOption.isWindows : _remoteOption.isWindows; + } + final _fileFetcher = FileFetcher(); final _jobResultListener = JobResultListener>(); @@ -115,10 +129,20 @@ class FileModel extends ChangeNotifier { tryUpdateJobProgress(Map evt) { try { int id = int.parse(evt['id']); - _jobProgress.id = id; - _jobProgress.fileNum = int.parse(evt['file_num']); - _jobProgress.speed = double.parse(evt['speed']); - _jobProgress.finishedSize = int.parse(evt['finished_size']); + if (!isDesktop) { + _jobProgress.id = id; + _jobProgress.fileNum = int.parse(evt['file_num']); + _jobProgress.speed = double.parse(evt['speed']); + _jobProgress.finishedSize = int.parse(evt['finished_size']); + } else { + // Desktop uses jobTable + final job = _jobTable[id]; + if (job != null) { + job.fileNum = int.parse(evt['file_num']); + job.speed = double.parse(evt['speed']); + job.finishedSize = int.parse(evt['finished_size']); + } + } notifyListeners(); } catch (e) { debugPrint("Failed to tryUpdateJobProgress,evt:${evt.toString()}"); @@ -270,12 +294,12 @@ class FileModel extends ChangeNotifier { } notifyListeners(); } catch (e) { - debugPrint("Failed to openDirectory :$e"); + debugPrint("Failed to openDirectory ${path} :$e"); } } - goHome() { - openDirectory(currentHome); + goHome({bool? isLocal}) { + openDirectory(currentHome, isLocal: isLocal); } goToParentDirectory({bool? isLocal}) { @@ -303,7 +327,10 @@ class FileModel extends ChangeNotifier { final showHidden = isRemote ? _localOption.showHidden : _remoteOption.showHidden ; items.items.forEach((from) async { - _jobId++; + final jobId = ++_jobId; + _jobTable[jobId] = JobProgress() + ..state = JobState.inProgress + ..id = jobId; await _ffi.target?.bind.sessionSendFiles(id: '${_ffi.target?.id}', actId: _jobId, path: from.path, to: PathUtil.join(toPath, from.name, isWindows) ,fileNum: 0, includeHidden: showHidden, isRemote: isRemote); });