diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index 6027fb8de..b991c7a96 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -743,39 +743,3 @@ Future>? matchPeers(String searchText, List peers) async { } 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(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(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(tag: tag(id)); -} diff --git a/flutter/lib/common/shared_state.dart b/flutter/lib/common/shared_state.dart new file mode 100644 index 000000000..7232cb6ad --- /dev/null +++ b/flutter/lib/common/shared_state.dart @@ -0,0 +1,87 @@ +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(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(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(tag: tag(id)); +} + +class ConnectionType { + final Rx _secure = kInvalidValueStr.obs; + final Rx _direct = kInvalidValueStr.obs; + + Rx get secure => _secure; + Rx get direct => _direct; + + static String get strSecure => 'secure'; + static String get strInsecure => 'insecure'; + static String get strDirect => ''; + static String get strIndirect => '_relay'; + + void setSecure(bool v) { + _secure.value = v ? strSecure : strInsecure; + } + + void setDirect(bool v) { + _direct.value = v ? strDirect : strIndirect; + } + + 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 key = tag(id); + if (!Get.isRegistered(tag: key)) { + final ConnectionType collectionType = ConnectionType(); + Get.put(collectionType, tag: key); + } + } + + static void delete(String id) { + final key = tag(id); + if (Get.isRegistered(tag: key)) { + Get.delete(tag: key); + } + } + + static ConnectionType find(String id) => + Get.find(tag: tag(id)); +} diff --git a/flutter/lib/consts.dart b/flutter/lib/consts.dart index 3f0abd43f..6c67e2ab9 100644 --- a/flutter/lib/consts.dart +++ b/flutter/lib/consts.dart @@ -10,3 +10,5 @@ const String kTabLabelSettingPage = "Settings"; const int kDefaultDisplayWidth = 1280; const int kDefaultDisplayHeight = 720; + +const kInvalidValueStr = "InvalidValueStr"; diff --git a/flutter/lib/desktop/pages/connection_tab_page.dart b/flutter/lib/desktop/pages/connection_tab_page.dart index 1d00cdc8a..1b9e04ebf 100644 --- a/flutter/lib/desktop/pages/connection_tab_page.dart +++ b/flutter/lib/desktop/pages/connection_tab_page.dart @@ -3,6 +3,7 @@ import 'dart:convert'; import 'package:desktop_multi_window/desktop_multi_window.dart'; import 'package:flutter/material.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/desktop/pages/remote_page.dart'; import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart'; @@ -20,22 +21,24 @@ class ConnectionTabPage extends StatefulWidget { class _ConnectionTabPageState extends State { final tabController = Get.put(DesktopTabController()); - static final IconData selectedIcon = Icons.desktop_windows_sharp; - static final IconData unselectedIcon = Icons.desktop_windows_outlined; + static const IconData selectedIcon = Icons.desktop_windows_sharp; + static const IconData unselectedIcon = Icons.desktop_windows_outlined; var connectionMap = RxList.empty(growable: true); _ConnectionTabPageState(Map params) { final RxBool fullscreen = Get.find(tag: 'fullscreen'); - if (params['id'] != null) { + final peerId = params['id']; + if (peerId != null) { + ConnectionTypeState.init(peerId); tabController.add(TabInfo( - key: params['id'], - label: params['id'], + key: peerId, + label: peerId, selectedIcon: selectedIcon, unselectedIcon: unselectedIcon, page: Obx(() => RemotePage( - key: ValueKey(params['id']), - id: params['id'], + key: ValueKey(peerId), + id: peerId, tabBarHeight: fullscreen.isTrue ? 0 : kDesktopRemoteTabBarHeight, )))); @@ -103,6 +106,44 @@ class _ConnectionTabPageState extends State { .setFullscreen(fullscreen.isTrue); return pageView; }, + tabBuilder: (key, icon, label, themeConf) => Obx(() { + final connectionType = ConnectionTypeState.find(key); + if (!ConnectionTypeState.find(key).isValid()) { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + icon, + label, + ], + ); + } else { + final msgDirect = translate( + connectionType.direct.value == + ConnectionType.strDirect + ? 'Direct Connection' + : 'Relay Connection'); + final msgSecure = translate( + connectionType.secure.value == + ConnectionType.strSecure + ? 'Secure Connection' + : 'Insecure Connection'); + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + icon, + Tooltip( + message: '$msgDirect\n$msgSecure', + child: Image.asset( + 'assets/${connectionType.secure.value}${connectionType.direct.value}.png', + width: themeConf.iconSize, + height: themeConf.iconSize, + ).paddingOnly(right: 5), + ), + label, + ], + ); + } + }), ))), ), )); @@ -112,6 +153,7 @@ class _ConnectionTabPageState extends State { if (tabController.state.value.tabs.isEmpty) { WindowController.fromWindowId(windowId()).hide(); } + ConnectionTypeState.delete(id); } int windowId() { diff --git a/flutter/lib/desktop/pages/remote_page.dart b/flutter/lib/desktop/pages/remote_page.dart index fbba5bafe..f723b17d0 100644 --- a/flutter/lib/desktop/pages/remote_page.dart +++ b/flutter/lib/desktop/pages/remote_page.dart @@ -5,11 +5,9 @@ import 'dart:ui' as ui; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:flutter_hbb/models/chat_model.dart'; import 'package:get/get.dart'; import 'package:provider/provider.dart'; import 'package:wakelock/wakelock.dart'; -import 'package:tuple/tuple.dart'; // import 'package:window_manager/window_manager.dart'; @@ -19,6 +17,8 @@ import '../../mobile/widgets/dialog.dart'; import '../../mobile/widgets/overlay.dart'; import '../../models/model.dart'; import '../../models/platform_model.dart'; +import '../../models/chat_model.dart'; +import '../../common/shared_state.dart'; final initText = '\1' * 1024; @@ -41,7 +41,7 @@ class _RemotePageState extends State Timer? _timer; bool _showBar = !isWebDesktop; String _value = ''; - var _cursorOverImage = false.obs; + final _cursorOverImage = false.obs; final FocusNode _mobileFocusNode = FocusNode(); final FocusNode _physicalFocusNode = FocusNode(); @@ -54,6 +54,18 @@ class _RemotePageState extends State _ffi.canvasModel.tabBarHeight = widget.tabBarHeight; } + void _initStates(String id) { + PrivacyModeState.init(id); + BlockInputState.init(id); + CurrentDisplayState.init(id); + } + + void _removeStates(String id) { + PrivacyModeState.delete(id); + BlockInputState.delete(id); + CurrentDisplayState.delete(id); + } + @override void initState() { super.initState(); @@ -74,14 +86,12 @@ class _RemotePageState extends State _ffi.listenToMouse(true); _ffi.qualityMonitorModel.checkShowQualityMonitor(widget.id); // WindowManager.instance.addListener(this); - PrivacyModeState.init(widget.id); - BlockInputState.init(widget.id); - CurrentDisplayState.init(widget.id); + _initStates(widget.id); } @override void dispose() { - print("REMOTE PAGE dispose ${widget.id}"); + debugPrint("REMOTE PAGE dispose ${widget.id}"); hideMobileActionsOverlay(); _ffi.listenToMouse(false); _mobileFocusNode.dispose(); @@ -97,9 +107,7 @@ class _RemotePageState extends State // WindowManager.instance.removeListener(this); Get.delete(tag: widget.id); super.dispose(); - PrivacyModeState.delete(widget.id); - BlockInputState.delete(widget.id); - CurrentDisplayState.delete(widget.id); + _removeStates(widget.id); } void resetTool() { diff --git a/flutter/lib/desktop/widgets/popup_menu.dart b/flutter/lib/desktop/widgets/popup_menu.dart index acb8f184c..3d5fdf7f6 100644 --- a/flutter/lib/desktop/widgets/popup_menu.dart +++ b/flutter/lib/desktop/widgets/popup_menu.dart @@ -4,12 +4,10 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:tuple/tuple.dart'; -import './material_mod_popup_menu.dart' as modMenu; - -const kInvalidValueStr = "InvalidValueStr"; +import './material_mod_popup_menu.dart' as mod_menu; // https://stackoverflow.com/questions/68318314/flutter-popup-menu-inside-popup-menu -class PopupMenuChildrenItem extends modMenu.PopupMenuEntry { +class PopupMenuChildrenItem extends mod_menu.PopupMenuEntry { const PopupMenuChildrenItem({ key, this.height = kMinInteractiveDimension, @@ -17,19 +15,19 @@ class PopupMenuChildrenItem extends modMenu.PopupMenuEntry { this.enable = true, this.textStyle, this.onTap, - this.position = modMenu.PopupMenuPosition.overSide, + this.position = mod_menu.PopupMenuPosition.overSide, this.offset = Offset.zero, required this.itemBuilder, required this.child, }) : super(key: key); - final modMenu.PopupMenuPosition position; + final mod_menu.PopupMenuPosition position; final Offset offset; final TextStyle? textStyle; final EdgeInsets? padding; final bool enable; final void Function()? onTap; - final List> Function(BuildContext) itemBuilder; + final List> Function(BuildContext) itemBuilder; final Widget child; @override @@ -59,7 +57,7 @@ class MyPopupMenuItemState> popupMenuTheme.textStyle ?? theme.textTheme.subtitle1!; - return modMenu.PopupMenuButton( + return mod_menu.PopupMenuButton( enabled: widget.enable, position: widget.position, offset: widget.offset, @@ -88,22 +86,29 @@ class MenuConfig { static const iconWidth = 12.0; static const iconHeight = 12.0; - final double secondMenuHeight; + final double height; + final double dividerHeight; final Color commonColor; const MenuConfig( {required this.commonColor, - this.secondMenuHeight = kMinInteractiveDimension}); + this.height = kMinInteractiveDimension, + this.dividerHeight = 16.0}); } abstract class MenuEntryBase { - modMenu.PopupMenuEntry build(BuildContext context, MenuConfig conf); + List> build(BuildContext context, MenuConfig conf); } class MenuEntryDivider extends MenuEntryBase { @override - modMenu.PopupMenuEntry build(BuildContext context, MenuConfig conf) { - return const modMenu.PopupMenuDivider(); + List> build( + BuildContext context, MenuConfig conf) { + return [ + mod_menu.PopupMenuDivider( + height: conf.dividerHeight, + ) + ]; } } @@ -111,6 +116,85 @@ typedef RadioOptionsGetter = List> Function(); typedef RadioCurOptionGetter = Future Function(); typedef RadioOptionSetter = Future Function(String); +class MenuEntryRadioUtils {} + +class MenuEntryRadios extends MenuEntryBase { + final String text; + final RadioOptionsGetter optionsGetter; + final RadioCurOptionGetter curOptionGetter; + final RadioOptionSetter optionSetter; + final RxString _curOption = "".obs; + + MenuEntryRadios( + {required this.text, + required this.optionsGetter, + required this.curOptionGetter, + required this.optionSetter}) { + () async { + _curOption.value = await curOptionGetter(); + }(); + } + + List> get options => optionsGetter(); + RxString get curOption => _curOption; + setOption(String option) async { + await optionSetter(option); + final opt = await curOptionGetter(); + if (_curOption.value != opt) { + _curOption.value = opt; + } + } + + mod_menu.PopupMenuEntry _buildMenuItem( + BuildContext context, MenuConfig conf, Tuple2 opt) { + return mod_menu.PopupMenuItem( + padding: EdgeInsets.zero, + height: conf.height, + child: TextButton( + child: Container( + alignment: AlignmentDirectional.centerStart, + constraints: BoxConstraints(minHeight: conf.height), + child: Row( + children: [ + Text( + opt.item1, + style: const TextStyle( + color: Colors.black, + fontSize: MenuConfig.fontSize, + fontWeight: FontWeight.normal), + ), + Expanded( + child: Align( + alignment: Alignment.centerRight, + child: SizedBox( + width: 20.0, + height: 20.0, + child: Obx(() => opt.item2 == curOption.value + ? Icon( + Icons.check, + color: conf.commonColor, + ) + : const SizedBox.shrink())), + )), + ], + ), + ), + onPressed: () { + if (opt.item2 != curOption.value) { + setOption(opt.item2); + } + }, + ), + ); + } + + @override + List> build( + BuildContext context, MenuConfig conf) { + return options.map((opt) => _buildMenuItem(context, conf, opt)).toList(); + } +} + class MenuEntrySubRadios extends MenuEntryBase { final String text; final RadioOptionsGetter optionsGetter; @@ -138,33 +222,37 @@ class MenuEntrySubRadios extends MenuEntryBase { } } - modMenu.PopupMenuEntry _buildSecondMenu( + mod_menu.PopupMenuEntry _buildSecondMenu( BuildContext context, MenuConfig conf, Tuple2 opt) { - return modMenu.PopupMenuItem( + return mod_menu.PopupMenuItem( padding: EdgeInsets.zero, + height: conf.height, child: TextButton( child: Container( alignment: AlignmentDirectional.centerStart, - constraints: BoxConstraints(minHeight: conf.secondMenuHeight), + constraints: BoxConstraints(minHeight: conf.height), child: Row( children: [ - SizedBox( - width: 20.0, - height: 20.0, - child: Obx(() => opt.item2 == curOption.value - ? Icon( - Icons.check, - color: conf.commonColor, - ) - : SizedBox.shrink())), - const SizedBox(width: MenuConfig.midPadding), Text( opt.item1, style: const TextStyle( color: Colors.black, fontSize: MenuConfig.fontSize, fontWeight: FontWeight.normal), - ) + ), + Expanded( + child: Align( + alignment: Alignment.centerRight, + child: SizedBox( + width: 20.0, + height: 20.0, + child: Obx(() => opt.item2 == curOption.value + ? Icon( + Icons.check, + color: conf.commonColor, + ) + : const SizedBox.shrink())), + )), ], ), ), @@ -178,31 +266,34 @@ class MenuEntrySubRadios extends MenuEntryBase { } @override - modMenu.PopupMenuEntry build(BuildContext context, MenuConfig conf) { - return PopupMenuChildrenItem( - height: conf.secondMenuHeight, - padding: EdgeInsets.zero, - itemBuilder: (BuildContext context) => - options.map((opt) => _buildSecondMenu(context, conf, opt)).toList(), - child: Row(children: [ - const SizedBox(width: MenuConfig.midPadding), - Text( - text, - style: const TextStyle( - color: Colors.black, - fontSize: MenuConfig.fontSize, - fontWeight: FontWeight.normal), - ), - Expanded( - child: Align( - alignment: Alignment.centerRight, - child: Icon( - Icons.keyboard_arrow_right, - color: conf.commonColor, + List> build( + BuildContext context, MenuConfig conf) { + return [ + PopupMenuChildrenItem( + padding: EdgeInsets.zero, + height: conf.height, + itemBuilder: (BuildContext context) => + options.map((opt) => _buildSecondMenu(context, conf, opt)).toList(), + child: Row(children: [ + const SizedBox(width: MenuConfig.midPadding), + Text( + text, + style: const TextStyle( + color: Colors.black, + fontSize: MenuConfig.fontSize, + fontWeight: FontWeight.normal), ), - )) - ]), - ); + Expanded( + child: Align( + alignment: Alignment.centerRight, + child: Icon( + Icons.keyboard_arrow_right, + color: conf.commonColor, + ), + )) + ]), + ) + ]; } } @@ -218,34 +309,38 @@ abstract class MenuEntrySwitchBase extends MenuEntryBase { Future setOption(bool option); @override - modMenu.PopupMenuEntry build(BuildContext context, MenuConfig conf) { - return modMenu.PopupMenuItem( - padding: EdgeInsets.zero, - child: Obx( - () => SwitchListTile( - value: curOption.value, - onChanged: (v) { - setOption(v); - }, - title: Container( - alignment: AlignmentDirectional.centerStart, - constraints: BoxConstraints(minHeight: conf.secondMenuHeight), - child: Text( - text, - style: const TextStyle( - color: Colors.black, - fontSize: MenuConfig.fontSize, - fontWeight: FontWeight.normal), - )), - dense: true, - visualDensity: const VisualDensity( - horizontal: VisualDensity.minimumDensity, - vertical: VisualDensity.minimumDensity, + List> build( + BuildContext context, MenuConfig conf) { + return [ + mod_menu.PopupMenuItem( + padding: EdgeInsets.zero, + height: conf.height, + child: Obx( + () => SwitchListTile( + value: curOption.value, + onChanged: (v) { + setOption(v); + }, + title: Container( + alignment: AlignmentDirectional.centerStart, + constraints: BoxConstraints(minHeight: conf.height), + child: Text( + text, + style: const TextStyle( + color: Colors.black, + fontSize: MenuConfig.fontSize, + fontWeight: FontWeight.normal), + )), + dense: true, + visualDensity: const VisualDensity( + horizontal: VisualDensity.minimumDensity, + vertical: VisualDensity.minimumDensity, + ), + contentPadding: const EdgeInsets.only(left: 8.0), ), - contentPadding: EdgeInsets.only(left: 8.0), ), - ), - ); + ) + ]; } } @@ -303,32 +398,37 @@ class MenuEntrySubMenu extends MenuEntryBase { }); @override - modMenu.PopupMenuEntry build(BuildContext context, MenuConfig conf) { - return PopupMenuChildrenItem( - height: conf.secondMenuHeight, - padding: EdgeInsets.zero, - position: modMenu.PopupMenuPosition.overSide, - itemBuilder: (BuildContext context) => - entries.map((entry) => entry.build(context, conf)).toList(), - child: Row(children: [ - const SizedBox(width: MenuConfig.midPadding), - Text( - text, - style: const TextStyle( - color: Colors.black, - fontSize: MenuConfig.fontSize, - fontWeight: FontWeight.normal), - ), - Expanded( - child: Align( - alignment: Alignment.centerRight, - child: Icon( - Icons.keyboard_arrow_right, - color: conf.commonColor, + List> build( + BuildContext context, MenuConfig conf) { + return [ + PopupMenuChildrenItem( + height: conf.height, + padding: EdgeInsets.zero, + position: mod_menu.PopupMenuPosition.overSide, + itemBuilder: (BuildContext context) => entries + .map((entry) => entry.build(context, conf)) + .expand((i) => i) + .toList(), + child: Row(children: [ + const SizedBox(width: MenuConfig.midPadding), + Text( + text, + style: const TextStyle( + color: Colors.black, + fontSize: MenuConfig.fontSize, + fontWeight: FontWeight.normal), ), - )) - ]), - ); + Expanded( + child: Align( + alignment: Alignment.centerRight, + child: Icon( + Icons.keyboard_arrow_right, + color: conf.commonColor, + ), + )) + ]), + ) + ]; } } @@ -342,34 +442,27 @@ class MenuEntryButton extends MenuEntryBase { }); @override - modMenu.PopupMenuEntry build(BuildContext context, MenuConfig conf) { - return modMenu.PopupMenuItem( - padding: EdgeInsets.zero, - child: TextButton( - child: Container( - alignment: AlignmentDirectional.centerStart, - constraints: BoxConstraints(minHeight: conf.secondMenuHeight), - child: childBuilder( - const TextStyle( - color: Colors.black, - fontSize: MenuConfig.fontSize, - fontWeight: FontWeight.normal), - )), - onPressed: () { - proc(); - }, - ), - ); - } -} - -class CustomMenu { - final List> entries; - final MenuConfig conf; - - const CustomMenu({required this.entries, required this.conf}); - - List> build(BuildContext context) { - return entries.map((entry) => entry.build(context, conf)).toList(); + List> build( + BuildContext context, MenuConfig conf) { + return [ + mod_menu.PopupMenuItem( + padding: EdgeInsets.zero, + height: conf.height, + child: TextButton( + child: Container( + alignment: AlignmentDirectional.centerStart, + constraints: BoxConstraints(minHeight: conf.height), + child: childBuilder( + const TextStyle( + color: Colors.black, + fontSize: MenuConfig.fontSize, + fontWeight: FontWeight.normal), + )), + onPressed: () { + proc(); + }, + ), + ) + ]; } } diff --git a/flutter/lib/desktop/widgets/remote_menubar.dart b/flutter/lib/desktop/widgets/remote_menubar.dart index 011525ba9..0e931dd71 100644 --- a/flutter/lib/desktop/widgets/remote_menubar.dart +++ b/flutter/lib/desktop/widgets/remote_menubar.dart @@ -9,12 +9,15 @@ import '../../mobile/widgets/dialog.dart'; import '../../mobile/widgets/overlay.dart'; import '../../models/model.dart'; import '../../models/platform_model.dart'; +import '../../common/shared_state.dart'; import './popup_menu.dart'; import './material_mod_popup_menu.dart' as mod_menu; class _MenubarTheme { 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 { @@ -168,11 +171,9 @@ class _RemoteMenubarState extends State { ), itemBuilder: (BuildContext context) { final List rowChildren = []; - const double selectorScale = 1.3; for (int i = 0; i < pi.displays.length; i++) { - rowChildren.add(Transform.scale( - scale: selectorScale, - child: Stack( + rowChildren.add( + Stack( alignment: Alignment.center, children: [ const Icon( @@ -203,7 +204,7 @@ class _RemoteMenubarState extends State { ) ], ), - )); + ); } return >[ mod_menu.PopupMenuItem( @@ -232,8 +233,10 @@ class _RemoteMenubarState extends State { context, const MenuConfig( commonColor: _MenubarTheme.commonColor, - secondMenuHeight: _MenubarTheme.height, + height: _MenubarTheme.height, + dividerHeight: _MenubarTheme.dividerHeight, ))) + .expand((i) => i) .toList(), ); } @@ -253,8 +256,10 @@ class _RemoteMenubarState extends State { context, const MenuConfig( commonColor: _MenubarTheme.commonColor, - secondMenuHeight: _MenubarTheme.height, + height: _MenubarTheme.height, + dividerHeight: _MenubarTheme.dividerHeight, ))) + .expand((i) => i) .toList(), ); } @@ -398,7 +403,7 @@ class _RemoteMenubarState extends State { List> _getDisplayMenu() { final displayMenu = [ - MenuEntrySubRadios( + MenuEntryRadios( text: translate('Ratio'), optionsGetter: () => [ Tuple2(translate('Original'), 'original'), @@ -415,7 +420,8 @@ class _RemoteMenubarState extends State { id: widget.id, name: "view-style", value: v); widget.ffi.canvasModel.updateViewStyle(); }), - MenuEntrySubRadios( + MenuEntryDivider(), + MenuEntryRadios( text: translate('Scroll Style'), optionsGetter: () => [ Tuple2(translate('ScrollAuto'), 'scrollauto'), @@ -431,7 +437,8 @@ class _RemoteMenubarState extends State { id: widget.id, name: "scroll-style", value: v); widget.ffi.canvasModel.updateScrollStyle(); }), - MenuEntrySubRadios( + MenuEntryDivider(), + MenuEntryRadios( text: translate('Image Quality'), optionsGetter: () => [ Tuple2(translate('Good image quality'), 'best'), @@ -448,6 +455,7 @@ class _RemoteMenubarState extends State { optionSetter: (String v) async { await bind.sessionSetImageQuality(id: widget.id, value: v); }), + MenuEntryDivider(), MenuEntrySwitch( text: translate('Show remote cursor'), getter: () async { diff --git a/flutter/lib/desktop/widgets/tabbar_widget.dart b/flutter/lib/desktop/widgets/tabbar_widget.dart index 8ef082b49..6e0ce747d 100644 --- a/flutter/lib/desktop/widgets/tabbar_widget.dart +++ b/flutter/lib/desktop/widgets/tabbar_widget.dart @@ -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 Function(String key); + class DesktopTab extends StatelessWidget { final Function(String)? onTabClose; final TarBarTheme theme; @@ -134,24 +144,29 @@ class DesktopTab extends StatelessWidget { final Widget Function(Widget pageView)? pageViewBuilder; final Widget? tail; final VoidCallback? onClose; + final TabBuilder? tabBuilder; + final LabelGetter? labelGetter; final DesktopTabController controller; Rx get state => controller.state; - const DesktopTab( - {required this.controller, - required this.isMainWindow, - this.theme = const TarBarTheme.light(), - this.onTabClose, - this.showTabBar = true, - this.showLogo = true, - this.showTitle = true, - this.showMinimize = true, - this.showMaximize = true, - this.showClose = true, - this.pageViewBuilder, - this.tail, - this.onClose}); + const DesktopTab({ + required this.controller, + required this.isMainWindow, + this.theme = const TarBarTheme.light(), + this.onTabClose, + this.showTabBar = true, + this.showLogo = true, + this.showTitle = true, + this.showMinimize = true, + this.showMaximize = true, + this.showClose = true, + this.pageViewBuilder, + this.tail, + this.onClose, + this.tabBuilder, + this.labelGetter, + }); @override Widget build(BuildContext context) { @@ -194,8 +209,10 @@ class DesktopTab extends StatelessWidget { child: Row( children: [ Offstage( - offstage: !Platform.isMacOS, - child: const SizedBox(width: 78,)), + offstage: !Platform.isMacOS, + child: const SizedBox( + width: 78, + )), Row(children: [ Offstage( offstage: !showLogo, @@ -228,6 +245,8 @@ class DesktopTab extends StatelessWidget { controller: controller, onTabClose: onTabClose, theme: theme, + tabBuilder: tabBuilder, + labelGetter: labelGetter, )), ), ], @@ -356,10 +375,18 @@ class _ListView extends StatelessWidget { final DesktopTabController controller; final Function(String key)? onTabClose; final TarBarTheme theme; + + final TabBuilder? tabBuilder; + final LabelGetter? labelGetter; + Rx get state => controller.state; - const _ListView( - {required this.controller, required this.onTabClose, required this.theme}); + _ListView( + {required this.controller, + required this.onTabClose, + required this.theme, + this.tabBuilder, + this.labelGetter}); @override Widget build(BuildContext context) { @@ -373,7 +400,9 @@ class _ListView extends StatelessWidget { final tab = e.value; return _Tab( index: index, - label: tab.label, + label: labelGetter == null + ? Rx(tab.label) + : labelGetter!(tab.label), selectedIcon: tab.selectedIcon, unselectedIcon: tab.unselectedIcon, closable: tab.closable, @@ -381,6 +410,16 @@ class _ListView extends StatelessWidget { onClose: () => controller.remove(index), onSelected: () => controller.jumpTo(index), theme: theme, + tabBuilder: tabBuilder == null + ? null + : (Widget icon, Widget labelWidget, TabThemeConf themeConf) { + return tabBuilder!( + tab.label, + icon, + labelWidget, + themeConf, + ); + }, ); }).toList())); } @@ -388,7 +427,7 @@ class _ListView extends StatelessWidget { class _Tab extends StatelessWidget { late final int index; - late final String label; + late final Rx label; late final IconData? selectedIcon; late final IconData? unselectedIcon; late final bool closable; @@ -397,6 +436,8 @@ class _Tab extends StatelessWidget { late final Function() onSelected; final RxBool _hover = false.obs; late final TarBarTheme theme; + final Widget Function(Widget icon, Widget label, TabThemeConf themeConf)? + tabBuilder; _Tab( {Key? key, @@ -404,6 +445,7 @@ class _Tab extends StatelessWidget { required this.label, this.selectedIcon, this.unselectedIcon, + this.tabBuilder, required this.closable, required this.selected, required this.onClose, @@ -411,11 +453,49 @@ class _Tab extends StatelessWidget { required this.theme}) : 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 Widget build(BuildContext context) { - bool show_icon = selectedIcon != null && unselectedIcon != null; - bool is_selected = index == selected; - bool show_divider = index != selected - 1 && index != selected; + bool showIcon = selectedIcon != null && unselectedIcon != null; + bool isSelected = index == selected; + bool showDivider = index != selected - 1 && index != selected; return Ink( child: InkWell( onHover: (hover) => _hover.value = hover, @@ -427,40 +507,19 @@ class _Tab extends StatelessWidget { child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ - Row( - 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), - ), - ], - ), + _buildTabContent(), Offstage( offstage: !closable, child: Obx((() => _CloseButton( visiable: _hover.value, - tabSelected: is_selected, + tabSelected: isSelected, onClose: () => onClose(), theme: theme, ))), ) ])).paddingSymmetric(horizontal: 10), Offstage( - offstage: !show_divider, + offstage: !showDivider, child: VerticalDivider( width: 1, indent: _kDividerIndent, diff --git a/flutter/lib/main.dart b/flutter/lib/main.dart index da3b07567..efca88180 100644 --- a/flutter/lib/main.dart +++ b/flutter/lib/main.dart @@ -202,7 +202,7 @@ class App extends StatelessWidget { title: 'RustDesk', theme: getCurrentTheme(), home: isDesktop - ? DesktopTabPage() + ? const DesktopTabPage() : !isAndroid ? WebHomePage() : HomePage(), diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index 70e922bce..171a41dfa 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -17,6 +17,7 @@ import 'package:shared_preferences/shared_preferences.dart'; import 'package:tuple/tuple.dart'; import '../common.dart'; +import '../common/shared_state.dart'; import '../mobile/widgets/dialog.dart'; import '../mobile/widgets/overlay.dart'; import 'peer_model.dart'; @@ -96,25 +97,26 @@ class FfiModel with ChangeNotifier { clearPermissions(); } - void setConnectionType(bool secure, bool direct) { + void setConnectionType(String peerId, bool secure, bool direct) { _secure = secure; _direct = direct; + try { + var connectionType = ConnectionTypeState.find(peerId); + connectionType.setSecure(secure); + connectionType.setDirect(direct); + } catch (e) { + // + } } Image? getConnectionImage() { - String? icon; - if (secure == true && direct == true) { - icon = 'secure'; - } else if (secure == false && direct == true) { - icon = 'insecure'; - } else if (secure == false && direct == false) { - icon = 'insecure_relay'; - } else if (secure == true && direct == false) { - icon = 'secure_relay'; + if (secure == null || direct == null) { + return null; + } else { + final icon = + '${secure == true ? "secure" : "insecure"}${direct == true ? "" : "_relay"}'; + return Image.asset('assets/$icon.png', width: 48, height: 48); } - return icon == null - ? null - : Image.asset('assets/$icon.png', width: 48, height: 48); } void clearPermissions() { @@ -130,7 +132,8 @@ class FfiModel with ChangeNotifier { } else if (name == 'peer_info') { handlePeerInfo(evt, peerId); } 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') { handleSwitchDisplay(evt); } else if (name == 'cursor_data') { @@ -189,7 +192,7 @@ class FfiModel with ChangeNotifier { handlePeerInfo(evt, peerId); } else if (name == 'connection_ready') { parent.target?.ffiModel.setConnectionType( - evt['secure'] == 'true', evt['direct'] == 'true'); + peerId, evt['secure'] == 'true', evt['direct'] == 'true'); } else if (name == 'switch_display') { handleSwitchDisplay(evt); } else if (name == 'cursor_data') { @@ -1067,8 +1070,10 @@ class FFI { imageModel._id = id; cursorModel.id = id; } - final stream = bind.sessionConnect( + // ignore: unused_local_variable + final addRes = bind.sessionAddSync( id: id, isFileTransfer: isFileTransfer, isPortForward: isPortForward); + final stream = bind.sessionStart(id: id); final cb = ffiModel.startEventListener(id); () async { await for (final message in stream) { diff --git a/flutter/pubspec.yaml b/flutter/pubspec.yaml index fc59b8bd3..a35f1c872 100644 --- a/flutter/pubspec.yaml +++ b/flutter/pubspec.yaml @@ -40,10 +40,7 @@ dependencies: url_launcher: ^6.0.9 shared_preferences: ^2.0.6 toggle_switch: ^1.4.0 - dash_chat_2: - git: - url: https://github.com/fufesou/Dash-Chat-2 - ref: feat_maxWidth + dash_chat_2: ^0.0.14 draggable_float_widget: ^0.0.2 settings_ui: ^2.0.2 flutter_breadcrumb: ^1.0.1 diff --git a/src/client.rs b/src/client.rs index d7f0bf4fa..0bc69a7c1 100644 --- a/src/client.rs +++ b/src/client.rs @@ -847,7 +847,7 @@ impl VideoHandler { pub struct LoginConfigHandler { id: String, pub is_file_transfer: bool, - is_port_forward: bool, + pub is_port_forward: bool, hash: Hash, password: Vec, // remember password for reconnect pub remember: bool, diff --git a/src/flutter.rs b/src/flutter.rs index 7192c0fdf..60650aa9b 100644 --- a/src/flutter.rs +++ b/src/flutter.rs @@ -8,16 +8,13 @@ use std::{ use flutter_rust_bridge::{StreamSink, ZeroCopyBuffer}; -use hbb_common::config::{PeerConfig, TransferSerde}; -use hbb_common::fs::get_job; use hbb_common::{ - allow_err, + allow_err, bail, compress::decompress, - config::{Config, LocalConfig}, - fs, + config::{Config, LocalConfig, PeerConfig, TransferSerde}, fs::{ - can_enable_overwrite_detection, get_string, new_send_confirm, transform_windows_path, - DigestCheckResult, + self, can_enable_overwrite_detection, get_job, get_string, new_send_confirm, + transform_windows_path, DigestCheckResult, }, log, message_proto::*, @@ -28,7 +25,7 @@ use hbb_common::{ sync::mpsc, time::{self, Duration, Instant, Interval}, }, - Stream, + ResultType, Stream, }; use crate::common::{self, make_fd_to_json, CLIPBOARD_INTERVAL}; @@ -60,7 +57,7 @@ pub struct Session { id: String, sender: Arc>>>, // UI to rust lc: Arc>, - events2ui: Arc>>, + events2ui: Arc>>>, } impl Session { @@ -71,23 +68,17 @@ impl Session { /// * `id` - The identifier of the remote session with prefix. Regex: [\w]*[\_]*[\d]+ /// * `is_file_transfer` - If the session is used for file transfer. /// * `is_port_forward` - If the session is used for port forward. - pub fn start( - identifier: &str, - is_file_transfer: bool, - is_port_forward: bool, - events2ui: StreamSink, - ) { + pub fn add(id: &str, is_file_transfer: bool, is_port_forward: bool) -> ResultType<()> { // TODO check same id - let session_id = get_session_id(identifier.to_owned()); + let session_id = get_session_id(id.to_owned()); LocalConfig::set_remote_id(&session_id); // TODO close // Self::close(); - let events2ui = Arc::new(RwLock::new(events2ui)); let session = Session { id: session_id.clone(), sender: Default::default(), lc: Default::default(), - events2ui, + events2ui: Arc::new(RwLock::new(None)), }; session.lc.write().unwrap().initialize( session_id.clone(), @@ -97,10 +88,29 @@ impl Session { SESSIONS .write() .unwrap() - .insert(identifier.to_owned(), session.clone()); - std::thread::spawn(move || { - Connection::start(session, is_file_transfer, is_port_forward); - }); + .insert(id.to_owned(), session.clone()); + Ok(()) + } + + /// Create a new remote session with the given id. + /// + /// # Arguments + /// + /// * `id` - The identifier of the remote session with prefix. Regex: [\w]*[\_]*[\d]+ + /// * `events2ui` - The events channel to ui. + pub fn start(id: &str, events2ui: StreamSink) -> ResultType<()> { + if let Some(session) = SESSIONS.write().unwrap().get_mut(id) { + *session.events2ui.write().unwrap() = Some(events2ui); + let session = session.clone(); + std::thread::spawn(move || { + let is_file_transfer = session.lc.read().unwrap().is_file_transfer; + let is_port_forward = session.lc.read().unwrap().is_port_forward; + Connection::start(session, is_file_transfer, is_port_forward); + }); + Ok(()) + } else { + bail!("No session with peer id {}", id) + } } /// Get the current session instance. @@ -305,7 +315,9 @@ impl Session { assert!(h.get("name").is_none()); h.insert("name", name); let out = serde_json::ser::to_string(&h).unwrap_or("".to_owned()); - self.events2ui.read().unwrap().add(EventToUI::Event(out)); + if let Some(stream) = &*self.events2ui.read().unwrap() { + stream.add(EventToUI::Event(out)); + } } /// Get platform of peer. @@ -998,11 +1010,12 @@ impl Connection { }) }; if let Ok(true) = self.video_handler.handle_frame(vf) { - let stream = self.session.events2ui.read().unwrap(); - self.frame_count.fetch_add(1, Ordering::Relaxed); - stream.add(EventToUI::Rgba(ZeroCopyBuffer( - self.video_handler.rgb.clone(), - ))); + if let Some(stream) = &*self.session.events2ui.read().unwrap() { + self.frame_count.fetch_add(1, Ordering::Relaxed); + stream.add(EventToUI::Rgba(ZeroCopyBuffer( + self.video_handler.rgb.clone(), + ))); + } } } Some(message::Union::Hash(hash)) => { diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index dd147bb77..db8030782 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -107,14 +107,18 @@ pub fn host_stop_system_key_propagate(stopped: bool) { crate::platform::windows::stop_system_key_propagate(stopped); } -pub fn session_connect( - events2ui: StreamSink, - id: String, - is_file_transfer: bool, - is_port_forward: bool, -) -> ResultType<()> { - Session::start(&id, is_file_transfer, is_port_forward, events2ui); - Ok(()) +// FIXME: -> ResultType<()> cannot be parsed by frb_codegen +// thread 'main' panicked at 'Failed to parse function output type `ResultType<()>`', $HOME\.cargo\git\checkouts\flutter_rust_bridge-ddba876d3ebb2a1e\e5adce5\frb_codegen\src\parser\mod.rs:151:25 +pub fn session_add_sync(id: String, is_file_transfer: bool, is_port_forward: bool) -> SyncReturn { + if let Err(e) = Session::add(&id, is_file_transfer, is_port_forward) { + SyncReturn(format!("Failed to add session with id {}, {}", &id, e)) + } else { + SyncReturn("".to_owned()) + } +} + +pub fn session_start(events2ui: StreamSink, id: String) -> ResultType<()> { + Session::start(&id, events2ui) } pub fn session_get_remember(id: String) -> Option { @@ -602,7 +606,12 @@ pub fn main_load_lan_peers() { }; } -pub fn session_add_port_forward(id: String, local_port: i32, remote_host: String, remote_port: i32) { +pub fn session_add_port_forward( + id: String, + local_port: i32, + remote_host: String, + remote_port: i32, +) { if let Some(session) = SESSIONS.write().unwrap().get_mut(&id) { session.add_port_forward(local_port, remote_host, remote_port); } diff --git a/src/lang/cn.rs b/src/lang/cn.rs index 3e50396e6..fdb23f88e 100644 --- a/src/lang/cn.rs +++ b/src/lang/cn.rs @@ -313,5 +313,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Scroll Style", "滚屏方式"), ("Show Menubar", "显示菜单栏"), ("Hide Menubar", "隐藏菜单栏"), + ("Direct Connection", "直接连接"), + ("Relay Connection", "中继连接"), + ("Secure Connection", "安全连接"), + ("Insecure Connection", "非安全连接"), ].iter().cloned().collect(); } diff --git a/src/lang/cs.rs b/src/lang/cs.rs index f94df1ceb..d9ddf78cc 100644 --- a/src/lang/cs.rs +++ b/src/lang/cs.rs @@ -313,5 +313,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Scroll Style", "Štýl posúvania"), ("Show Menubar", "Zobrazit panel nabídek"), ("Hide Menubar", "skrýt panel nabídek"), + ("Direct Connection", "Přímé spojení"), + ("Relay Connection", "Připojení relé"), + ("Secure Connection", "Zabezpečené připojení"), + ("Insecure Connection", "Nezabezpečené připojení"), ].iter().cloned().collect(); } diff --git a/src/lang/da.rs b/src/lang/da.rs index c0f7abf91..0e2d99425 100644 --- a/src/lang/da.rs +++ b/src/lang/da.rs @@ -313,5 +313,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Scroll Style", "Rulstil"), ("Show Menubar", "Vis menulinje"), ("Hide Menubar", "skjul menulinjen"), + ("Direct Connection", "Direkte forbindelse"), + ("Relay Connection", "Relæforbindelse"), + ("Secure Connection", "Sikker forbindelse"), + ("Insecure Connection", "Usikker forbindelse"), ].iter().cloned().collect(); } diff --git a/src/lang/de.rs b/src/lang/de.rs index e411a751d..20cd9330e 100644 --- a/src/lang/de.rs +++ b/src/lang/de.rs @@ -313,5 +313,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Scroll Style", "Scroll-Stil"), ("Show Menubar", "Menüleiste anzeigen"), ("Hide Menubar", "Menüleiste ausblenden"), + ("Direct Connection", "Direkte Verbindung"), + ("Relay Connection", "Relaisverbindung"), + ("Secure Connection", "Sichere Verbindung"), + ("Insecure Connection", "Unsichere Verbindung"), ].iter().cloned().collect(); } diff --git a/src/lang/eo.rs b/src/lang/eo.rs index 211e6728d..fe12e2d24 100644 --- a/src/lang/eo.rs +++ b/src/lang/eo.rs @@ -313,5 +313,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Scroll Style", "Ruluma Stilo"), ("Show Menubar", "Montru menubreton"), ("Hide Menubar", "kaŝi menubreton"), + ("Direct Connection", "Rekta Konekto"), + ("Relay Connection", "Relajsa Konekto"), + ("Secure Connection", "Sekura Konekto"), + ("Insecure Connection", "Nesekura Konekto"), ].iter().cloned().collect(); } diff --git a/src/lang/es.rs b/src/lang/es.rs index 068442bf4..313ea8cac 100644 --- a/src/lang/es.rs +++ b/src/lang/es.rs @@ -313,5 +313,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Scroll Style", "Estilo de desplazamiento"), ("Show Menubar", "ajustes de pantalla"), ("Hide Menubar", "ocultar barra de menú"), + ("Direct Connection", "Conexión directa"), + ("Relay Connection", "Conexión de relé"), + ("Secure Connection", "Conexión segura"), + ("Insecure Connection", "Conexión insegura"), ].iter().cloned().collect(); } diff --git a/src/lang/fr.rs b/src/lang/fr.rs index d568f050b..c8b12243f 100644 --- a/src/lang/fr.rs +++ b/src/lang/fr.rs @@ -313,5 +313,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Scroll Style", "Style de défilement"), ("Show Menubar", "Afficher la barre de menus"), ("Hide Menubar", "masquer la barre de menus"), + ("Direct Connection", "Connexion directe"), + ("Relay Connection", "Connexion relais"), + ("Secure Connection", "Connexion sécurisée"), + ("Insecure Connection", "Connexion non sécurisée"), ].iter().cloned().collect(); } diff --git a/src/lang/hu.rs b/src/lang/hu.rs index 1fe693248..a6356b000 100644 --- a/src/lang/hu.rs +++ b/src/lang/hu.rs @@ -313,5 +313,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Scroll Style", "Görgetési stílus"), ("Show Menubar", "Menüsor megjelenítése"), ("Hide Menubar", "menüsor elrejtése"), + ("Direct Connection", "Közvetlen kapcsolat"), + ("Relay Connection", "Relé csatlakozás"), + ("Secure Connection", "Biztonságos kapcsolat"), + ("Insecure Connection", "Nem biztonságos kapcsolat"), ].iter().cloned().collect(); } diff --git a/src/lang/id.rs b/src/lang/id.rs index d5d6ed920..8548eb6bc 100644 --- a/src/lang/id.rs +++ b/src/lang/id.rs @@ -313,5 +313,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Scroll Style", "Gaya Gulir"), ("Show Menubar", "Tampilkan bilah menu"), ("Hide Menubar", "sembunyikan bilah menu"), + ("Direct Connection", "Koneksi langsung"), + ("Relay Connection", "Koneksi Relay"), + ("Secure Connection", "Koneksi aman"), + ("Insecure Connection", "Koneksi Tidak Aman"), ].iter().cloned().collect(); } diff --git a/src/lang/it.rs b/src/lang/it.rs index 26e7d4073..fdf8d27d9 100644 --- a/src/lang/it.rs +++ b/src/lang/it.rs @@ -312,5 +312,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Scroll Style", "Stile di scorrimento"), ("Show Menubar", "Mostra la barra dei menu"), ("Hide Menubar", "nascondi la barra dei menu"), + ("Direct Connection", "Connessione diretta"), + ("Relay Connection", "Collegamento a relè"), + ("Secure Connection", "Connessione sicura"), + ("Insecure Connection", "Connessione insicura"), ].iter().cloned().collect(); } diff --git a/src/lang/ja.rs b/src/lang/ja.rs index f1331b01d..1d031f2f2 100644 --- a/src/lang/ja.rs +++ b/src/lang/ja.rs @@ -310,5 +310,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Scroll Style", "スクロール スタイル"), ("Show Menubar", "メニューバーを表示"), ("Hide Menubar", "メニューバーを隠す"), + ("Direct Connection", "直接接続"), + ("Relay Connection", "リレー接続"), + ("Secure Connection", "安全な接続"), + ("Insecure Connection", "安全でない接続"), ].iter().cloned().collect(); } diff --git a/src/lang/ko.rs b/src/lang/ko.rs index 7a0d8dbdf..19d4c7ddf 100644 --- a/src/lang/ko.rs +++ b/src/lang/ko.rs @@ -310,5 +310,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Scroll Style", "스크롤 스타일"), ("Show Menubar", "메뉴 표시줄 표시"), ("Hide Menubar", "메뉴 표시줄 숨기기"), + ("Direct Connection", "직접 연결"), + ("Relay Connection", "릴레이 연결"), + ("Secure Connection", "보안 연결"), + ("Insecure Connection", "안전하지 않은 연결"), ].iter().cloned().collect(); } \ No newline at end of file diff --git a/src/lang/pl.rs b/src/lang/pl.rs index 6f6326121..251c349a2 100644 --- a/src/lang/pl.rs +++ b/src/lang/pl.rs @@ -314,5 +314,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Scroll Style", "Styl przewijania"), ("Show Menubar", "Pokaż pasek menu"), ("Hide Menubar", "ukryj pasek menu"), + ("Direct Connection", "Bezpośrednie połączenie"), + ("Relay Connection", "Połączenie przekaźnika"), + ("Secure Connection", "Bezpieczne połączenie"), + ("Insecure Connection", "Niepewne połączenie"), ].iter().cloned().collect(); } diff --git a/src/lang/pt_PT.rs b/src/lang/pt_PT.rs index 2df6c63dc..fd4384767 100644 --- a/src/lang/pt_PT.rs +++ b/src/lang/pt_PT.rs @@ -310,5 +310,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Scroll Style", "Estilo de rolagem"), ("Show Menubar", "Mostrar barra de menus"), ("Hide Menubar", "ocultar barra de menu"), + ("Direct Connection", "Conexão direta"), + ("Relay Connection", "Conexão de relé"), + ("Secure Connection", "Conexão segura"), + ("Insecure Connection", "Conexão insegura"), ].iter().cloned().collect(); } diff --git a/src/lang/ptbr.rs b/src/lang/ptbr.rs index a0981f867..85eda60e6 100644 --- a/src/lang/ptbr.rs +++ b/src/lang/ptbr.rs @@ -313,5 +313,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Scroll Style", ""), ("Show Menubar", ""), ("Hide Menubar", ""), + ("Direct Connection", ""), + ("Relay Connection", ""), + ("Secure Connection", ""), + ("Insecure Connection", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ru.rs b/src/lang/ru.rs index eed658dfd..def560217 100644 --- a/src/lang/ru.rs +++ b/src/lang/ru.rs @@ -313,5 +313,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Scroll Style", "Стиль прокрутки"), ("Show Menubar", "Показать строку меню"), ("Hide Menubar", "скрыть строку меню"), + ("Direct Connection", "Прямая связь"), + ("Relay Connection", "Релейное соединение"), + ("Secure Connection", "Безопасное соединение"), + ("Insecure Connection", "Небезопасное соединение"), ].iter().cloned().collect(); } diff --git a/src/lang/sk.rs b/src/lang/sk.rs index b4e61e83f..4c04618aa 100644 --- a/src/lang/sk.rs +++ b/src/lang/sk.rs @@ -313,5 +313,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Scroll Style", "Štýl posúvania"), ("Show Menubar", "Zobraziť panel s ponukami"), ("Hide Menubar", "skryť panel s ponukami"), + ("Direct Connection", "Priame pripojenie"), + ("Relay Connection", "Reléové pripojenie"), + ("Secure Connection", "Zabezpečené pripojenie"), + ("Insecure Connection", "Nezabezpečené pripojenie"), ].iter().cloned().collect(); } diff --git a/src/lang/template.rs b/src/lang/template.rs index 2e5c67cd8..081b7bf55 100644 --- a/src/lang/template.rs +++ b/src/lang/template.rs @@ -313,5 +313,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Scroll Style", ""), ("Show Menubar", ""), ("Hide Menubar", ""), + ("Direct Connection", ""), + ("Relay Connection", ""), + ("Secure Connection", ""), + ("Insecure Connection", ""), ].iter().cloned().collect(); } diff --git a/src/lang/tr.rs b/src/lang/tr.rs index 829659954..9738ed469 100644 --- a/src/lang/tr.rs +++ b/src/lang/tr.rs @@ -313,5 +313,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Scroll Style", "Kaydırma Stili"), ("Show Menubar", "Menü çubuğunu göster"), ("Hide Menubar", "menü çubuğunu gizle"), + ("Direct Connection", "Doğrudan Bağlantı"), + ("Relay Connection", "Röle Bağlantısı"), + ("Secure Connection", "Güvenli bağlantı"), + ("Insecure Connection", "Güvenli Bağlantı"), ].iter().cloned().collect(); } diff --git a/src/lang/tw.rs b/src/lang/tw.rs index f7d7cbe1d..46276dd2a 100644 --- a/src/lang/tw.rs +++ b/src/lang/tw.rs @@ -313,5 +313,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Scroll Style", "滾動樣式"), ("Show Menubar", "顯示菜單欄"), ("Hide Menubar", "隱藏菜單欄"), + ("Direct Connection", "直接連接"), + ("Relay Connection", "中繼連接"), + ("Secure Connection", "安全連接"), + ("Insecure Connection", "非安全連接"), ].iter().cloned().collect(); } diff --git a/src/lang/vn.rs b/src/lang/vn.rs index 1c77139b3..474e57337 100644 --- a/src/lang/vn.rs +++ b/src/lang/vn.rs @@ -313,5 +313,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Scroll Style", "Kiểu cuộn"), ("Show Menubar", "Hiển thị thanh menu"), ("Hide Menubar", "ẩn thanh menu"), + ("Direct Connection", "Kết nối trực tiếp"), + ("Relay Connection", "Kết nối chuyển tiếp"), + ("Secure Connection", "Kết nối an toàn"), + ("Insecure Connection", "Kết nối không an toàn"), ].iter().cloned().collect(); }