Merge branch 'master' of https://github.com/rustdesk/rustdesk into opt_chat_overlay_and_fix_pageview_2

This commit is contained in:
csf
2023-02-08 22:29:51 +09:00
161 changed files with 3439 additions and 1118 deletions

View File

@@ -1,6 +1,7 @@
import 'dart:ui' as ui;
import 'package:bot_toast/bot_toast.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hbb/common/widgets/address_book.dart';
import 'package:flutter_hbb/common/widgets/my_group.dart';
@@ -11,106 +12,15 @@ import 'package:flutter_hbb/desktop/widgets/popup_menu.dart';
import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart';
import 'package:flutter_hbb/desktop/widgets/material_mod_popup_menu.dart'
as mod_menu;
import 'package:flutter_hbb/models/peer_tab_model.dart';
import 'package:get/get.dart';
import 'package:get/get_rx/src/rx_workers/utils/debouncer.dart';
import 'package:provider/provider.dart';
import 'package:visibility_detector/visibility_detector.dart';
import '../../common.dart';
import '../../models/platform_model.dart';
const int groupTabIndex = 4;
const String defaultGroupTabname = 'Group';
class StatePeerTab {
final RxInt currentTab = 0.obs;
final RxInt tabHiddenFlag = 0.obs;
final RxList<String> tabNames = [
'Recent Sessions',
'Favorites',
'Discovered',
'Address Book',
defaultGroupTabname,
].obs;
StatePeerTab._() {
tabHiddenFlag.value = (int.tryParse(
bind.getLocalFlutterConfig(k: 'hidden-peer-card'),
radix: 2) ??
0);
var tabs = _notHiddenTabs();
currentTab.value =
int.tryParse(bind.getLocalFlutterConfig(k: 'peer-tab-index')) ?? 0;
if (!tabs.contains(currentTab.value)) {
currentTab.value = 0;
}
}
static final StatePeerTab instance = StatePeerTab._();
check() {
var tabs = _notHiddenTabs();
if (filterGroupCard()) {
if (currentTab.value == groupTabIndex) {
currentTab.value =
tabs.firstWhereOrNull((e) => e != groupTabIndex) ?? 0;
bind.setLocalFlutterConfig(
k: 'peer-tab-index', v: currentTab.value.toString());
}
} else {
if (gFFI.userModel.isAdmin.isFalse &&
gFFI.userModel.groupName.isNotEmpty) {
tabNames[groupTabIndex] = gFFI.userModel.groupName.value;
} else {
tabNames[groupTabIndex] = defaultGroupTabname;
}
if (tabs.contains(groupTabIndex) &&
int.tryParse(bind.getLocalFlutterConfig(k: 'peer-tab-index')) ==
groupTabIndex) {
currentTab.value = groupTabIndex;
}
}
}
List<int> currentTabs() {
var v = List<int>.empty(growable: true);
for (int i = 0; i < tabNames.length; i++) {
if (!_isTabHidden(i) && !_isTabFilter(i)) {
v.add(i);
}
}
return v;
}
bool filterGroupCard() {
if (gFFI.groupModel.users.isEmpty ||
(gFFI.userModel.isAdmin.isFalse && gFFI.userModel.groupName.isEmpty)) {
return true;
} else {
return false;
}
}
bool _isTabHidden(int tabindex) {
return tabHiddenFlag & (1 << tabindex) != 0;
}
bool _isTabFilter(int tabIndex) {
if (tabIndex == groupTabIndex) {
return filterGroupCard();
}
return false;
}
List<int> _notHiddenTabs() {
var v = List<int>.empty(growable: true);
for (int i = 0; i < tabNames.length; i++) {
if (!_isTabHidden(i)) {
v.add(i);
}
}
return v;
}
}
final statePeerTab = StatePeerTab.instance;
class PeerTabPage extends StatefulWidget {
const PeerTabPage({Key? key}) : super(key: key);
@override
@@ -156,11 +66,10 @@ class _PeerTabPageState extends State<PeerTabPage>
),
() => {}),
];
final _scrollDebounce = Debouncer(delay: Duration(milliseconds: 50));
@override
void initState() {
adjustTab();
final uiType = bind.getLocalFlutterConfig(k: 'peer-card-ui-type');
if (uiType != '') {
peerCardUiType.value = int.parse(uiType) == PeerUiType.list.index
@@ -172,16 +81,11 @@ class _PeerTabPageState extends State<PeerTabPage>
Future<void> handleTabSelection(int tabIndex) async {
if (tabIndex < entries.length) {
statePeerTab.currentTab.value = tabIndex;
gFFI.peerTabModel.setCurrentTab(tabIndex);
entries[tabIndex].load();
}
}
@override
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context) {
return Column(
@@ -199,6 +103,7 @@ class _PeerTabPageState extends State<PeerTabPage>
Expanded(
child: visibleContextMenuListener(
_createSwitchBar(context))),
buildScrollJumper(),
const PeerSearchBar(),
Offstage(
offstage: !isDesktop,
@@ -213,82 +118,115 @@ class _PeerTabPageState extends State<PeerTabPage>
}
Widget _createSwitchBar(BuildContext context) {
final textColor = Theme.of(context).textTheme.titleLarge?.color;
return Obx(() {
var tabs = statePeerTab.currentTabs();
return ListView(
scrollDirection: Axis.horizontal,
physics: NeverScrollableScrollPhysics(),
controller: ScrollController(),
children: tabs.map((t) {
return InkWell(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 8),
decoration: BoxDecoration(
color: statePeerTab.currentTab.value == t
? Theme.of(context).backgroundColor
: null,
borderRadius: BorderRadius.circular(isDesktop ? 2 : 6),
),
child: Align(
alignment: Alignment.center,
child: Text(
translatedTabname(t),
textAlign: TextAlign.center,
style: TextStyle(
height: 1,
fontSize: 14,
color: statePeerTab.currentTab.value == t
? textColor
: textColor
?..withOpacity(0.5)),
),
)),
onTap: () async {
await handleTabSelection(t);
await bind.setLocalFlutterConfig(
k: 'peer-tab-index', v: t.toString());
final model = Provider.of<PeerTabModel>(context);
int indexCounter = -1;
return ReorderableListView(
buildDefaultDragHandles: false,
onReorder: (oldIndex, newIndex) {
model.onReorder(oldIndex, newIndex);
},
scrollDirection: Axis.horizontal,
physics: NeverScrollableScrollPhysics(),
scrollController: model.sc,
children: model.visibleOrderedTabs.map((t) {
indexCounter++;
return ReorderableDragStartListener(
key: ValueKey(t),
index: indexCounter,
child: VisibilityDetector(
key: ValueKey(t),
onVisibilityChanged: (info) {
final id = (info.key as ValueKey).value;
model.setTabFullyVisible(id, info.visibleFraction > 0.99);
},
);
}).toList());
});
child: Listener(
// handle mouse wheel
onPointerSignal: (e) {
if (e is PointerScrollEvent) {
if (!model.sc.canScroll) return;
_scrollDebounce.call(() {
model.sc.animateTo(model.sc.offset + e.scrollDelta.dy,
duration: Duration(milliseconds: 200),
curve: Curves.ease);
});
}
},
child: InkWell(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 8),
decoration: BoxDecoration(
color: model.currentTab == t
? Theme.of(context).backgroundColor
: null,
borderRadius: BorderRadius.circular(isDesktop ? 2 : 6),
),
child: Align(
alignment: Alignment.center,
child: Text(
model.translatedTabname(t),
textAlign: TextAlign.center,
style: TextStyle(
height: 1,
fontSize: 14,
color: model.currentTab == t
? MyTheme.tabbar(context).selectedTextColor
: MyTheme.tabbar(context).unSelectedTextColor
?..withOpacity(0.5)),
),
)),
onTap: () async {
await handleTabSelection(t);
await bind.setLocalFlutterConfig(
k: 'peer-tab-index', v: t.toString());
},
),
),
),
);
}).toList());
}
translatedTabname(int index) {
if (index < statePeerTab.tabNames.length) {
final name = statePeerTab.tabNames[index];
if (index == groupTabIndex) {
if (name == defaultGroupTabname) {
return translate(name);
} else {
return name;
}
} else {
return translate(name);
}
}
assert(false);
return index.toString();
Widget buildScrollJumper() {
final model = Provider.of<PeerTabModel>(context);
return Offstage(
offstage: !model.showScrollBtn,
child: Row(
children: [
GestureDetector(
child: Icon(Icons.arrow_left,
size: 22,
color: model.leftFullyVisible
? Theme.of(context).disabledColor
: null),
onTap: model.sc.backward),
GestureDetector(
child: Icon(Icons.arrow_right,
size: 22,
color: model.rightFullyVisible
? Theme.of(context).disabledColor
: null),
onTap: model.sc.forward)
],
));
}
Widget _createPeersView() {
final verticalMargin = isDesktop ? 12.0 : 6.0;
return Expanded(
child: Obx(() {
var tabs = statePeerTab.currentTabs();
if (tabs.isEmpty) {
return visibleContextMenuListener(Center(
child: Text(translate('Right click to select tabs')),
));
final model = Provider.of<PeerTabModel>(context);
Widget child;
if (model.visibleOrderedTabs.isEmpty) {
child = visibleContextMenuListener(Center(
child: Text(translate('Right click to select tabs')),
));
} else {
if (model.visibleOrderedTabs.contains(model.currentTab)) {
child = entries[model.currentTab].widget;
} else {
if (tabs.contains(statePeerTab.currentTab.value)) {
return entries[statePeerTab.currentTab.value].widget;
} else {
statePeerTab.currentTab.value = tabs[0];
return entries[statePeerTab.currentTab.value].widget;
}
model.setCurrentTab(model.visibleOrderedTabs[0]);
child = entries[0].widget;
}
}).marginSymmetric(vertical: verticalMargin));
}
return Expanded(
child: child.marginSymmetric(vertical: isDesktop ? 12.0 : 6.0));
}
Widget _createPeerViewTypeSwitch(BuildContext context) {
@@ -321,13 +259,6 @@ class _PeerTabPageState extends State<PeerTabPage>
);
}
adjustTab() {
var tabs = statePeerTab.currentTabs();
if (tabs.isNotEmpty && !tabs.contains(statePeerTab.currentTab.value)) {
statePeerTab.currentTab.value = tabs[0];
}
}
Widget visibleContextMenuListener(Widget child) {
return Listener(
onPointerDown: (e) {
@@ -347,44 +278,36 @@ class _PeerTabPageState extends State<PeerTabPage>
}
Widget visibleContextMenu(CancelFunc cancelFunc) {
return Obx(() {
final List<MenuEntryBase> menu = List.empty(growable: true);
for (int i = 0; i < statePeerTab.tabNames.length; i++) {
if (i == groupTabIndex && statePeerTab.filterGroupCard()) {
continue;
}
int bitMask = 1 << i;
menu.add(MenuEntrySwitch(
switchType: SwitchType.scheckbox,
text: translatedTabname(i),
getter: () async {
return statePeerTab.tabHiddenFlag & bitMask == 0;
},
setter: (show) async {
if (show) {
statePeerTab.tabHiddenFlag.value &= ~bitMask;
} else {
statePeerTab.tabHiddenFlag.value |= bitMask;
}
await bind.setLocalFlutterConfig(
k: 'hidden-peer-card',
v: statePeerTab.tabHiddenFlag.value.toRadixString(2));
cancelFunc();
adjustTab();
}));
}
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());
});
final model = Provider.of<PeerTabModel>(context);
final List<MenuEntryBase> menu = List.empty(growable: true);
final List<int> menuIndex = List.empty(growable: true);
var list = model.orderedNotFilteredTabs();
for (int i = 0; i < list.length; i++) {
int tabIndex = list[i];
int bitMask = 1 << tabIndex;
menuIndex.add(tabIndex);
menu.add(MenuEntrySwitch(
switchType: SwitchType.scheckbox,
text: model.translatedTabname(tabIndex),
getter: () async {
return model.tabHiddenFlag & bitMask == 0;
},
setter: (show) async {
model.onHideShow(tabIndex, 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());
}
}
@@ -421,7 +344,9 @@ class _PeerSearchBarState extends State<PeerSearchBar> {
FocusNode focusNode = FocusNode();
focusNode.addListener(() {
focused.value = focusNode.hasFocus;
peerSearchTextController.selection = TextSelection(baseOffset: 0, extentOffset: peerSearchTextController.value.text.length);
peerSearchTextController.selection = TextSelection(
baseOffset: 0,
extentOffset: peerSearchTextController.value.text.length);
});
return Container(
width: 120,