diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index fa1ea11b5951..efcff4f83f4c 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -350,6 +350,9 @@ class MyTheme { hoverColor: Color.fromARGB(255, 224, 224, 224), scaffoldBackgroundColor: Colors.white, dialogBackgroundColor: Colors.white, + appBarTheme: AppBarTheme( + shadowColor: Colors.transparent, + ), dialogTheme: DialogTheme( elevation: 15, shape: RoundedRectangleBorder( @@ -445,6 +448,9 @@ class MyTheme { hoverColor: Color.fromARGB(255, 45, 46, 53), scaffoldBackgroundColor: Color(0xFF18191E), dialogBackgroundColor: Color(0xFF18191E), + appBarTheme: AppBarTheme( + shadowColor: Colors.transparent, + ), dialogTheme: DialogTheme( elevation: 15, shape: RoundedRectangleBorder( @@ -550,7 +556,7 @@ class MyTheme { static void changeDarkMode(ThemeMode mode) async { Get.changeThemeMode(mode); - if (desktopType == DesktopType.main || isAndroid || isIOS) { + if (desktopType == DesktopType.main || isAndroid || isIOS || isWeb) { if (mode == ThemeMode.system) { await bind.mainSetLocalOption( key: kCommConfKeyTheme, value: defaultOptionTheme); @@ -558,7 +564,7 @@ class MyTheme { await bind.mainSetLocalOption( key: kCommConfKeyTheme, value: mode.toShortString()); } - await bind.mainChangeTheme(dark: mode.toShortString()); + if (!isWeb) await bind.mainChangeTheme(dark: mode.toShortString()); // Synchronize the window theme of the system. updateSystemWindowTheme(); } diff --git a/flutter/lib/desktop/pages/desktop_setting_page.dart b/flutter/lib/desktop/pages/desktop_setting_page.dart index 6d9f92b59dc4..4ab1a8c56671 100644 --- a/flutter/lib/desktop/pages/desktop_setting_page.dart +++ b/flutter/lib/desktop/pages/desktop_setting_page.dart @@ -61,7 +61,8 @@ class DesktopSettingPage extends StatefulWidget { final SettingsTabKey initialTabkey; static final List tabKeys = [ SettingsTabKey.general, - if (!bind.isOutgoingOnly() && + if (!isWeb && + !bind.isOutgoingOnly() && !bind.isDisableSettings() && bind.mainGetBuildinOption(key: kOptionHideSecuritySetting) != 'Y') SettingsTabKey.safety, @@ -216,7 +217,7 @@ class _DesktopSettingPageState extends State width: _kTabWidth, child: Column( children: [ - _header(), + _header(context), Flexible(child: _listView(tabs: _settingTabs())), ], ), @@ -239,21 +240,40 @@ class _DesktopSettingPageState extends State ); } - Widget _header() { + Widget _header(BuildContext context) { + final settingsText = Text( + translate('Settings'), + textAlign: TextAlign.left, + style: const TextStyle( + color: _accentColor, + fontSize: _kTitleFontSize, + fontWeight: FontWeight.w400, + ), + ); return Row( children: [ - SizedBox( - height: 62, - child: Text( - translate('Settings'), - textAlign: TextAlign.left, - style: const TextStyle( - color: _accentColor, - fontSize: _kTitleFontSize, - fontWeight: FontWeight.w400, + if (isWeb) + IconButton( + onPressed: () { + if (Navigator.canPop(context)) { + Navigator.pop(context); + } + }, + icon: Icon(Icons.arrow_back), + ).marginOnly(left: 5), + if (isWeb) + SizedBox( + height: 62, + child: Align( + alignment: Alignment.center, + child: settingsText, ), - ), - ).marginOnly(left: 20, top: 10), + ).marginOnly(left: 20), + if (!isWeb) + SizedBox( + height: 62, + child: settingsText, + ).marginOnly(left: 20, top: 10), const Spacer(), ], ); @@ -322,7 +342,8 @@ class _General extends StatefulWidget { } class _GeneralState extends State<_General> { - final RxBool serviceStop = Get.find(tag: 'stop-service'); + final RxBool serviceStop = + isWeb ? RxBool(false) : Get.find(tag: 'stop-service'); RxBool serviceBtnEnabled = true.obs; @override @@ -334,13 +355,13 @@ class _GeneralState extends State<_General> { physics: DraggableNeverScrollableScrollPhysics(), controller: scrollController, children: [ - service(), + if (!isWeb) service(), theme(), _Card(title: 'Language', children: [language()]), - hwcodec(), - audio(context), - record(context), - WaylandCard(), + if (!isWeb) hwcodec(), + if (!isWeb) audio(context), + if (!isWeb) record(context), + if (!isWeb) WaylandCard(), other() ], ).marginOnly(bottom: _kListViewBottomMargin)); @@ -394,13 +415,13 @@ class _GeneralState extends State<_General> { Widget other() { final children = [ - if (!bind.isIncomingOnly()) + if (!isWeb && !bind.isIncomingOnly()) _OptionCheckBox(context, 'Confirm before closing multiple tabs', kOptionEnableConfirmClosingTabs, isServer: false), _OptionCheckBox(context, 'Adaptive bitrate', kOptionEnableAbr), - wallpaper(), - if (!bind.isIncomingOnly()) ...[ + if (!isWeb) wallpaper(), + if (!isWeb && !bind.isIncomingOnly()) ...[ _OptionCheckBox( context, 'Open connection in new tab', @@ -417,18 +438,19 @@ class _GeneralState extends State<_General> { kOptionAllowAlwaysSoftwareRender, ), ), - Tooltip( - message: translate('texture_render_tip'), - child: _OptionCheckBox( - context, - "Use texture rendering", - kOptionTextureRender, - optGetter: bind.mainGetUseTextureRender, - optSetter: (k, v) async => - await bind.mainSetLocalOption(key: k, value: v ? 'Y' : 'N'), + if (!isWeb) + Tooltip( + message: translate('texture_render_tip'), + child: _OptionCheckBox( + context, + "Use texture rendering", + kOptionTextureRender, + optGetter: bind.mainGetUseTextureRender, + optSetter: (k, v) async => + await bind.mainSetLocalOption(key: k, value: v ? 'Y' : 'N'), + ), ), - ), - if (!bind.isCustomClient()) + if (!isWeb && !bind.isCustomClient()) _OptionCheckBox( context, 'Check for software update on startup', @@ -443,7 +465,7 @@ class _GeneralState extends State<_General> { ) ], ]; - if (bind.mainShowOption(key: kOptionAllowLinuxHeadless)) { + if (!isWeb && bind.mainShowOption(key: kOptionAllowLinuxHeadless)) { children.add(_OptionCheckBox( context, 'Allow linux headless', kOptionAllowLinuxHeadless)); } @@ -641,8 +663,9 @@ class _GeneralState extends State<_General> { initialKey: currentKey, onChanged: (key) async { await bind.mainSetLocalOption(key: kCommConfKeyLang, value: key); - reloadAllWindows(); - bind.mainChangeLanguage(lang: key); + if (isWeb) reloadCurrentWindow(); + if (!isWeb) reloadAllWindows(); + if (!isWeb) bind.mainChangeLanguage(lang: key); }, enabled: !isOptFixed, ).marginOnly(left: _kContentHMargin); @@ -1337,7 +1360,7 @@ class _Network extends StatefulWidget { class _NetworkState extends State<_Network> with AutomaticKeepAliveClientMixin { @override bool get wantKeepAlive => true; - bool locked = bind.mainIsInstalled(); + bool locked = !isWeb && bind.mainIsInstalled(); @override Widget build(BuildContext context) { @@ -1346,8 +1369,9 @@ class _NetworkState extends State<_Network> with AutomaticKeepAliveClientMixin { final scrollController = ScrollController(); final hideServer = bind.mainGetBuildinOption(key: kOptionHideServerSetting) == 'Y'; + // TODO: support web proxy final hideProxy = - bind.mainGetBuildinOption(key: kOptionHideProxySetting) == 'Y'; + isWeb || bind.mainGetBuildinOption(key: kOptionHideProxySetting) == 'Y'; return DesktopScrollWrapper( scrollController: scrollController, child: ListView( @@ -1467,7 +1491,7 @@ class _DisplayState extends State<_Display> { scrollStyle(context), imageQuality(context), codec(context), - privacyModeImpl(context), + if (!isWeb) privacyModeImpl(context), other(context), ]).marginOnly(bottom: _kListViewBottomMargin)); } diff --git a/flutter/lib/mobile/pages/connection_page.dart b/flutter/lib/mobile/pages/connection_page.dart index 02c552d716d7..9fcef8e3f14b 100644 --- a/flutter/lib/mobile/pages/connection_page.dart +++ b/flutter/lib/mobile/pages/connection_page.dart @@ -9,19 +9,16 @@ import 'package:url_launcher/url_launcher.dart'; import 'package:flutter_hbb/models/peer_model.dart'; import '../../common.dart'; -import '../../common/widgets/login.dart'; import '../../common/widgets/peer_tab_page.dart'; import '../../common/widgets/autocomplete.dart'; import '../../consts.dart'; import '../../models/model.dart'; import '../../models/platform_model.dart'; import 'home_page.dart'; -import 'scan_page.dart'; -import 'settings_page.dart'; /// Connection page for connecting to a remote peer. class ConnectionPage extends StatefulWidget implements PageShape { - ConnectionPage({Key? key}) : super(key: key); + ConnectionPage({Key? key, required this.appBarActions}) : super(key: key); @override final icon = const Icon(Icons.connected_tv); @@ -30,7 +27,7 @@ class ConnectionPage extends StatefulWidget implements PageShape { final title = translate("Connection"); @override - final appBarActions = isWeb ? [const WebMenu()] : []; + final List appBarActions; @override State createState() => _ConnectionPageState(); @@ -356,73 +353,3 @@ class _ConnectionPageState extends State { super.dispose(); } } - -class WebMenu extends StatefulWidget { - const WebMenu({Key? key}) : super(key: key); - - @override - State createState() => _WebMenuState(); -} - -class _WebMenuState extends State { - @override - Widget build(BuildContext context) { - Provider.of(context); - return PopupMenuButton( - tooltip: "", - icon: const Icon(Icons.more_vert), - itemBuilder: (context) { - return (isIOS - ? [ - const PopupMenuItem( - value: "scan", - child: Icon(Icons.qr_code_scanner, color: Colors.black), - ) - ] - : >[]) + - [ - PopupMenuItem( - value: "server", - child: Text(translate('ID/Relay Server')), - ) - ] + - [ - PopupMenuItem( - value: "login", - child: Text(gFFI.userModel.userName.value.isEmpty - ? translate("Login") - : '${translate("Logout")} (${gFFI.userModel.userName.value})'), - ) - ] + - [ - PopupMenuItem( - value: "about", - child: Text(translate('About RustDesk')), - ) - ]; - }, - onSelected: (value) { - if (value == 'server') { - showServerSettings(gFFI.dialogManager); - } - if (value == 'about') { - showAbout(gFFI.dialogManager); - } - if (value == 'login') { - if (gFFI.userModel.userName.value.isEmpty) { - loginDialog(); - } else { - logOutConfirmDialog(); - } - } - if (value == 'scan') { - Navigator.push( - context, - MaterialPageRoute( - builder: (BuildContext context) => ScanPage(), - ), - ); - } - }); - } -} diff --git a/flutter/lib/mobile/pages/home_page.dart b/flutter/lib/mobile/pages/home_page.dart index 078e2b2f7b3c..0db7a2b91e3a 100644 --- a/flutter/lib/mobile/pages/home_page.dart +++ b/flutter/lib/mobile/pages/home_page.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_hbb/mobile/pages/server_page.dart'; import 'package:flutter_hbb/mobile/pages/settings_page.dart'; +import 'package:flutter_hbb/web/settings_page.dart'; import 'package:get/get.dart'; import '../../common.dart'; import '../../common/widgets/chat_page.dart'; @@ -45,7 +46,10 @@ class HomePageState extends State { void initPages() { _pages.clear(); - if (!bind.isIncomingOnly()) _pages.add(ConnectionPage()); + if (!bind.isIncomingOnly()) + _pages.add(ConnectionPage( + appBarActions: [], + )); if (isAndroid && !bind.isOutgoingOnly()) { _chatPageTabIndex = _pages.length; _pages.addAll([ChatPage(type: ChatPageType.mobileMain), ServerPage()]); @@ -149,7 +153,8 @@ class HomePageState extends State { } class WebHomePage extends StatelessWidget { - final connectionPage = ConnectionPage(); + final connectionPage = + ConnectionPage(appBarActions: [const WebSettingsPage()]); @override Widget build(BuildContext context) { diff --git a/flutter/lib/web/bridge.dart b/flutter/lib/web/bridge.dart index 3aa65a5be69f..305ed0b7550b 100644 --- a/flutter/lib/web/bridge.dart +++ b/flutter/lib/web/bridge.dart @@ -736,7 +736,8 @@ class RustdeskImpl { } Future mainGetLicense({dynamic hint}) { - throw UnimplementedError(); + // TODO: implement + return Future(() => ''); } Future mainGetVersion({dynamic hint}) { @@ -975,10 +976,11 @@ class RustdeskImpl { Future mainSetUserDefaultOption( {required String key, required String value, dynamic hint}) { - return js.context.callMethod('getByName', [ + js.context.callMethod('setByName', [ 'option:user:default', jsonEncode({'name': key, 'value': value}) ]); + return Future.value(); } String mainGetUserDefaultOption({required String key, dynamic hint}) { @@ -1052,7 +1054,7 @@ class RustdeskImpl { } Future mainGetLangs({dynamic hint}) { - throw UnimplementedError(); + return Future(() => js.context.callMethod('getByName', ['langs'])); } Future mainGetTemporaryPassword({dynamic hint}) { @@ -1064,7 +1066,8 @@ class RustdeskImpl { } Future mainGetFingerprint({dynamic hint}) { - throw UnimplementedError(); + // TODO: implement + return Future.value(''); } Future cmGetClientsState({dynamic hint}) { @@ -1106,7 +1109,7 @@ class RustdeskImpl { } String mainSupportedHwdecodings({dynamic hint}) { - throw UnimplementedError(); + return '{}'; } Future mainIsRoot({dynamic hint}) { @@ -1295,8 +1298,7 @@ class RustdeskImpl { } Future mainGetBuildDate({dynamic hint}) { - // TODO - throw UnimplementedError(); + return Future(() => js.context.callMethod('getByName', ['build_date'])); } String translate( diff --git a/flutter/lib/web/settings_page.dart b/flutter/lib/web/settings_page.dart new file mode 100644 index 000000000000..13ba6cb2f275 --- /dev/null +++ b/flutter/lib/web/settings_page.dart @@ -0,0 +1,98 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_hbb/desktop/pages/desktop_setting_page.dart'; +import 'package:flutter_hbb/mobile/pages/scan_page.dart'; +import 'package:flutter_hbb/mobile/pages/settings_page.dart'; +import 'package:provider/provider.dart'; + +import '../../common.dart'; +import '../../common/widgets/login.dart'; +import '../../models/model.dart'; + +class WebSettingsPage extends StatelessWidget { + const WebSettingsPage({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + if (isWebDesktop) { + return _buildDesktopButton(context); + } else { + return _buildMobileMenu(context); + } + } + + Widget _buildDesktopButton(BuildContext context) { + return IconButton( + icon: const Icon(Icons.more_vert), + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (BuildContext context) => + DesktopSettingPage(initialTabkey: SettingsTabKey.general), + ), + ); + }, + ); + } + + Widget _buildMobileMenu(BuildContext context) { + Provider.of(context); + return PopupMenuButton( + tooltip: "", + icon: const Icon(Icons.more_vert), + itemBuilder: (context) { + return (isIOS + ? [ + const PopupMenuItem( + value: "scan", + child: Icon(Icons.qr_code_scanner, color: Colors.black), + ) + ] + : >[]) + + [ + PopupMenuItem( + value: "server", + child: Text(translate('ID/Relay Server')), + ) + ] + + [ + PopupMenuItem( + value: "login", + child: Text(gFFI.userModel.userName.value.isEmpty + ? translate("Login") + : '${translate("Logout")} (${gFFI.userModel.userName.value})'), + ) + ] + + [ + PopupMenuItem( + value: "about", + child: Text(translate('About RustDesk')), + ) + ]; + }, + onSelected: (value) { + if (value == 'server') { + showServerSettings(gFFI.dialogManager); + } + if (value == 'about') { + showAbout(gFFI.dialogManager); + } + if (value == 'login') { + if (gFFI.userModel.userName.value.isEmpty) { + loginDialog(); + } else { + logOutConfirmDialog(); + } + } + if (value == 'scan') { + Navigator.push( + context, + MaterialPageRoute( + builder: (BuildContext context) => ScanPage(), + ), + ); + } + }); + } +} +