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 70ddde698..000000000
Binary files a/flutter/assets/checkbox.ttf and /dev/null differ
diff --git a/flutter/assets/chevron_up_chevron_down.svg b/flutter/assets/chevron_up_chevron_down.svg
new file mode 100644
index 000000000..b5ebf211a
--- /dev/null
+++ b/flutter/assets/chevron_up_chevron_down.svg
@@ -0,0 +1 @@
+
\ 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.