From 70c0edcbe764b2b806aa8fae3cc984cb8895bc12 Mon Sep 17 00:00:00 2001 From: 21pages Date: Mon, 2 Oct 2023 19:32:28 +0800 Subject: [PATCH] mobile peer tab right actions dropdown Signed-off-by: 21pages --- flutter/assets/checkbox-outline.svg | 1 + flutter/assets/checkbox.ttf | Bin 1728 -> 0 bytes flutter/assets/chevron_up_chevron_down.svg | 1 + flutter/lib/common.dart | 1 - flutter/lib/common/widgets/peer_tab_page.dart | 185 ++++++++++++++---- flutter/pubspec.lock | 8 + flutter/pubspec.yaml | 4 +- 7 files changed, 159 insertions(+), 41 deletions(-) create mode 100644 flutter/assets/checkbox-outline.svg delete mode 100644 flutter/assets/checkbox.ttf create mode 100644 flutter/assets/chevron_up_chevron_down.svg diff --git a/flutter/assets/checkbox-outline.svg b/flutter/assets/checkbox-outline.svg new file mode 100644 index 000000000..77ca35529 --- /dev/null +++ b/flutter/assets/checkbox-outline.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/flutter/assets/checkbox.ttf b/flutter/assets/checkbox.ttf deleted file mode 100644 index 70ddde69842369c9634906a967604c5bfcff2e86..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1728 zcmd^9OK%%h6#nj=vEz)vZ5~9(MP=$VUO=%^0|E6BMJ-Jv1QfL)LI?>aOf2+2X z>)!(mpZ=Q-&#BH${QN3$pZ`LG#Mq8;gZKyjCmXGJGef}KyHpuJbRE2@Y@wcrFM#z+D|0+oToSF8(;9IO`?beV6IRW#YHjm zq?lS1$HkaPkEKo&O5*v6=N84YCo0FqV-wRkAqr`ZGb9%1Db9*-b5>_uPUpwp`YUs6 zT4wg|TRGwH^=8i&3TJ2KQZnv+o-Y>ja!Ki4wrk}lrBxLtEjf|5_Ip{;KM0*oQVkUI zd%aOo()A>EbpOByHqH&u1d5tYRjY}G5S+T8d zpE=HXinyV%z)^gou|y8HG&acpq_Ii-tHvqJU{~Wb`8|y-tcgc8&Y~#Xi;?HVUe#XT zvYT!g)WRShjaR(-hVMipb2P4bQKuOO_UY36h+6RiFB+BUT(4K+xMtU)uw^f^p6C0a z-HyU*o*S1Maoj#rE~|MIUvk41pSlPh9K_UB*jUFFbrUW^1gIrCe#pMU=sGswGc$U~ zdpLWI(Gfa~Q#EazMv2eX;iwfx1xe)LZaTQm*(&TYW=A&vh?QDI%ep_0o%qBy4^a88 zv5w2=5*kTXn@@F_-)KFf_)@aY*6?*z*G;{z|0PmL!Si+-p8MW \ No newline at end of file diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index 858756ed2..93c23d59d 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -91,7 +91,6 @@ class IconFont { static const IconData roundClose = IconData(0xe6ed, fontFamily: _family2); static const IconData addressBook = IconData(0xe602, fontFamily: "AddressBook"); - static const IconData checkbox = IconData(0xe7d6, fontFamily: "CheckBox"); } class ColorThemeExtension extends ThemeExtension { diff --git a/flutter/lib/common/widgets/peer_tab_page.dart b/flutter/lib/common/widgets/peer_tab_page.dart index 8f02a5c77..b5143eb82 100644 --- a/flutter/lib/common/widgets/peer_tab_page.dart +++ b/flutter/lib/common/widgets/peer_tab_page.dart @@ -15,8 +15,10 @@ import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart'; import 'package:flutter_hbb/models/ab_model.dart'; import 'package:flutter_hbb/models/peer_tab_model.dart'; +import 'package:flutter_svg/flutter_svg.dart'; import 'package:get/get.dart'; import 'package:provider/provider.dart'; +import 'package:pull_down_button/pull_down_button.dart'; import '../../common.dart'; import '../../models/platform_model.dart'; @@ -110,39 +112,10 @@ class _PeerTabPageState extends State Expanded( child: visibleContextMenuListener(_createSwitchBar(context))), - const PeerSearchBar().marginOnly(right: isMobile ? 0 : 13), - _createRefresh( - index: PeerTabIndex.ab, loading: gFFI.abModel.abLoading), - _createRefresh( - index: PeerTabIndex.group, - loading: gFFI.groupModel.groupLoading), - _createMultiSelection(), - Offstage( - offstage: !isDesktop, - child: _createPeerViewTypeSwitch(context)), - Offstage( - offstage: gFFI.peerTabModel.currentTab == 0, - child: PeerSortDropdown(), - ), - Offstage( - offstage: gFFI.peerTabModel.currentTab != 3, - child: _hoverAction( - context: context, - hoverableWhenfalse: hideAbTagsPanel, - child: Tooltip( - message: translate('Toggle Tags'), - child: Icon( - Icons.tag_rounded, - size: 18, - )), - onTap: () async { - await bind.mainSetLocalOption( - key: "hideAbTagsPanel", - value: hideAbTagsPanel.value ? "" : "Y"); - hideAbTagsPanel.value = !hideAbTagsPanel.value; - }, - ), - ), + if (isMobile) + ..._mobileRightActions(context) + else + ..._desktopRightActions(context) ], )), ), @@ -270,17 +243,20 @@ class _PeerTabPageState extends State Widget _createMultiSelection() { final textColor = Theme.of(context).textTheme.titleLarge?.color; final model = Provider.of(context); - if (model.currentTabCachedPeers.isEmpty) return Offstage(); return _hoverAction( context: context, onTap: () { model.setMultiSelectionMode(true); + if (isMobile && Navigator.canPop(context)) { + Navigator.pop(context); + } }, child: Tooltip( message: translate('Select'), - child: Icon( - IconFont.checkbox, - size: 18, + child: SvgPicture.asset( + "assets/checkbox-outline.svg", + width: 18, + height: 18, color: textColor, )), ); @@ -564,6 +540,130 @@ class _PeerTabPageState extends State Tooltip(message: translate('Close'), child: Icon(Icons.clear))) .marginOnly(left: 6); } + + Widget _toggleTags() { + return _hoverAction( + context: context, + hoverableWhenfalse: hideAbTagsPanel, + child: Tooltip( + message: translate('Toggle Tags'), + child: Icon( + Icons.tag_rounded, + size: 18, + )), + onTap: () async { + await bind.mainSetLocalOption( + key: "hideAbTagsPanel", value: hideAbTagsPanel.value ? "" : "Y"); + hideAbTagsPanel.value = !hideAbTagsPanel.value; + }); + } + + List _desktopRightActions(BuildContext context) { + final model = Provider.of(context); + return [ + const PeerSearchBar().marginOnly(right: isMobile ? 0 : 13), + _createRefresh(index: PeerTabIndex.ab, loading: gFFI.abModel.abLoading), + _createRefresh( + index: PeerTabIndex.group, loading: gFFI.groupModel.groupLoading), + Offstage( + offstage: model.currentTabCachedPeers.isEmpty, + child: _createMultiSelection(), + ), + _createPeerViewTypeSwitch(context), + Offstage( + offstage: model.currentTab == PeerTabIndex.recent.index, + child: PeerSortDropdown(), + ), + Offstage( + offstage: model.currentTab != PeerTabIndex.ab.index, + child: _toggleTags(), + ), + ]; + } + + List _mobileRightActions(BuildContext context) { + final model = Provider.of(context); + final screenWidth = MediaQuery.of(context).size.width; + final leftIconSize = Theme.of(context).iconTheme.size ?? 24; + final leftActionsSize = + (leftIconSize + (4 + 4) * 2) * model.visibleIndexs.length; + final availableWidth = screenWidth - 10 * 2 - leftActionsSize - 2 * 2; + final searchWidth = 120; + final otherActionWidth = 18 + 10; + + dropDown(List menus) { + final padding = 6.0; + final textColor = Theme.of(context).textTheme.titleLarge?.color; + return PullDownButton( + buttonBuilder: + (BuildContext context, Future Function() showMenu) { + return _hoverAction( + context: context, + child: Tooltip( + message: translate('More'), + child: SvgPicture.asset( + "assets/chevron_up_chevron_down.svg", + width: 18, + height: 18, + color: textColor, + )), + onTap: showMenu, + ); + }, + routeTheme: PullDownMenuRouteTheme( + width: menus.length * (otherActionWidth + padding * 2) * 1.0), + itemBuilder: (context) => [ + PullDownMenuEntryImpl( + child: Row( + mainAxisSize: MainAxisSize.min, + children: menus + .map((e) => + Material(child: e.paddingSymmetric(horizontal: padding))) + .toList(), + ), + ) + ], + ); + } + + // Always show search, refresh + List actions = [ + const PeerSearchBar(), + if (model.currentTab == PeerTabIndex.ab.index) + _createRefresh(index: PeerTabIndex.ab, loading: gFFI.abModel.abLoading), + if (model.currentTab == PeerTabIndex.group.index) + _createRefresh( + index: PeerTabIndex.group, loading: gFFI.groupModel.groupLoading), + ]; + final List dynamicActions = [ + if (model.currentTabCachedPeers.isNotEmpty) _createMultiSelection(), + if (model.currentTab != PeerTabIndex.recent.index) PeerSortDropdown(), + if (model.currentTab == PeerTabIndex.ab.index) _toggleTags() + ]; + final rightWidth = availableWidth - + searchWidth - + (actions.length == 2 ? otherActionWidth : 0); + final availablePositions = rightWidth ~/ otherActionWidth; + debugPrint( + "dynamic action count:${dynamicActions.length}, available positions: $availablePositions"); + + if (availablePositions < dynamicActions.length && + dynamicActions.length > 1) { + if (availablePositions < 2) { + actions.addAll([ + dropDown(dynamicActions), + ]); + } else { + actions.addAll([ + ...dynamicActions.sublist(0, availablePositions - 1), + dropDown(dynamicActions.sublist(availablePositions - 1)), + ]); + } + } else { + actions.addAll(dynamicActions); + } + return actions; + } } class PeerSearchBar extends StatefulWidget { @@ -839,3 +939,14 @@ Widget _hoverAction( child: Container(padding: padding, child: child))), ); } + +class PullDownMenuEntryImpl extends StatelessWidget + implements PullDownMenuEntry { + final Widget child; + const PullDownMenuEntryImpl({super.key, required this.child}); + + @override + Widget build(BuildContext context) { + return child; + } +} diff --git a/flutter/pubspec.lock b/flutter/pubspec.lock index 559cb11ee..f57a4b92c 100644 --- a/flutter/pubspec.lock +++ b/flutter/pubspec.lock @@ -1045,6 +1045,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.1" + pull_down_button: + dependency: "direct main" + description: + name: pull_down_button + sha256: "235b302701ce029fd9e9470975069376a6700935bb47a5f1b3ec8a5efba07e6f" + url: "https://pub.dev" + source: hosted + version: "0.9.3" puppeteer: dependency: transitive description: diff --git a/flutter/pubspec.yaml b/flutter/pubspec.yaml index 478c9703c..c542dabae 100644 --- a/flutter/pubspec.yaml +++ b/flutter/pubspec.yaml @@ -104,6 +104,7 @@ dependencies: git: url: https://github.com/21pages/dynamic_layouts.git ref: 24cb88413fa5181d949ddacbb30a65d5c459e7d9 + pull_down_button: ^0.9.3 dev_dependencies: icons_launcher: ^2.0.4 @@ -156,9 +157,6 @@ flutter: - family: AddressBook fonts: - asset: assets/address_book.ttf - - family: CheckBox - fonts: - - asset: assets/checkbox.ttf # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware.