From 516ff4221b4c8ae50c70ab97abba891f031b2614 Mon Sep 17 00:00:00 2001 From: csf Date: Fri, 14 Oct 2022 23:50:13 +0900 Subject: [PATCH 1/5] opt desktop chat page style --- flutter/lib/common/widgets/overlay.dart | 38 +++++++++---------- .../lib/desktop/widgets/tabbar_widget.dart | 20 +++++----- flutter/lib/mobile/pages/chat_page.dart | 35 ++++++++++++----- 3 files changed, 56 insertions(+), 37 deletions(-) diff --git a/flutter/lib/common/widgets/overlay.dart b/flutter/lib/common/widgets/overlay.dart index c5a3ad8b6..9680d1bf3 100644 --- a/flutter/lib/common/widgets/overlay.dart +++ b/flutter/lib/common/widgets/overlay.dart @@ -36,7 +36,7 @@ class DraggableChatWindow extends StatelessWidget { appBar: CustomAppBar( onPanUpdate: onPanUpdate, appBar: isDesktop - ? _buildDesktopAppBar() + ? _buildDesktopAppBar(context) : _buildMobileAppBar(context), ), body: ChatPage(chatModel: chatModel), @@ -82,33 +82,33 @@ class DraggableChatWindow extends StatelessWidget { ); } - Widget _buildDesktopAppBar() { + Widget _buildDesktopAppBar(BuildContext context) { return Container( - color: MyTheme.accent50, - height: 35, + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: Theme.of(context).hintColor.withOpacity(0.4)))), + height: 38, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Padding( - padding: const EdgeInsets.symmetric(horizontal: 15), - child: Text( - translate("Chat"), - style: const TextStyle( - color: Colors.white, - fontFamily: 'WorkSans', - fontWeight: FontWeight.bold), - )), - Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - ActionIcon( + padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 8), + child: Row(children: [ + Icon(Icons.chat_bubble_outline, + size: 20, color: Theme.of(context).colorScheme.primary), + SizedBox(width: 6), + Text(translate("Chat")) + ])), + Padding( + padding: EdgeInsets.all(2), + child: ActionIcon( message: 'Close', icon: IconFont.close, onTap: chatModel.hideChatWindowOverlay, isClose: true, - ) - ], - ) + size: 32, + )) ], ), ); diff --git a/flutter/lib/desktop/widgets/tabbar_widget.dart b/flutter/lib/desktop/widgets/tabbar_widget.dart index 3eadf75fd..9e191ac28 100644 --- a/flutter/lib/desktop/widgets/tabbar_widget.dart +++ b/flutter/lib/desktop/widgets/tabbar_widget.dart @@ -799,13 +799,15 @@ class ActionIcon extends StatelessWidget { final IconData icon; final Function() onTap; final bool isClose; - const ActionIcon({ - Key? key, - required this.message, - required this.icon, - required this.onTap, - required this.isClose, - }) : super(key: key); + final double? size; + const ActionIcon( + {Key? key, + required this.message, + required this.icon, + required this.onTap, + required this.isClose, + this.size}) + : super(key: key); @override Widget build(BuildContext context) { @@ -820,8 +822,8 @@ class ActionIcon extends StatelessWidget { onHover: (value) => hover.value = value, onTap: onTap, child: SizedBox( - height: _kTabBarHeight - 1, - width: _kTabBarHeight - 1, + height: size ?? (_kTabBarHeight - 1), + width: size ?? (_kTabBarHeight - 1), child: Icon( icon, color: hover.value && isClose diff --git a/flutter/lib/mobile/pages/chat_page.dart b/flutter/lib/mobile/pages/chat_page.dart index b7cf28c9d..11794cb3d 100644 --- a/flutter/lib/mobile/pages/chat_page.dart +++ b/flutter/lib/mobile/pages/chat_page.dart @@ -61,19 +61,36 @@ class ChatPage extends StatelessWidget implements PageShape { [], inputOptions: InputOptions( sendOnEnter: true, - inputDecoration: defaultInputDecoration( - hintText: "${translate('Write a message')}...", - fillColor: Theme.of(context).backgroundColor), - sendButtonBuilder: defaultSendButton( - color: Theme.of(context) - .textTheme - .titleLarge! - .color!), inputTextStyle: TextStyle( + fontSize: 14, color: Theme.of(context) .textTheme .titleLarge - ?.color)), + ?.color), + inputDecoration: isDesktop + ? InputDecoration( + isDense: true, + hintText: + "${translate('Write a message')}...", + filled: true, + fillColor: Theme.of(context).backgroundColor, + contentPadding: EdgeInsets.all(10), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(6), + borderSide: const BorderSide( + width: 0, + style: BorderStyle.none, + ), + ), + ) + : defaultInputDecoration( + hintText: + "${translate('Write a message')}...", + fillColor: Theme.of(context).backgroundColor), + sendButtonBuilder: defaultSendButton( + padding: EdgeInsets.symmetric( + horizontal: 6, vertical: 0), + color: Theme.of(context).colorScheme.primary)), messageOptions: MessageOptions( showOtherUsersAvatar: false, showTime: true, From 76581d46f2c90562e386f580b5a7512ce2df631e Mon Sep 17 00:00:00 2001 From: csf Date: Mon, 17 Oct 2022 19:37:00 +0900 Subject: [PATCH 2/5] fix can't update isMaximized IconButton via double click title logo --- .../lib/desktop/widgets/tabbar_widget.dart | 76 +++++++++++-------- 1 file changed, 43 insertions(+), 33 deletions(-) diff --git a/flutter/lib/desktop/widgets/tabbar_widget.dart b/flutter/lib/desktop/widgets/tabbar_widget.dart index 9e191ac28..98eea7595 100644 --- a/flutter/lib/desktop/widgets/tabbar_widget.dart +++ b/flutter/lib/desktop/widgets/tabbar_widget.dart @@ -191,6 +191,8 @@ class DesktopTab extends StatelessWidget { final DesktopTabController controller; Rx get state => controller.state; + final isMaximized = false.obs; + late final DesktopTabType tabType; late final bool isMainWindow; @@ -297,8 +299,10 @@ class DesktopTab extends StatelessWidget { width: 78, )), GestureDetector( - onDoubleTap: () => - showMaximize ? toggleMaximize(isMainWindow) : null, + onDoubleTap: showMaximize + ? () => toggleMaximize(isMainWindow) + .then((value) => isMaximized.value = value) + : null, onPanStart: (_) => startDragging(isMainWindow), child: Row(children: [ Offstage( @@ -331,6 +335,7 @@ class DesktopTab extends StatelessWidget { tabType: tabType, state: state, tail: tail, + isMaximized: isMaximized, showMinimize: showMinimize, showMaximize: showMaximize, showClose: showClose, @@ -345,6 +350,7 @@ class WindowActionPanel extends StatefulWidget { final bool isMainWindow; final DesktopTabType tabType; final Rx state; + final RxBool isMaximized; final bool showMinimize; final bool showMaximize; @@ -357,6 +363,7 @@ class WindowActionPanel extends StatefulWidget { required this.isMainWindow, required this.tabType, required this.state, + required this.isMaximized, this.tail, this.showMinimize = true, this.showMaximize = true, @@ -372,30 +379,31 @@ class WindowActionPanel extends StatefulWidget { class WindowActionPanelState extends State with MultiWindowListener, WindowListener { - bool isMaximized = false; - @override void initState() { super.initState(); DesktopMultiWindow.addListener(this); windowManager.addListener(this); - if (widget.isMainWindow) { - windowManager.isMaximized().then((maximized) { - if (isMaximized != maximized) { - WidgetsBinding.instance.addPostFrameCallback( - (_) => setState(() => isMaximized = maximized)); - } - }); - } else { - final wc = WindowController.fromWindowId(windowId!); - wc.isMaximized().then((maximized) { - if (isMaximized != maximized) { - WidgetsBinding.instance.addPostFrameCallback( - (_) => setState(() => isMaximized = maximized)); - } - }); - } + Future.delayed(Duration(milliseconds: 500), () { + if (widget.isMainWindow) { + windowManager.isMaximized().then((maximized) { + if (widget.isMaximized.value != maximized) { + WidgetsBinding.instance.addPostFrameCallback( + (_) => setState(() => widget.isMaximized.value = maximized)); + } + }); + } else { + final wc = WindowController.fromWindowId(windowId!); + wc.isMaximized().then((maximized) { + debugPrint("isMaximized $maximized"); + if (widget.isMaximized.value != maximized) { + WidgetsBinding.instance.addPostFrameCallback( + (_) => setState(() => widget.isMaximized.value = maximized)); + } + }); + } + }); } @override @@ -408,8 +416,8 @@ class WindowActionPanelState extends State @override void onWindowMaximize() { // catch maximize from system - if (!isMaximized) { - setState(() => isMaximized = true); + if (!widget.isMaximized.value) { + widget.isMaximized.value = true; } super.onWindowMaximize(); } @@ -417,8 +425,8 @@ class WindowActionPanelState extends State @override void onWindowUnmaximize() { // catch unmaximize from system - if (isMaximized) { - setState(() => isMaximized = false); + if (widget.isMaximized.value) { + widget.isMaximized.value = false; } super.onWindowUnmaximize(); } @@ -450,12 +458,14 @@ class WindowActionPanelState extends State )), Offstage( offstage: !widget.showMaximize, - child: ActionIcon( - message: isMaximized ? "Restore" : "Maximize", - icon: isMaximized ? IconFont.restore : IconFont.max, - onTap: _toggleMaximize, - isClose: false, - )), + child: Obx(() => ActionIcon( + message: widget.isMaximized.value ? "Restore" : "Maximize", + icon: widget.isMaximized.value + ? IconFont.restore + : IconFont.max, + onTap: _toggleMaximize, + isClose: false, + ))), Offstage( offstage: !widget.showClose, child: ActionIcon( @@ -482,9 +492,9 @@ class WindowActionPanelState extends State void _toggleMaximize() { toggleMaximize(widget.isMainWindow).then((maximize) { - if (isMaximized != maximize) { - // setState for sub window, wc.unmaximize/maximize() will not invoke onWindowMaximize/Unmaximize - setState(() => isMaximized = !isMaximized); + if (widget.isMaximized.value != maximize) { + // update state for sub window, wc.unmaximize/maximize() will not invoke onWindowMaximize/Unmaximize + widget.isMaximized.value = maximize; } }); } From 7e7214bd07a2516b927e1fd161f9e5f46771e023 Mon Sep 17 00:00:00 2001 From: csf Date: Mon, 17 Oct 2022 22:26:18 +0900 Subject: [PATCH 3/5] desktop file transfer ctrl + click multi selection --- flutter/lib/consts.dart | 2 + .../lib/desktop/pages/file_manager_page.dart | 320 ++++++++++-------- 2 files changed, 182 insertions(+), 140 deletions(-) diff --git a/flutter/lib/consts.dart b/flutter/lib/consts.dart index f43c20cc6..726ae24be 100644 --- a/flutter/lib/consts.dart +++ b/flutter/lib/consts.dart @@ -31,6 +31,8 @@ const int kMobileMaxDisplayHeight = 1280; const int kDesktopMaxDisplayWidth = 1920; const int kDesktopMaxDisplayHeight = 1080; +const int kDesktopDoubleClickTimeMilli = 200; + const Size kConnectionManagerWindowSize = Size(300, 400); // Tabbar transition duration, now we remove the duration const Duration kTabTransitionDuration = Duration.zero; diff --git a/flutter/lib/desktop/pages/file_manager_page.dart b/flutter/lib/desktop/pages/file_manager_page.dart index f6fae1e31..6c2e20e78 100644 --- a/flutter/lib/desktop/pages/file_manager_page.dart +++ b/flutter/lib/desktop/pages/file_manager_page.dart @@ -1,8 +1,10 @@ +import 'dart:async'; import 'dart:io'; import 'dart:math'; import 'package:desktop_drop/desktop_drop.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_breadcrumb/flutter_breadcrumb.dart'; import 'package:flutter_hbb/mobile/pages/file_manager_page.dart'; import 'package:flutter_hbb/models/file_model.dart'; @@ -44,15 +46,17 @@ class _FileManagerPageState extends State final _locationStatusLocal = LocationStatus.bread.obs; final _locationStatusRemote = LocationStatus.bread.obs; - final FocusNode _locationNodeLocal = - FocusNode(debugLabel: "locationNodeLocal"); - final FocusNode _locationNodeRemote = - FocusNode(debugLabel: "locationNodeRemote"); + final _locationNodeLocal = FocusNode(debugLabel: "locationNodeLocal"); + final _locationNodeRemote = FocusNode(debugLabel: "locationNodeRemote"); final _searchTextLocal = "".obs; final _searchTextRemote = "".obs; final _breadCrumbScrollerLocal = ScrollController(); final _breadCrumbScrollerRemote = ScrollController(); + /// [_lastClickTime], [_lastClickEntry] help to handle double click + int _lastClickTime = DateTime.now().millisecondsSinceEpoch; + Entry? _lastClickEntry; + final _dropMaskVisible = false.obs; // TODO impl drop mask ScrollController getBreadCrumbScrollController(bool isLocal) { @@ -171,22 +175,6 @@ class _FileManagerPageState extends State } Widget body({bool isLocal = false}) { - final fd = model.getCurrentDir(isLocal); - final entries = fd.entries; - final sortIndex = (SortBy style) { - switch (style) { - case SortBy.Name: - return 0; - case SortBy.Type: - return 0; - case SortBy.Modified: - return 1; - case SortBy.Size: - return 2; - } - }(model.getSortStyle(isLocal)); - final sortAscending = - isLocal ? model.localSortAscending : model.remoteSortAscending; return Container( decoration: BoxDecoration(border: Border.all(color: Colors.black26)), margin: const EdgeInsets.all(16.0), @@ -208,126 +196,7 @@ class _FileManagerPageState extends State Expanded( child: SingleChildScrollView( controller: ScrollController(), - child: ObxValue( - (searchText) { - final filteredEntries = searchText.isNotEmpty - ? entries.where((element) { - return element.name.contains(searchText.value); - }).toList(growable: false) - : entries; - return DataTable( - key: ValueKey(isLocal ? 0 : 1), - showCheckboxColumn: true, - dataRowHeight: 25, - 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()) - : ""; - return DataRow( - key: ValueKey(entry.name), - onSelectChanged: (s) { - if (s != null) { - if (s) { - getSelectedItem(isLocal) - .add(isLocal, entry); - } else { - getSelectedItem(isLocal).remove(entry); - } - setState(() {}); - } - }, - selected: - getSelectedItem(isLocal).contains(entry), - cells: [ - DataCell( - Container( - width: 180, - child: Tooltip( - message: entry.name, - child: Row(children: [ - Icon( - entry.isFile - ? Icons.feed_outlined - : Icons.folder, - size: 20, - color: Theme.of(context) - .iconTheme - .color - ?.withOpacity(0.7), - ).marginSymmetric(horizontal: 2), - Expanded( - child: Text(entry.name, - overflow: - TextOverflow.ellipsis)) - ]), - )), onTap: () { - if (entry.isDirectory) { - openDirectory(entry.path, isLocal: isLocal); - if (isLocal) { - _localSelectedItems.clear(); - } else { - _remoteSelectedItems.clear(); - } - } else { - // Perform file-related tasks. - final selectedItems = - getSelectedItem(isLocal); - if (selectedItems.contains(entry)) { - selectedItems.remove(entry); - } else { - selectedItems.add(isLocal, entry); - } - setState(() {}); - } - }), - DataCell(FittedBox( - child: Text( - "${entry.lastModified().toString().replaceAll(".000", "")} ", - style: TextStyle( - fontSize: 12, color: MyTheme.darkGray), - ))), - DataCell(Text( - sizeStr, - overflow: TextOverflow.ellipsis, - style: TextStyle( - fontSize: 10, color: MyTheme.darkGray), - )), - ]); - }).toList(growable: false), - ); - }, - isLocal ? _searchTextLocal : _searchTextRemote, - ), + child: _buildDataTable(context, isLocal), ), ) ], @@ -337,6 +206,176 @@ class _FileManagerPageState extends State ); } + Widget _buildDataTable(BuildContext context, bool isLocal) { + final fd = model.getCurrentDir(isLocal); + final entries = fd.entries; + final sortIndex = (SortBy style) { + switch (style) { + case SortBy.Name: + return 0; + case SortBy.Type: + return 0; + case SortBy.Modified: + return 1; + case SortBy.Size: + return 2; + } + }(model.getSortStyle(isLocal)); + final sortAscending = + isLocal ? model.localSortAscending : model.remoteSortAscending; + + return ObxValue( + (searchText) { + final filteredEntries = searchText.isNotEmpty + ? entries.where((element) { + return element.name.contains(searchText.value); + }).toList(growable: false) + : entries; + return DataTable( + key: ValueKey(isLocal ? 0 : 1), + showCheckboxColumn: false, + dataRowHeight: 25, + 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.lastModified().toString().replaceAll(".000", "")} "; + return DataRow( + key: ValueKey(entry.name), + onSelectChanged: (s) { + final isCtrlDown = RawKeyboard.instance.keysPressed + .contains(LogicalKeyboardKey.controlLeft); + final items = getSelectedItem(isLocal); + if (isCtrlDown) { + if (s != null) { + if (s) { + items.add(isLocal, entry); + } else { + items.remove(entry); + } + } + } else { + items.clear(); + items.add(isLocal, entry); + } + setState(() {}); + }, + selected: getSelectedItem(isLocal).contains(entry), + cells: [ + DataCell( + Container( + width: 200, + child: Tooltip( + waitDuration: Duration(milliseconds: 500), + message: entry.name, + child: Row(children: [ + Icon( + entry.isFile ? Icons.feed_outlined : Icons.folder, + size: 20, + color: Theme.of(context) + .iconTheme + .color + ?.withOpacity(0.7), + ).marginSymmetric(horizontal: 2), + Expanded( + child: Text(entry.name, + overflow: TextOverflow.ellipsis)) + ]), + )), + onTap: () { + final items = getSelectedItem(isLocal); + + // handle double click + if (_checkDoubleClick(entry)) { + openDirectory(entry.path, isLocal: isLocal); + items.clear(); + return; + } + + final isCtrlDown = RawKeyboard.instance.keysPressed + .contains(LogicalKeyboardKey.controlLeft); + if (isCtrlDown) { + if (items.contains(entry)) { + items.remove(entry); + } else { + items.add(isLocal, entry); + } + } else { + items.clear(); + items.add(isLocal, entry); + } + setState(() {}); + }, + ), + DataCell(FittedBox( + child: Tooltip( + waitDuration: Duration(milliseconds: 500), + message: lastModifiedStr, + child: Text( + lastModifiedStr, + style: TextStyle( + fontSize: 12, color: MyTheme.darkGray), + )))), + DataCell(Tooltip( + waitDuration: Duration(milliseconds: 500), + message: sizeStr, + child: Text( + sizeStr, + overflow: TextOverflow.ellipsis, + style: TextStyle(fontSize: 10, color: MyTheme.darkGray), + ))), + ]); + }).toList(growable: false), + ); + }, + isLocal ? _searchTextLocal : _searchTextRemote, + ); + } + + bool _checkDoubleClick(Entry entry) { + final current = DateTime.now().millisecondsSinceEpoch; + final elapsed = current - _lastClickTime; + _lastClickTime = current; + if (_lastClickEntry == entry) { + if (elapsed < kDesktopDoubleClickTimeMilli) { + return true; + } + } else { + _lastClickEntry = entry; + } + return false; + } + /// transfer status list /// watch transfer status Widget statusList() { @@ -369,6 +408,7 @@ class _FileManagerPageState extends State crossAxisAlignment: CrossAxisAlignment.start, children: [ Tooltip( + waitDuration: Duration(milliseconds: 500), message: item.jobName, child: Text( item.jobName, From b265d25dcb1ff3709d5f530f0184c6e4058523df Mon Sep 17 00:00:00 2001 From: csf Date: Mon, 17 Oct 2022 23:07:40 +0900 Subject: [PATCH 4/5] desktop file transfer shift + click multi selection --- .../lib/desktop/pages/file_manager_page.dart | 66 ++++++++++--------- .../lib/mobile/pages/file_manager_page.dart | 47 ------------- flutter/lib/models/file_model.dart | 46 ++++++++++++- 3 files changed, 81 insertions(+), 78 deletions(-) diff --git a/flutter/lib/desktop/pages/file_manager_page.dart b/flutter/lib/desktop/pages/file_manager_page.dart index 6c2e20e78..3ffca3f91 100644 --- a/flutter/lib/desktop/pages/file_manager_page.dart +++ b/flutter/lib/desktop/pages/file_manager_page.dart @@ -6,7 +6,6 @@ import 'package:desktop_drop/desktop_drop.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_breadcrumb/flutter_breadcrumb.dart'; -import 'package:flutter_hbb/mobile/pages/file_manager_page.dart'; import 'package:flutter_hbb/models/file_model.dart'; import 'package:get/get.dart'; import 'package:provider/provider.dart'; @@ -273,21 +272,8 @@ class _FileManagerPageState extends State return DataRow( key: ValueKey(entry.name), onSelectChanged: (s) { - final isCtrlDown = RawKeyboard.instance.keysPressed - .contains(LogicalKeyboardKey.controlLeft); - final items = getSelectedItem(isLocal); - if (isCtrlDown) { - if (s != null) { - if (s) { - items.add(isLocal, entry); - } else { - items.remove(entry); - } - } - } else { - items.clear(); - items.add(isLocal, entry); - } + _onSelectedChanged(getSelectedItem(isLocal), filteredEntries, + entry, isLocal); setState(() {}); }, selected: getSelectedItem(isLocal).contains(entry), @@ -321,20 +307,8 @@ class _FileManagerPageState extends State items.clear(); return; } - - final isCtrlDown = RawKeyboard.instance.keysPressed - .contains(LogicalKeyboardKey.controlLeft); - if (isCtrlDown) { - if (items.contains(entry)) { - items.remove(entry); - } else { - items.add(isLocal, entry); - } - } else { - items.clear(); - items.add(isLocal, entry); - } - setState(() {}); + _onSelectedChanged( + items, filteredEntries, entry, isLocal); }, ), DataCell(FittedBox( @@ -362,6 +336,38 @@ class _FileManagerPageState extends State ); } + void _onSelectedChanged(SelectedItems selectedItems, List entries, + Entry entry, bool isLocal) { + final isCtrlDown = RawKeyboard.instance.keysPressed + .contains(LogicalKeyboardKey.controlLeft); + final isShiftDown = + RawKeyboard.instance.keysPressed.contains(LogicalKeyboardKey.shiftLeft); + if (isCtrlDown) { + if (selectedItems.contains(entry)) { + selectedItems.remove(entry); + } else { + selectedItems.add(isLocal, entry); + } + } else if (isShiftDown) { + final List 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) + .forEach((e) => selectedItems.add(isLocal, e)); + } else { + selectedItems.clear(); + selectedItems.add(isLocal, entry); + } + setState(() {}); + } + bool _checkDoubleClick(Entry entry) { final current = DateTime.now().millisecondsSinceEpoch; final elapsed = current - _lastClickTime; diff --git a/flutter/lib/mobile/pages/file_manager_page.dart b/flutter/lib/mobile/pages/file_manager_page.dart index ce270ffea..0ee0e0f8d 100644 --- a/flutter/lib/mobile/pages/file_manager_page.dart +++ b/flutter/lib/mobile/pages/file_manager_page.dart @@ -555,50 +555,3 @@ class BottomSheetBody extends StatelessWidget { ); } } - -class SelectedItems { - bool? _isLocal; - final List _items = []; - - List get items => _items; - - int get length => _items.length; - - bool? get isLocal => _isLocal; - - add(bool isLocal, Entry e) { - if (_isLocal == null) { - _isLocal = isLocal; - } - if (_isLocal != null && _isLocal != isLocal) { - return; - } - if (!_items.contains(e)) { - _items.add(e); - } - } - - bool contains(Entry e) { - return _items.contains(e); - } - - remove(Entry e) { - _items.remove(e); - if (_items.length == 0) { - _isLocal = null; - } - } - - bool isOtherPage(bool currentIsLocal) { - if (_isLocal == null) { - return false; - } else { - return _isLocal != currentIsLocal; - } - } - - clear() { - _items.clear(); - _isLocal = null; - } -} diff --git a/flutter/lib/models/file_model.dart b/flutter/lib/models/file_model.dart index 1ba66b864..8fb29bd41 100644 --- a/flutter/lib/models/file_model.dart +++ b/flutter/lib/models/file_model.dart @@ -3,7 +3,6 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter_hbb/common.dart'; -import 'package:flutter_hbb/mobile/pages/file_manager_page.dart'; import 'package:get/get.dart'; import 'package:path/path.dart' as Path; @@ -1123,6 +1122,51 @@ class DirectoryOption { } } +class SelectedItems { + bool? _isLocal; + final List _items = []; + + List get items => _items; + + int get length => _items.length; + + bool? get isLocal => _isLocal; + + add(bool isLocal, Entry e) { + _isLocal ??= isLocal; + if (_isLocal != null && _isLocal != isLocal) { + return; + } + if (!_items.contains(e)) { + _items.add(e); + } + } + + bool contains(Entry e) { + return _items.contains(e); + } + + remove(Entry e) { + _items.remove(e); + if (_items.length == 0) { + _isLocal = null; + } + } + + bool isOtherPage(bool currentIsLocal) { + if (_isLocal == null) { + return false; + } else { + return _isLocal != currentIsLocal; + } + } + + clear() { + _items.clear(); + _isLocal = null; + } +} + // code from file_manager pkg after edit List _sortList(List list, SortBy sortType, bool ascending) { if (sortType == SortBy.Name) { From 64c44e1be6855808d875cce52efec39372cc702e Mon Sep 17 00:00:00 2001 From: csf Date: Mon, 17 Oct 2022 23:09:38 +0900 Subject: [PATCH 5/5] file transfer clear selected items onSearchText --- flutter/lib/desktop/pages/file_manager_page.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/flutter/lib/desktop/pages/file_manager_page.dart b/flutter/lib/desktop/pages/file_manager_page.dart index 3ffca3f91..f9521f4dc 100644 --- a/flutter/lib/desktop/pages/file_manager_page.dart +++ b/flutter/lib/desktop/pages/file_manager_page.dart @@ -833,8 +833,10 @@ class _FileManagerPageState extends State onSearchText(String searchText, bool isLocal) { if (isLocal) { + _localSelectedItems.clear(); _searchTextLocal.value = searchText; } else { + _remoteSelectedItems.clear(); _searchTextRemote.value = searchText; } }