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 roundClose = IconData(0xe6ed, fontFamily: _family2);
|
||||||
static const IconData addressBook =
|
static const IconData addressBook =
|
||||||
IconData(0xe602, fontFamily: "AddressBook");
|
IconData(0xe602, fontFamily: "AddressBook");
|
||||||
static const IconData checkbox = IconData(0xe7d6, fontFamily: "CheckBox");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class ColorThemeExtension extends ThemeExtension<ColorThemeExtension> {
|
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/ab_model.dart';
|
||||||
|
|
||||||
import 'package:flutter_hbb/models/peer_tab_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:get/get.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:pull_down_button/pull_down_button.dart';
|
||||||
|
|
||||||
import '../../common.dart';
|
import '../../common.dart';
|
||||||
import '../../models/platform_model.dart';
|
import '../../models/platform_model.dart';
|
||||||
@ -110,39 +112,10 @@ class _PeerTabPageState extends State<PeerTabPage>
|
|||||||
Expanded(
|
Expanded(
|
||||||
child:
|
child:
|
||||||
visibleContextMenuListener(_createSwitchBar(context))),
|
visibleContextMenuListener(_createSwitchBar(context))),
|
||||||
const PeerSearchBar().marginOnly(right: isMobile ? 0 : 13),
|
if (isMobile)
|
||||||
_createRefresh(
|
..._mobileRightActions(context)
|
||||||
index: PeerTabIndex.ab, loading: gFFI.abModel.abLoading),
|
else
|
||||||
_createRefresh(
|
..._desktopRightActions(context)
|
||||||
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;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
)),
|
)),
|
||||||
),
|
),
|
||||||
@ -270,17 +243,20 @@ class _PeerTabPageState extends State<PeerTabPage>
|
|||||||
Widget _createMultiSelection() {
|
Widget _createMultiSelection() {
|
||||||
final textColor = Theme.of(context).textTheme.titleLarge?.color;
|
final textColor = Theme.of(context).textTheme.titleLarge?.color;
|
||||||
final model = Provider.of<PeerTabModel>(context);
|
final model = Provider.of<PeerTabModel>(context);
|
||||||
if (model.currentTabCachedPeers.isEmpty) return Offstage();
|
|
||||||
return _hoverAction(
|
return _hoverAction(
|
||||||
context: context,
|
context: context,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
model.setMultiSelectionMode(true);
|
model.setMultiSelectionMode(true);
|
||||||
|
if (isMobile && Navigator.canPop(context)) {
|
||||||
|
Navigator.pop(context);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
child: Tooltip(
|
child: Tooltip(
|
||||||
message: translate('Select'),
|
message: translate('Select'),
|
||||||
child: Icon(
|
child: SvgPicture.asset(
|
||||||
IconFont.checkbox,
|
"assets/checkbox-outline.svg",
|
||||||
size: 18,
|
width: 18,
|
||||||
|
height: 18,
|
||||||
color: textColor,
|
color: textColor,
|
||||||
)),
|
)),
|
||||||
);
|
);
|
||||||
@ -564,6 +540,130 @@ class _PeerTabPageState extends State<PeerTabPage>
|
|||||||
Tooltip(message: translate('Close'), child: Icon(Icons.clear)))
|
Tooltip(message: translate('Close'), child: Icon(Icons.clear)))
|
||||||
.marginOnly(left: 6);
|
.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 {
|
class PeerSearchBar extends StatefulWidget {
|
||||||
@ -839,3 +939,14 @@ Widget _hoverAction(
|
|||||||
child: Container(padding: padding, child: child))),
|
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"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.1"
|
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:
|
puppeteer:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -104,6 +104,7 @@ dependencies:
|
|||||||
git:
|
git:
|
||||||
url: https://github.com/21pages/dynamic_layouts.git
|
url: https://github.com/21pages/dynamic_layouts.git
|
||||||
ref: 24cb88413fa5181d949ddacbb30a65d5c459e7d9
|
ref: 24cb88413fa5181d949ddacbb30a65d5c459e7d9
|
||||||
|
pull_down_button: ^0.9.3
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
icons_launcher: ^2.0.4
|
icons_launcher: ^2.0.4
|
||||||
@ -156,9 +157,6 @@ flutter:
|
|||||||
- family: AddressBook
|
- family: AddressBook
|
||||||
fonts:
|
fonts:
|
||||||
- asset: assets/address_book.ttf
|
- 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
|
# An image asset can refer to one or more resolution-specific "variants", see
|
||||||
# https://flutter.dev/assets-and-images/#resolution-aware.
|
# https://flutter.dev/assets-and-images/#resolution-aware.
|
||||||
|
Loading…
Reference in New Issue
Block a user