Merge pull request #5890 from 21pages/mobile_peer_actions

dynamically display mobile peer tab actions
This commit is contained in:
RustDesk 2023-10-03 13:14:49 +08:00 committed by GitHub
commit e55752869b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 159 additions and 41 deletions

View 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.

View 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

View File

@ -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> {

View File

@ -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;
}
}

View File

@ -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:

View File

@ -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.