allow hide peer tab

Signed-off-by: 21pages <pages21@163.com>
This commit is contained in:
21pages 2023-09-14 16:30:45 +08:00
parent 348ed268c3
commit 09d380ba8f
3 changed files with 157 additions and 10 deletions

View File

@ -1,3 +1,6 @@
import 'dart:ui' as ui;
import 'package:bot_toast/bot_toast.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hbb/common/widgets/address_book.dart'; import 'package:flutter_hbb/common/widgets/address_book.dart';
import 'package:flutter_hbb/common/widgets/dialog.dart'; import 'package:flutter_hbb/common/widgets/dialog.dart';
@ -6,6 +9,9 @@ import 'package:flutter_hbb/common/widgets/peers_view.dart';
import 'package:flutter_hbb/common/widgets/peer_card.dart'; import 'package:flutter_hbb/common/widgets/peer_card.dart';
import 'package:flutter_hbb/consts.dart'; import 'package:flutter_hbb/consts.dart';
import 'package:flutter_hbb/desktop/widgets/popup_menu.dart'; import 'package:flutter_hbb/desktop/widgets/popup_menu.dart';
import 'package:flutter_hbb/desktop/widgets/material_mod_popup_menu.dart'
as mod_menu;
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';
@ -61,6 +67,7 @@ class _PeerTabPageState extends State<PeerTabPage>
({dynamic hint}) => gFFI.groupModel.pull(force: hint == null), ({dynamic hint}) => gFFI.groupModel.pull(force: hint == null),
), ),
]; ];
RelativeRect? mobileTabContextMenuPos;
@override @override
void initState() { void initState() {
@ -100,7 +107,9 @@ class _PeerTabPageState extends State<PeerTabPage>
child: selectionWrap(Row( child: selectionWrap(Row(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
Expanded(child: _createSwitchBar(context)), Expanded(
child:
visibleContextMenuListener(_createSwitchBar(context))),
const PeerSearchBar().marginOnly(right: isMobile ? 0 : 13), const PeerSearchBar().marginOnly(right: isMobile ? 0 : 13),
_createRefresh(), _createRefresh(),
_createMultiSelection(), _createMultiSelection(),
@ -145,7 +154,7 @@ class _PeerTabPageState extends State<PeerTabPage>
return ListView( return ListView(
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
physics: NeverScrollableScrollPhysics(), physics: NeverScrollableScrollPhysics(),
children: model.indexs.map((t) { children: model.visibleIndexs.map((t) {
final selected = model.currentTab == t; final selected = model.currentTab == t;
final color = selected final color = selected
? MyTheme.tabbar(context).selectedTextColor ? MyTheme.tabbar(context).selectedTextColor
@ -164,8 +173,10 @@ class _PeerTabPageState extends State<PeerTabPage>
decoration: decoration:
selected ? decoBorder : (hover.value ? deco : null), selected ? decoBorder : (hover.value ? deco : null),
child: Tooltip( child: Tooltip(
preferBelow: false,
message: message:
model.tabTooltip(t, gFFI.groupModel.groupName.value), model.tabTooltip(t, gFFI.groupModel.groupName.value),
onTriggered: isMobile ? mobileShowTabVisibilityMenu : null,
child: Icon(model.tabIcon(t), color: color), child: Icon(model.tabIcon(t), color: color),
).paddingSymmetric(horizontal: 4), ).paddingSymmetric(horizontal: 4),
).paddingSymmetric(horizontal: 4), ).paddingSymmetric(horizontal: 4),
@ -182,14 +193,15 @@ class _PeerTabPageState extends State<PeerTabPage>
Widget _createPeersView() { Widget _createPeersView() {
final model = Provider.of<PeerTabModel>(context); final model = Provider.of<PeerTabModel>(context);
Widget child; Widget child;
if (model.indexs.isEmpty) { if (model.visibleIndexs.isEmpty) {
child = Center( child = visibleContextMenuListener(Row(
child: Text(translate('Right click to select tabs')), children: [Expanded(child: InkWell())],
); ));
} else { } else {
if (model.indexs.contains(model.currentTab)) { if (model.visibleIndexs.contains(model.currentTab)) {
child = entries[model.currentTab].widget; child = entries[model.currentTab].widget;
} else { } else {
debugPrint("should not happen! currentTab not in visibleIndexs");
Future.delayed(Duration.zero, () { Future.delayed(Duration.zero, () {
model.setCurrentTab(model.indexs[0]); model.setCurrentTab(model.indexs[0]);
}); });
@ -268,6 +280,96 @@ class _PeerTabPageState extends State<PeerTabPage>
); );
} }
void mobileShowTabVisibilityMenu() {
final model = gFFI.peerTabModel;
final items = List<PopupMenuItem>.empty(growable: true);
for (int i = 0; i < model.tabNames.length; i++) {
items.add(PopupMenuItem(
height: kMinInteractiveDimension * 0.8,
onTap: () => model.setTabVisible(i, !model.isVisible[i]),
child: Row(
children: [
Checkbox(
value: model.isVisible[i],
onChanged: (_) {
model.setTabVisible(i, !model.isVisible[i]);
if (Navigator.canPop(context)) {
Navigator.pop(context);
}
}),
Expanded(
child:
Text(model.tabTooltip(i, gFFI.groupModel.groupName.value))),
],
),
));
}
if (mobileTabContextMenuPos != null) {
showMenu(
context: context, position: mobileTabContextMenuPos!, items: items);
}
}
Widget visibleContextMenuListener(Widget child) {
if (isMobile) {
return GestureDetector(
onLongPressDown: (e) {
final x = e.globalPosition.dx;
final y = e.globalPosition.dy;
mobileTabContextMenuPos = RelativeRect.fromLTRB(x, y, x, y);
},
onLongPressUp: () {
mobileShowTabVisibilityMenu();
},
child: child,
);
} else {
return Listener(
onPointerDown: (e) {
if (e.kind != ui.PointerDeviceKind.mouse) {
return;
}
if (e.buttons == 2) {
showRightMenu(
(CancelFunc cancelFunc) {
return visibleContextMenu(cancelFunc);
},
target: e.position,
);
}
},
child: child);
}
}
Widget visibleContextMenu(CancelFunc cancelFunc) {
final model = Provider.of<PeerTabModel>(context);
final menu = List<MenuEntrySwitch>.empty(growable: true);
for (int i = 0; i < model.tabNames.length; i++) {
menu.add(MenuEntrySwitch(
switchType: SwitchType.scheckbox,
text: model.tabTooltip(i, gFFI.groupModel.groupName.value),
getter: () async {
return model.isVisible[i];
},
setter: (show) async {
model.setTabVisible(i, show);
cancelFunc();
}));
}
return mod_menu.PopupMenu(
items: menu
.map((entry) => entry.build(
context,
const MenuConfig(
commonColor: MyTheme.accent,
height: 20.0,
dividerHeight: 12.0,
)))
.expand((i) => i)
.toList());
}
Widget createMultiSelectionBar() { Widget createMultiSelectionBar() {
final model = Provider.of<PeerTabModel>(context); final model = Provider.of<PeerTabModel>(context);
return Row( return Row(

View File

@ -1,3 +1,4 @@
import 'dart:convert';
import 'dart:math'; import 'dart:math';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -36,7 +37,10 @@ class PeerTabModel with ChangeNotifier {
IconFont.addressBook, IconFont.addressBook,
Icons.group, Icons.group,
]; ];
final List<bool> _isVisible = List.filled(4, true, growable: false);
List<bool> get isVisible => _isVisible;
List<int> get indexs => List.generate(tabNames.length, (index) => index); List<int> get indexs => List.generate(tabNames.length, (index) => index);
List<int> get visibleIndexs => indexs.where((e) => _isVisible[e]).toList();
List<Peer> _selectedPeers = List.empty(growable: true); List<Peer> _selectedPeers = List.empty(growable: true);
List<Peer> get selectedPeers => _selectedPeers; List<Peer> get selectedPeers => _selectedPeers;
bool _multiSelectionMode = false; bool _multiSelectionMode = false;
@ -49,12 +53,29 @@ class PeerTabModel with ChangeNotifier {
String get lastId => _lastId; String get lastId => _lastId;
PeerTabModel(this.parent) { PeerTabModel(this.parent) {
// visible
try {
final option = bind.getLocalFlutterOption(k: 'peer-tab-visible');
if (option.isNotEmpty) {
List<dynamic> decodeList = jsonDecode(option);
if (decodeList.length == _isVisible.length) {
for (int i = 0; i < _isVisible.length; i++) {
if (decodeList[i] is bool) {
_isVisible[i] = decodeList[i];
}
}
}
}
} catch (e) {
debugPrint("failed to get peer tab visible list:$e");
}
// init currentTab // init currentTab
_currentTab = _currentTab =
int.tryParse(bind.getLocalFlutterOption(k: 'peer-tab-index')) ?? 0; int.tryParse(bind.getLocalFlutterOption(k: 'peer-tab-index')) ?? 0;
if (_currentTab < 0 || _currentTab >= tabNames.length) { if (_currentTab < 0 || _currentTab >= tabNames.length) {
_currentTab = 0; _currentTab = 0;
} }
_trySetCurrentTabToFirstVisible();
} }
setCurrentTab(int index) { setCurrentTab(int index) {
@ -158,4 +179,31 @@ class PeerTabModel with ChangeNotifier {
} }
} }
} }
setTabVisible(int index, bool visible) {
if (index >= 0 && index < _isVisible.length) {
if (_isVisible[index] != visible) {
_isVisible[index] = visible;
if (index == _currentTab && !visible) {
_trySetCurrentTabToFirstVisible();
} else if (visible && visibleIndexs.length == 1) {
_currentTab = index;
}
try {
bind.setLocalFlutterOption(
k: 'peer-tab-visible', v: jsonEncode(_isVisible));
} catch (_) {}
notifyListeners();
}
}
}
_trySetCurrentTabToFirstVisible() {
if (!_isVisible[_currentTab]) {
int firstVisible = _isVisible.indexWhere((e) => e);
if (firstVisible >= 0) {
_currentTab = firstVisible;
}
}
}
} }

View File

@ -57,7 +57,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("ID Server", "ID serveris"), ("ID Server", "ID serveris"),
("Relay Server", "Releja serveris"), ("Relay Server", "Releja serveris"),
("API Server", "API serveris"), ("API Server", "API serveris"),
("Key", "Atslēga"),
("invalid_http", "jāsākas ar http:// vai https://"), ("invalid_http", "jāsākas ar http:// vai https://"),
("Invalid IP", "Nederīga IP"), ("Invalid IP", "Nederīga IP"),
("Invalid format", "Nederīgs formāts"), ("Invalid format", "Nederīgs formāts"),
@ -297,7 +296,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("This file exists, skip or overwrite this file?", "Šis fails pastāv, izlaist vai pārrakstīt šo failu?"), ("This file exists, skip or overwrite this file?", "Šis fails pastāv, izlaist vai pārrakstīt šo failu?"),
("Quit", "Iziet"), ("Quit", "Iziet"),
("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"), ("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"),
("doc_fix_wayland", "https://rustdesk.com/docs/en/manual/linux/#x11-required"),
("Help", "Palīdzība"), ("Help", "Palīdzība"),
("Failed", "Neizdevās"), ("Failed", "Neizdevās"),
("Succeeded", "Izdevās"), ("Succeeded", "Izdevās"),
@ -481,7 +479,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Me", "Es"), ("Me", "Es"),
("identical_file_tip", "Šis fails ir identisks sesijas failam."), ("identical_file_tip", "Šis fails ir identisks sesijas failam."),
("show_monitors_tip", "Rādīt monitorus rīkjoslā"), ("show_monitors_tip", "Rādīt monitorus rīkjoslā"),
("enter_rustdesk_passwd_tip", "Ievadiet RustDesk paroli"),
("View Mode", "Skatīšanas režīms"), ("View Mode", "Skatīšanas režīms"),
("login_linux_tip", "Jums ir jāpiesakās attālajā Linux kontā, lai iespējotu X darbvirsmas sesiju"), ("login_linux_tip", "Jums ir jāpiesakās attālajā Linux kontā, lai iespējotu X darbvirsmas sesiju"),
("verify_rustdesk_password_tip", "Pārbaudīt RustDesk paroli"), ("verify_rustdesk_password_tip", "Pārbaudīt RustDesk paroli"),