flutter_desktop: connection type, mid commit

Signed-off-by: fufesou <shuanglongchen@yeah.net>
This commit is contained in:
fufesou 2022-08-29 18:48:12 +08:00
parent 256149ecdf
commit 01e96a1134
10 changed files with 303 additions and 165 deletions

View File

@ -743,39 +743,3 @@ Future<List<Peer>>? matchPeers(String searchText, List<Peer> peers) async {
} }
return filteredList; return filteredList;
} }
class PrivacyModeState {
static String tag(String id) => 'privacy_mode_' + id;
static void init(String id) {
final RxBool state = false.obs;
Get.put(state, tag: tag(id));
}
static void delete(String id) => Get.delete(tag: tag(id));
static RxBool find(String id) => Get.find<RxBool>(tag: tag(id));
}
class BlockInputState {
static String tag(String id) => 'block_input_' + id;
static void init(String id) {
final RxBool state = false.obs;
Get.put(state, tag: tag(id));
}
static void delete(String id) => Get.delete(tag: tag(id));
static RxBool find(String id) => Get.find<RxBool>(tag: tag(id));
}
class CurrentDisplayState {
static String tag(String id) => 'current_display_' + id;
static void init(String id) {
final RxInt state = RxInt(0);
Get.put(state, tag: tag(id));
}
static void delete(String id) => Get.delete(tag: tag(id));
static RxInt find(String id) => Get.find<RxInt>(tag: tag(id));
}

View File

@ -0,0 +1,73 @@
import 'package:get/get.dart';
import '../consts.dart';
class PrivacyModeState {
static String tag(String id) => 'privacy_mode_$id';
static void init(String id) {
final RxBool state = false.obs;
Get.put(state, tag: tag(id));
}
static void delete(String id) => Get.delete(tag: tag(id));
static RxBool find(String id) => Get.find<RxBool>(tag: tag(id));
}
class BlockInputState {
static String tag(String id) => 'block_input_$id';
static void init(String id) {
final RxBool state = false.obs;
Get.put(state, tag: tag(id));
}
static void delete(String id) => Get.delete(tag: tag(id));
static RxBool find(String id) => Get.find<RxBool>(tag: tag(id));
}
class CurrentDisplayState {
static String tag(String id) => 'current_display_$id';
static void init(String id) {
final RxInt state = RxInt(0);
Get.put(state, tag: tag(id));
}
static void delete(String id) => Get.delete(tag: tag(id));
static RxInt find(String id) => Get.find<RxInt>(tag: tag(id));
}
class ConnectionType {
final Rx<String> _secure = kInvalidValueStr.obs;
final Rx<String> _direct = kInvalidValueStr.obs;
Rx<String> get secure => _secure;
Rx<String> get direct => _direct;
void setSecure(bool v) {
_secure.value = v ? 'secure' : 'insecure';
}
void setDirect(bool v) {
_direct.value = v ? '' : '_relay';
}
bool isValid() {
return _secure.value != kInvalidValueStr &&
_direct.value != kInvalidValueStr;
}
}
class ConnectionTypeState {
static String tag(String id) => 'connection_type_$id';
static void init(String id) {
final ConnectionType collectionType = ConnectionType();
Get.put(collectionType, tag: tag(id));
}
static void delete(String id) => Get.delete(tag: tag(id));
static ConnectionType find(String id) =>
Get.find<ConnectionType>(tag: tag(id));
}

View File

@ -10,3 +10,5 @@ const String kTabLabelSettingPage = "Settings";
const int kDefaultDisplayWidth = 1280; const int kDefaultDisplayWidth = 1280;
const int kDefaultDisplayHeight = 720; const int kDefaultDisplayHeight = 720;
const kInvalidValueStr = "InvalidValueStr";

View File

@ -3,6 +3,7 @@ import 'dart:convert';
import 'package:desktop_multi_window/desktop_multi_window.dart'; import 'package:desktop_multi_window/desktop_multi_window.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hbb/common.dart'; import 'package:flutter_hbb/common.dart';
import 'package:flutter_hbb/common/shared_state.dart';
import 'package:flutter_hbb/consts.dart'; import 'package:flutter_hbb/consts.dart';
import 'package:flutter_hbb/desktop/pages/remote_page.dart'; import 'package:flutter_hbb/desktop/pages/remote_page.dart';
import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart'; import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart';
@ -20,8 +21,8 @@ class ConnectionTabPage extends StatefulWidget {
class _ConnectionTabPageState extends State<ConnectionTabPage> { class _ConnectionTabPageState extends State<ConnectionTabPage> {
final tabController = Get.put(DesktopTabController()); final tabController = Get.put(DesktopTabController());
static final IconData selectedIcon = Icons.desktop_windows_sharp; static const IconData selectedIcon = Icons.desktop_windows_sharp;
static final IconData unselectedIcon = Icons.desktop_windows_outlined; static const IconData unselectedIcon = Icons.desktop_windows_outlined;
var connectionMap = RxList<Widget>.empty(growable: true); var connectionMap = RxList<Widget>.empty(growable: true);
@ -34,7 +35,7 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
selectedIcon: selectedIcon, selectedIcon: selectedIcon,
unselectedIcon: unselectedIcon, unselectedIcon: unselectedIcon,
page: Obx(() => RemotePage( page: Obx(() => RemotePage(
key: ValueKey(params['id']), key: ValueKey(params['id']),
id: params['id'], id: params['id'],
tabBarHeight: tabBarHeight:
fullscreen.isTrue ? 0 : kDesktopRemoteTabBarHeight, fullscreen.isTrue ? 0 : kDesktopRemoteTabBarHeight,
@ -88,10 +89,10 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
child: Scaffold( child: Scaffold(
backgroundColor: MyTheme.color(context).bg, backgroundColor: MyTheme.color(context).bg,
body: Obx(() => DesktopTab( body: Obx(() => DesktopTab(
controller: tabController, controller: tabController,
theme: theme, theme: theme,
isMainWindow: false, isMainWindow: false,
showTabBar: fullscreen.isFalse, showTabBar: fullscreen.isFalse,
onClose: () { onClose: () {
tabController.clear(); tabController.clear();
}, },
@ -103,7 +104,36 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
.setFullscreen(fullscreen.isTrue); .setFullscreen(fullscreen.isTrue);
return pageView; return pageView;
}, },
))), tabBuilder: (key, icon, label, themeConf) {
final connectionType = ConnectionTypeState.find(key);
if (!connectionType.isValid()) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
icon,
label,
],
);
} else {
final iconName =
'${connectionType.secure.value}${connectionType.direct.value}';
final connectionIcon = Image.asset(
'assets/$iconName.png',
width: themeConf.iconSize,
height: themeConf.iconSize,
color: theme.selectedtabIconColor,
);
//.paddingOnly(right: 5);
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
icon,
connectionIcon,
label,
],
);
}
}))),
), ),
)); ));
} }

View File

@ -5,11 +5,9 @@ import 'dart:ui' as ui;
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_hbb/models/chat_model.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:wakelock/wakelock.dart'; import 'package:wakelock/wakelock.dart';
import 'package:tuple/tuple.dart';
// import 'package:window_manager/window_manager.dart'; // import 'package:window_manager/window_manager.dart';
@ -19,6 +17,8 @@ import '../../mobile/widgets/dialog.dart';
import '../../mobile/widgets/overlay.dart'; import '../../mobile/widgets/overlay.dart';
import '../../models/model.dart'; import '../../models/model.dart';
import '../../models/platform_model.dart'; import '../../models/platform_model.dart';
import '../../models/chat_model.dart';
import '../../common/shared_state.dart';
final initText = '\1' * 1024; final initText = '\1' * 1024;
@ -41,7 +41,7 @@ class _RemotePageState extends State<RemotePage>
Timer? _timer; Timer? _timer;
bool _showBar = !isWebDesktop; bool _showBar = !isWebDesktop;
String _value = ''; String _value = '';
var _cursorOverImage = false.obs; final _cursorOverImage = false.obs;
final FocusNode _mobileFocusNode = FocusNode(); final FocusNode _mobileFocusNode = FocusNode();
final FocusNode _physicalFocusNode = FocusNode(); final FocusNode _physicalFocusNode = FocusNode();
@ -54,6 +54,20 @@ class _RemotePageState extends State<RemotePage>
_ffi.canvasModel.tabBarHeight = widget.tabBarHeight; _ffi.canvasModel.tabBarHeight = widget.tabBarHeight;
} }
void _initStates(String id) {
PrivacyModeState.init(id);
BlockInputState.init(id);
CurrentDisplayState.init(id);
ConnectionTypeState.init(id);
}
void _removeStates(String id) {
PrivacyModeState.delete(id);
BlockInputState.delete(id);
CurrentDisplayState.delete(id);
ConnectionTypeState.delete(id);
}
@override @override
void initState() { void initState() {
super.initState(); super.initState();
@ -74,14 +88,12 @@ class _RemotePageState extends State<RemotePage>
_ffi.listenToMouse(true); _ffi.listenToMouse(true);
_ffi.qualityMonitorModel.checkShowQualityMonitor(widget.id); _ffi.qualityMonitorModel.checkShowQualityMonitor(widget.id);
// WindowManager.instance.addListener(this); // WindowManager.instance.addListener(this);
PrivacyModeState.init(widget.id); _initStates(widget.id);
BlockInputState.init(widget.id);
CurrentDisplayState.init(widget.id);
} }
@override @override
void dispose() { void dispose() {
print("REMOTE PAGE dispose ${widget.id}"); debugPrint("REMOTE PAGE dispose ${widget.id}");
hideMobileActionsOverlay(); hideMobileActionsOverlay();
_ffi.listenToMouse(false); _ffi.listenToMouse(false);
_mobileFocusNode.dispose(); _mobileFocusNode.dispose();
@ -97,9 +109,7 @@ class _RemotePageState extends State<RemotePage>
// WindowManager.instance.removeListener(this); // WindowManager.instance.removeListener(this);
Get.delete<FFI>(tag: widget.id); Get.delete<FFI>(tag: widget.id);
super.dispose(); super.dispose();
PrivacyModeState.delete(widget.id); _removeStates(widget.id);
BlockInputState.delete(widget.id);
CurrentDisplayState.delete(widget.id);
} }
void resetTool() { void resetTool() {

View File

@ -4,12 +4,10 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:tuple/tuple.dart'; import 'package:tuple/tuple.dart';
import './material_mod_popup_menu.dart' as modMenu; import './material_mod_popup_menu.dart' as mod_menu;
const kInvalidValueStr = "InvalidValueStr";
// https://stackoverflow.com/questions/68318314/flutter-popup-menu-inside-popup-menu // https://stackoverflow.com/questions/68318314/flutter-popup-menu-inside-popup-menu
class PopupMenuChildrenItem<T> extends modMenu.PopupMenuEntry<T> { class PopupMenuChildrenItem<T> extends mod_menu.PopupMenuEntry<T> {
const PopupMenuChildrenItem({ const PopupMenuChildrenItem({
key, key,
this.height = kMinInteractiveDimension, this.height = kMinInteractiveDimension,
@ -17,19 +15,19 @@ class PopupMenuChildrenItem<T> extends modMenu.PopupMenuEntry<T> {
this.enable = true, this.enable = true,
this.textStyle, this.textStyle,
this.onTap, this.onTap,
this.position = modMenu.PopupMenuPosition.overSide, this.position = mod_menu.PopupMenuPosition.overSide,
this.offset = Offset.zero, this.offset = Offset.zero,
required this.itemBuilder, required this.itemBuilder,
required this.child, required this.child,
}) : super(key: key); }) : super(key: key);
final modMenu.PopupMenuPosition position; final mod_menu.PopupMenuPosition position;
final Offset offset; final Offset offset;
final TextStyle? textStyle; final TextStyle? textStyle;
final EdgeInsets? padding; final EdgeInsets? padding;
final bool enable; final bool enable;
final void Function()? onTap; final void Function()? onTap;
final List<modMenu.PopupMenuEntry<T>> Function(BuildContext) itemBuilder; final List<mod_menu.PopupMenuEntry<T>> Function(BuildContext) itemBuilder;
final Widget child; final Widget child;
@override @override
@ -59,7 +57,7 @@ class MyPopupMenuItemState<T, W extends PopupMenuChildrenItem<T>>
popupMenuTheme.textStyle ?? popupMenuTheme.textStyle ??
theme.textTheme.subtitle1!; theme.textTheme.subtitle1!;
return modMenu.PopupMenuButton<T>( return mod_menu.PopupMenuButton<T>(
enabled: widget.enable, enabled: widget.enable,
position: widget.position, position: widget.position,
offset: widget.offset, offset: widget.offset,
@ -88,22 +86,26 @@ class MenuConfig {
static const iconWidth = 12.0; static const iconWidth = 12.0;
static const iconHeight = 12.0; static const iconHeight = 12.0;
final double secondMenuHeight; final double height;
final double dividerHeight;
final Color commonColor; final Color commonColor;
const MenuConfig( const MenuConfig(
{required this.commonColor, {required this.commonColor,
this.secondMenuHeight = kMinInteractiveDimension}); this.height = kMinInteractiveDimension,
this.dividerHeight = 16.0});
} }
abstract class MenuEntryBase<T> { abstract class MenuEntryBase<T> {
modMenu.PopupMenuEntry<T> build(BuildContext context, MenuConfig conf); mod_menu.PopupMenuEntry<T> build(BuildContext context, MenuConfig conf);
} }
class MenuEntryDivider<T> extends MenuEntryBase<T> { class MenuEntryDivider<T> extends MenuEntryBase<T> {
@override @override
modMenu.PopupMenuEntry<T> build(BuildContext context, MenuConfig conf) { mod_menu.PopupMenuEntry<T> build(BuildContext context, MenuConfig conf) {
return const modMenu.PopupMenuDivider(); return mod_menu.PopupMenuDivider(
height: conf.dividerHeight,
);
} }
} }
@ -138,14 +140,15 @@ class MenuEntrySubRadios<T> extends MenuEntryBase<T> {
} }
} }
modMenu.PopupMenuEntry<T> _buildSecondMenu( mod_menu.PopupMenuEntry<T> _buildSecondMenu(
BuildContext context, MenuConfig conf, Tuple2<String, String> opt) { BuildContext context, MenuConfig conf, Tuple2<String, String> opt) {
return modMenu.PopupMenuItem( return mod_menu.PopupMenuItem(
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
height: conf.height,
child: TextButton( child: TextButton(
child: Container( child: Container(
alignment: AlignmentDirectional.centerStart, alignment: AlignmentDirectional.centerStart,
constraints: BoxConstraints(minHeight: conf.secondMenuHeight), constraints: BoxConstraints(minHeight: conf.height),
child: Row( child: Row(
children: [ children: [
SizedBox( SizedBox(
@ -156,7 +159,7 @@ class MenuEntrySubRadios<T> extends MenuEntryBase<T> {
Icons.check, Icons.check,
color: conf.commonColor, color: conf.commonColor,
) )
: SizedBox.shrink())), : const SizedBox.shrink())),
const SizedBox(width: MenuConfig.midPadding), const SizedBox(width: MenuConfig.midPadding),
Text( Text(
opt.item1, opt.item1,
@ -178,10 +181,10 @@ class MenuEntrySubRadios<T> extends MenuEntryBase<T> {
} }
@override @override
modMenu.PopupMenuEntry<T> build(BuildContext context, MenuConfig conf) { mod_menu.PopupMenuEntry<T> build(BuildContext context, MenuConfig conf) {
return PopupMenuChildrenItem( return PopupMenuChildrenItem(
height: conf.secondMenuHeight,
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
height: conf.height,
itemBuilder: (BuildContext context) => itemBuilder: (BuildContext context) =>
options.map((opt) => _buildSecondMenu(context, conf, opt)).toList(), options.map((opt) => _buildSecondMenu(context, conf, opt)).toList(),
child: Row(children: [ child: Row(children: [
@ -218,9 +221,10 @@ abstract class MenuEntrySwitchBase<T> extends MenuEntryBase<T> {
Future<void> setOption(bool option); Future<void> setOption(bool option);
@override @override
modMenu.PopupMenuEntry<T> build(BuildContext context, MenuConfig conf) { mod_menu.PopupMenuEntry<T> build(BuildContext context, MenuConfig conf) {
return modMenu.PopupMenuItem( return mod_menu.PopupMenuItem(
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
height: conf.height,
child: Obx( child: Obx(
() => SwitchListTile( () => SwitchListTile(
value: curOption.value, value: curOption.value,
@ -229,7 +233,7 @@ abstract class MenuEntrySwitchBase<T> extends MenuEntryBase<T> {
}, },
title: Container( title: Container(
alignment: AlignmentDirectional.centerStart, alignment: AlignmentDirectional.centerStart,
constraints: BoxConstraints(minHeight: conf.secondMenuHeight), constraints: BoxConstraints(minHeight: conf.height),
child: Text( child: Text(
text, text,
style: const TextStyle( style: const TextStyle(
@ -242,7 +246,7 @@ abstract class MenuEntrySwitchBase<T> extends MenuEntryBase<T> {
horizontal: VisualDensity.minimumDensity, horizontal: VisualDensity.minimumDensity,
vertical: VisualDensity.minimumDensity, vertical: VisualDensity.minimumDensity,
), ),
contentPadding: EdgeInsets.only(left: 8.0), contentPadding: const EdgeInsets.only(left: 8.0),
), ),
), ),
); );
@ -303,11 +307,11 @@ class MenuEntrySubMenu<T> extends MenuEntryBase<T> {
}); });
@override @override
modMenu.PopupMenuEntry<T> build(BuildContext context, MenuConfig conf) { mod_menu.PopupMenuEntry<T> build(BuildContext context, MenuConfig conf) {
return PopupMenuChildrenItem( return PopupMenuChildrenItem(
height: conf.secondMenuHeight, height: conf.height,
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
position: modMenu.PopupMenuPosition.overSide, position: mod_menu.PopupMenuPosition.overSide,
itemBuilder: (BuildContext context) => itemBuilder: (BuildContext context) =>
entries.map((entry) => entry.build(context, conf)).toList(), entries.map((entry) => entry.build(context, conf)).toList(),
child: Row(children: [ child: Row(children: [
@ -342,13 +346,14 @@ class MenuEntryButton<T> extends MenuEntryBase<T> {
}); });
@override @override
modMenu.PopupMenuEntry<T> build(BuildContext context, MenuConfig conf) { mod_menu.PopupMenuEntry<T> build(BuildContext context, MenuConfig conf) {
return modMenu.PopupMenuItem( return mod_menu.PopupMenuItem(
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
height: conf.height,
child: TextButton( child: TextButton(
child: Container( child: Container(
alignment: AlignmentDirectional.centerStart, alignment: AlignmentDirectional.centerStart,
constraints: BoxConstraints(minHeight: conf.secondMenuHeight), constraints: BoxConstraints(minHeight: conf.height),
child: childBuilder( child: childBuilder(
const TextStyle( const TextStyle(
color: Colors.black, color: Colors.black,
@ -362,14 +367,3 @@ class MenuEntryButton<T> extends MenuEntryBase<T> {
); );
} }
} }
class CustomMenu<T> {
final List<MenuEntryBase<T>> entries;
final MenuConfig conf;
const CustomMenu({required this.entries, required this.conf});
List<modMenu.PopupMenuEntry<T>> build(BuildContext context) {
return entries.map((entry) => entry.build(context, conf)).toList();
}
}

View File

@ -9,12 +9,15 @@ import '../../mobile/widgets/dialog.dart';
import '../../mobile/widgets/overlay.dart'; import '../../mobile/widgets/overlay.dart';
import '../../models/model.dart'; import '../../models/model.dart';
import '../../models/platform_model.dart'; import '../../models/platform_model.dart';
import '../../common/shared_state.dart';
import './popup_menu.dart'; import './popup_menu.dart';
import './material_mod_popup_menu.dart' as mod_menu; import './material_mod_popup_menu.dart' as mod_menu;
class _MenubarTheme { class _MenubarTheme {
static const Color commonColor = MyTheme.accent; static const Color commonColor = MyTheme.accent;
static const double height = kMinInteractiveDimension; // kMinInteractiveDimension
static const double height = 24.0;
static const double dividerHeight = 12.0;
} }
class RemoteMenubar extends StatefulWidget { class RemoteMenubar extends StatefulWidget {
@ -168,11 +171,9 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
), ),
itemBuilder: (BuildContext context) { itemBuilder: (BuildContext context) {
final List<Widget> rowChildren = []; final List<Widget> rowChildren = [];
const double selectorScale = 1.3;
for (int i = 0; i < pi.displays.length; i++) { for (int i = 0; i < pi.displays.length; i++) {
rowChildren.add(Transform.scale( rowChildren.add(
scale: selectorScale, Stack(
child: Stack(
alignment: Alignment.center, alignment: Alignment.center,
children: [ children: [
const Icon( const Icon(
@ -203,7 +204,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
) )
], ],
), ),
)); );
} }
return <mod_menu.PopupMenuEntry<String>>[ return <mod_menu.PopupMenuEntry<String>>[
mod_menu.PopupMenuItem<String>( mod_menu.PopupMenuItem<String>(
@ -232,7 +233,8 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
context, context,
const MenuConfig( const MenuConfig(
commonColor: _MenubarTheme.commonColor, commonColor: _MenubarTheme.commonColor,
secondMenuHeight: _MenubarTheme.height, height: _MenubarTheme.height,
dividerHeight: _MenubarTheme.dividerHeight,
))) )))
.toList(), .toList(),
); );
@ -253,7 +255,8 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
context, context,
const MenuConfig( const MenuConfig(
commonColor: _MenubarTheme.commonColor, commonColor: _MenubarTheme.commonColor,
secondMenuHeight: _MenubarTheme.height, height: _MenubarTheme.height,
dividerHeight: _MenubarTheme.dividerHeight,
))) )))
.toList(), .toList(),
); );

View File

@ -121,6 +121,16 @@ class DesktopTabController {
} }
} }
class TabThemeConf {
double iconSize;
TarBarTheme theme;
TabThemeConf({required this.iconSize, required this.theme});
}
typedef TabBuilder = Widget Function(
String key, Widget icon, Widget label, TabThemeConf themeConf);
typedef LabelGetter = Rx<String> Function(String key);
class DesktopTab extends StatelessWidget { class DesktopTab extends StatelessWidget {
final Function(String)? onTabClose; final Function(String)? onTabClose;
final TarBarTheme theme; final TarBarTheme theme;
@ -134,24 +144,29 @@ class DesktopTab extends StatelessWidget {
final Widget Function(Widget pageView)? pageViewBuilder; final Widget Function(Widget pageView)? pageViewBuilder;
final Widget? tail; final Widget? tail;
final VoidCallback? onClose; final VoidCallback? onClose;
final TabBuilder? tabBuilder;
final LabelGetter? labelGetter;
final DesktopTabController controller; final DesktopTabController controller;
Rx<DesktopTabState> get state => controller.state; Rx<DesktopTabState> get state => controller.state;
const DesktopTab( const DesktopTab({
{required this.controller, required this.controller,
required this.isMainWindow, required this.isMainWindow,
this.theme = const TarBarTheme.light(), this.theme = const TarBarTheme.light(),
this.onTabClose, this.onTabClose,
this.showTabBar = true, this.showTabBar = true,
this.showLogo = true, this.showLogo = true,
this.showTitle = true, this.showTitle = true,
this.showMinimize = true, this.showMinimize = true,
this.showMaximize = true, this.showMaximize = true,
this.showClose = true, this.showClose = true,
this.pageViewBuilder, this.pageViewBuilder,
this.tail, this.tail,
this.onClose}); this.onClose,
this.tabBuilder,
this.labelGetter,
});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -194,8 +209,10 @@ class DesktopTab extends StatelessWidget {
child: Row( child: Row(
children: [ children: [
Offstage( Offstage(
offstage: !Platform.isMacOS, offstage: !Platform.isMacOS,
child: const SizedBox(width: 78,)), child: const SizedBox(
width: 78,
)),
Row(children: [ Row(children: [
Offstage( Offstage(
offstage: !showLogo, offstage: !showLogo,
@ -228,6 +245,8 @@ class DesktopTab extends StatelessWidget {
controller: controller, controller: controller,
onTabClose: onTabClose, onTabClose: onTabClose,
theme: theme, theme: theme,
tabBuilder: tabBuilder,
labelGetter: labelGetter,
)), )),
), ),
], ],
@ -356,10 +375,18 @@ class _ListView extends StatelessWidget {
final DesktopTabController controller; final DesktopTabController controller;
final Function(String key)? onTabClose; final Function(String key)? onTabClose;
final TarBarTheme theme; final TarBarTheme theme;
final TabBuilder? tabBuilder;
final LabelGetter? labelGetter;
Rx<DesktopTabState> get state => controller.state; Rx<DesktopTabState> get state => controller.state;
const _ListView( _ListView(
{required this.controller, required this.onTabClose, required this.theme}); {required this.controller,
required this.onTabClose,
required this.theme,
this.tabBuilder,
this.labelGetter});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -373,7 +400,9 @@ class _ListView extends StatelessWidget {
final tab = e.value; final tab = e.value;
return _Tab( return _Tab(
index: index, index: index,
label: tab.label, label: labelGetter == null
? Rx<String>(tab.label)
: labelGetter!(tab.label),
selectedIcon: tab.selectedIcon, selectedIcon: tab.selectedIcon,
unselectedIcon: tab.unselectedIcon, unselectedIcon: tab.unselectedIcon,
closable: tab.closable, closable: tab.closable,
@ -381,6 +410,16 @@ class _ListView extends StatelessWidget {
onClose: () => controller.remove(index), onClose: () => controller.remove(index),
onSelected: () => controller.jumpTo(index), onSelected: () => controller.jumpTo(index),
theme: theme, theme: theme,
tabBuilder: tabBuilder == null
? null
: (Widget icon, Widget labelWidget, TabThemeConf themeConf) {
return tabBuilder!(
tab.label,
icon,
labelWidget,
themeConf,
);
},
); );
}).toList())); }).toList()));
} }
@ -388,7 +427,7 @@ class _ListView extends StatelessWidget {
class _Tab extends StatelessWidget { class _Tab extends StatelessWidget {
late final int index; late final int index;
late final String label; late final Rx<String> label;
late final IconData? selectedIcon; late final IconData? selectedIcon;
late final IconData? unselectedIcon; late final IconData? unselectedIcon;
late final bool closable; late final bool closable;
@ -397,6 +436,8 @@ class _Tab extends StatelessWidget {
late final Function() onSelected; late final Function() onSelected;
final RxBool _hover = false.obs; final RxBool _hover = false.obs;
late final TarBarTheme theme; late final TarBarTheme theme;
final Widget Function(Widget icon, Widget label, TabThemeConf themeConf)?
tabBuilder;
_Tab( _Tab(
{Key? key, {Key? key,
@ -404,6 +445,7 @@ class _Tab extends StatelessWidget {
required this.label, required this.label,
this.selectedIcon, this.selectedIcon,
this.unselectedIcon, this.unselectedIcon,
this.tabBuilder,
required this.closable, required this.closable,
required this.selected, required this.selected,
required this.onClose, required this.onClose,
@ -411,11 +453,49 @@ class _Tab extends StatelessWidget {
required this.theme}) required this.theme})
: super(key: key); : super(key: key);
Widget _buildTabContent() {
bool showIcon = selectedIcon != null && unselectedIcon != null;
bool isSelected = index == selected;
final icon = Offstage(
offstage: !showIcon,
child: Icon(
isSelected ? selectedIcon : unselectedIcon,
size: _kIconSize,
color: isSelected
? theme.selectedtabIconColor
: theme.unSelectedtabIconColor,
).paddingOnly(right: 5));
final labelWidget = Obx(() {
return Text(
translate(label.value),
textAlign: TextAlign.center,
style: TextStyle(
color: isSelected
? theme.selectedTextColor
: theme.unSelectedTextColor),
);
});
if (tabBuilder == null) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
icon,
labelWidget,
],
);
} else {
return tabBuilder!(
icon, labelWidget, TabThemeConf(iconSize: _kIconSize, theme: theme));
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
bool show_icon = selectedIcon != null && unselectedIcon != null; bool showIcon = selectedIcon != null && unselectedIcon != null;
bool is_selected = index == selected; bool isSelected = index == selected;
bool show_divider = index != selected - 1 && index != selected; bool showDivider = index != selected - 1 && index != selected;
return Ink( return Ink(
child: InkWell( child: InkWell(
onHover: (hover) => _hover.value = hover, onHover: (hover) => _hover.value = hover,
@ -427,40 +507,19 @@ class _Tab extends StatelessWidget {
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
Row( _buildTabContent(),
mainAxisAlignment: MainAxisAlignment.center,
children: [
Offstage(
offstage: !show_icon,
child: Icon(
is_selected ? selectedIcon : unselectedIcon,
size: _kIconSize,
color: is_selected
? theme.selectedtabIconColor
: theme.unSelectedtabIconColor,
).paddingOnly(right: 5)),
Text(
translate(label),
textAlign: TextAlign.center,
style: TextStyle(
color: is_selected
? theme.selectedTextColor
: theme.unSelectedTextColor),
),
],
),
Offstage( Offstage(
offstage: !closable, offstage: !closable,
child: Obx((() => _CloseButton( child: Obx((() => _CloseButton(
visiable: _hover.value, visiable: _hover.value,
tabSelected: is_selected, tabSelected: isSelected,
onClose: () => onClose(), onClose: () => onClose(),
theme: theme, theme: theme,
))), ))),
) )
])).paddingSymmetric(horizontal: 10), ])).paddingSymmetric(horizontal: 10),
Offstage( Offstage(
offstage: !show_divider, offstage: !showDivider,
child: VerticalDivider( child: VerticalDivider(
width: 1, width: 1,
indent: _kDividerIndent, indent: _kDividerIndent,

View File

@ -202,7 +202,7 @@ class App extends StatelessWidget {
title: 'RustDesk', title: 'RustDesk',
theme: getCurrentTheme(), theme: getCurrentTheme(),
home: isDesktop home: isDesktop
? DesktopTabPage() ? const DesktopTabPage()
: !isAndroid : !isAndroid
? WebHomePage() ? WebHomePage()
: HomePage(), : HomePage(),

View File

@ -17,6 +17,7 @@ import 'package:shared_preferences/shared_preferences.dart';
import 'package:tuple/tuple.dart'; import 'package:tuple/tuple.dart';
import '../common.dart'; import '../common.dart';
import '../common/shared_state.dart';
import '../mobile/widgets/dialog.dart'; import '../mobile/widgets/dialog.dart';
import '../mobile/widgets/overlay.dart'; import '../mobile/widgets/overlay.dart';
import 'peer_model.dart'; import 'peer_model.dart';
@ -96,25 +97,26 @@ class FfiModel with ChangeNotifier {
clearPermissions(); clearPermissions();
} }
void setConnectionType(bool secure, bool direct) { void setConnectionType(String peerId, bool secure, bool direct) {
_secure = secure; _secure = secure;
_direct = direct; _direct = direct;
try {
var connectionType = ConnectionTypeState.find(peerId);
connectionType.setSecure(secure);
connectionType.setDirect(direct);
} catch (e) {
//
}
} }
Image? getConnectionImage() { Image? getConnectionImage() {
String? icon; if (secure == null || direct == null) {
if (secure == true && direct == true) { return null;
icon = 'secure'; } else {
} else if (secure == false && direct == true) { final icon =
icon = 'insecure'; '${secure == true ? "secure" : "insecure"}${direct == true ? "" : "_relay"}';
} else if (secure == false && direct == false) { return Image.asset('assets/$icon.png', width: 48, height: 48);
icon = 'insecure_relay';
} else if (secure == true && direct == false) {
icon = 'secure_relay';
} }
return icon == null
? null
: Image.asset('assets/$icon.png', width: 48, height: 48);
} }
void clearPermissions() { void clearPermissions() {
@ -130,7 +132,8 @@ class FfiModel with ChangeNotifier {
} else if (name == 'peer_info') { } else if (name == 'peer_info') {
handlePeerInfo(evt, peerId); handlePeerInfo(evt, peerId);
} else if (name == 'connection_ready') { } else if (name == 'connection_ready') {
setConnectionType(evt['secure'] == 'true', evt['direct'] == 'true'); setConnectionType(
peerId, evt['secure'] == 'true', evt['direct'] == 'true');
} else if (name == 'switch_display') { } else if (name == 'switch_display') {
handleSwitchDisplay(evt); handleSwitchDisplay(evt);
} else if (name == 'cursor_data') { } else if (name == 'cursor_data') {
@ -189,7 +192,7 @@ class FfiModel with ChangeNotifier {
handlePeerInfo(evt, peerId); handlePeerInfo(evt, peerId);
} else if (name == 'connection_ready') { } else if (name == 'connection_ready') {
parent.target?.ffiModel.setConnectionType( parent.target?.ffiModel.setConnectionType(
evt['secure'] == 'true', evt['direct'] == 'true'); peerId, evt['secure'] == 'true', evt['direct'] == 'true');
} else if (name == 'switch_display') { } else if (name == 'switch_display') {
handleSwitchDisplay(evt); handleSwitchDisplay(evt);
} else if (name == 'cursor_data') { } else if (name == 'cursor_data') {