allow hide peer tab
Signed-off-by: 21pages <pages21@163.com>
This commit is contained in:
parent
348ed268c3
commit
09d380ba8f
@ -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(
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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"),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user