Merge pull request #5890 from 21pages/mobile_peer_actions
dynamically display mobile peer tab actions
This commit is contained in:
commit
e55752869b
1
flutter/assets/checkbox-outline.svg
Normal file
1
flutter/assets/checkbox-outline.svg
Normal file
@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1696255389449" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1922" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32"><path d="M435.2 704c-9 0-17.8-3.8-23.8-10.6l-115.2-128c-11.8-13.2-10.8-33.4 2.4-45.2 13.2-11.8 33.4-10.8 45.2 2.4l90.6 100.6 245.2-291.8c11.4-13.6 31.6-15.2 45-4 13.6 11.4 15.2 31.6 4 45l-268.8 320c-6 7-14.6 11.2-24 11.4-0.2 0.2-0.4 0.2-0.6 0.2z" p-id="1923"></path><path d="M800 928H224c-70.6 0-128-57.4-128-128V224c0-70.6 57.4-128 128-128h576c70.6 0 128 57.4 128 128v576c0 70.6-57.4 128-128 128zM224 160c-35.2 0-64 28.8-64 64v576c0 35.2 28.8 64 64 64h576c35.2 0 64-28.8 64-64V224c0-35.2-28.8-64-64-64H224z" p-id="1924"></path></svg>
|
After Width: | Height: | Size: 856 B |
Binary file not shown.
1
flutter/assets/chevron_up_chevron_down.svg
Normal file
1
flutter/assets/chevron_up_chevron_down.svg
Normal file
@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1696245886035" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4133" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M512 132.717714c-9.435429 0-18.852571 3.84-29.147429 12.434286L194.011429 379.574857c-7.277714 6.418286-11.556571 15.433143-11.556572 28.288 0 22.272 16.713143 39.003429 39.424 39.003429 8.996571 0 18.432-3.437714 28.288-11.154286L512 222.281143l261.851429 213.430857c9.874286 7.716571 19.291429 11.154286 28.708571 11.154286 22.308571 0 39.003429-16.731429 39.003429-39.003429 0-12.854857-4.278857-21.869714-11.556572-28.288L541.147429 144.713143c-10.294857-8.137143-19.291429-11.995429-29.147429-11.995429z m0 758.564572c9.856 0 18.852571-3.84 29.147429-11.995429L829.988571 644.425143c7.277714-6.418286 11.556571-15.433143 11.556572-28.288 0-22.272-16.713143-39.424-38.985143-39.424-9.435429 0-18.870857 3.858286-28.708571 11.574857L512 801.718857 250.148571 588.288c-9.874286-7.716571-19.291429-11.574857-28.288-11.574857-22.710857 0-39.424 17.152-39.424 39.424 0 12.854857 4.278857 21.869714 11.556572 28.288l288.859428 234.422857c10.294857 8.594286 19.712 12.434286 29.147429 12.434286z" p-id="4134"></path></svg>
|
After Width: | Height: | Size: 1.3 KiB |
@ -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<ColorThemeExtension> {
|
||||
|
@ -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<PeerTabPage>
|
||||
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<PeerTabPage>
|
||||
Widget _createMultiSelection() {
|
||||
final textColor = Theme.of(context).textTheme.titleLarge?.color;
|
||||
final model = Provider.of<PeerTabModel>(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<PeerTabPage>
|
||||
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<Widget> _desktopRightActions(BuildContext context) {
|
||||
final model = Provider.of<PeerTabModel>(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<Widget> _mobileRightActions(BuildContext context) {
|
||||
final model = Provider.of<PeerTabModel>(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<Widget> menus) {
|
||||
final padding = 6.0;
|
||||
final textColor = Theme.of(context).textTheme.titleLarge?.color;
|
||||
return PullDownButton(
|
||||
buttonBuilder:
|
||||
(BuildContext context, Future<void> 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<Widget> 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<Widget> 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;
|
||||
}
|
||||
}
|
||||
|
@ -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:
|
||||
|
@ -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.
|
||||
|
Loading…
Reference in New Issue
Block a user