diff --git a/flutter/lib/desktop/pages/desktop_setting_page.dart b/flutter/lib/desktop/pages/desktop_setting_page.dart index 577b1e8c5..6f220a35f 100644 --- a/flutter/lib/desktop/pages/desktop_setting_page.dart +++ b/flutter/lib/desktop/pages/desktop_setting_page.dart @@ -11,6 +11,7 @@ import 'package:flutter_hbb/common/widgets/setting_widgets.dart'; import 'package:flutter_hbb/consts.dart'; import 'package:flutter_hbb/desktop/pages/desktop_home_page.dart'; import 'package:flutter_hbb/desktop/pages/desktop_tab_page.dart'; +import 'package:flutter_hbb/mobile/widgets/dialog.dart'; import 'package:flutter_hbb/models/platform_model.dart'; import 'package:flutter_hbb/models/server_model.dart'; import 'package:flutter_hbb/plugin/manager.dart'; @@ -1363,34 +1364,10 @@ class _NetworkState extends State<_Network> with AutomaticKeepAliveClientMixin { bool locked = !isWeb && bind.mainIsInstalled(); final scrollController = ScrollController(); - late final TextEditingController idController; - late final TextEditingController relayController; - late final TextEditingController apiController; - late final TextEditingController keyController; - - @override - void initState() { - super.initState(); - Map oldOptions = jsonDecode(bind.mainGetOptionsSync()); - old(String key) { - return (oldOptions[key] ?? '').trim(); - } - - idController = TextEditingController(text: old('custom-rendezvous-server')); - relayController = TextEditingController(text: old('relay-server')); - apiController = TextEditingController(text: old('api-server')); - keyController = TextEditingController(text: old('key')); - } @override Widget build(BuildContext context) { super.build(context); - bool enabled = !locked; - final hideServer = - bind.mainGetBuildinOption(key: kOptionHideServerSetting) == 'Y'; - // TODO: support web proxy - final hideProxy = - isWeb || bind.mainGetBuildinOption(key: kOptionHideProxySetting) == 'Y'; return ListView(controller: scrollController, children: [ _lock(locked, 'Unlock Network Settings', () { locked = false; @@ -1399,80 +1376,69 @@ class _NetworkState extends State<_Network> with AutomaticKeepAliveClientMixin { AbsorbPointer( absorbing: locked, child: Column(children: [ - if (!hideServer) server(enabled), - if (!hideProxy) - _Card(title: 'Proxy', children: [ - _Button('Socks5/Http(s) Proxy', changeSocks5Proxy, - enabled: enabled), - ]), + network(context), ]), ), ]).marginOnly(bottom: _kListViewBottomMargin); } - server(bool enabled) { - // Simple temp wrapper for PR check - tmpWrapper() { - // Setting page is not modal, oldOptions should only be used when getting options, never when setting. + Widget network(BuildContext context) { + final hideServer = + bind.mainGetBuildinOption(key: kOptionHideServerSetting) == 'Y'; + final hideProxy = + isWeb || bind.mainGetBuildinOption(key: kOptionHideProxySetting) == 'Y'; - RxString idErrMsg = ''.obs; - RxString relayErrMsg = ''.obs; - RxString apiErrMsg = ''.obs; - final controllers = [ - idController, - relayController, - apiController, - keyController, - ]; - final errMsgs = [ - idErrMsg, - relayErrMsg, - apiErrMsg, - ]; - - submit() async { - bool result = await setServerConfig( - null, - errMsgs, - ServerConfig( - idServer: idController.text, - relayServer: relayController.text, - apiServer: apiController.text, - key: keyController.text)); - if (result) { - setState(() {}); - showToast(translate('Successful')); - } else { - showToast(translate('Failed')); - } - } - - bool secure = !enabled; - return _Card( - title: 'ID/Relay Server', - title_suffix: ServerConfigImportExportWidgets(controllers, errMsgs), - children: [ - Column( - children: [ - Obx(() => _LabeledTextField(context, 'ID Server', idController, - idErrMsg.value, enabled, secure)), - if (!isWeb) - Obx(() => _LabeledTextField(context, 'Relay Server', - relayController, relayErrMsg.value, enabled, secure)), - Obx(() => _LabeledTextField(context, 'API Server', - apiController, apiErrMsg.value, enabled, secure)), - _LabeledTextField( - context, 'Key', keyController, '', enabled, secure), - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [_Button('Apply', submit, enabled: enabled)], - ).marginOnly(top: 10), - ], - ) - ]); + if (hideServer && hideProxy) { + return Offstage(); } - return tmpWrapper(); + return _Card( + title: 'Network', + children: [ + Container( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (!hideServer) + ListTile( + leading: Icon(Icons.dns_outlined, color: _accentColor), + title: Text( + translate('ID/Relay Server'), + style: TextStyle(fontSize: _kContentFontSize), + ), + enabled: !locked, + onTap: () => showServerSettings(gFFI.dialogManager), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + contentPadding: EdgeInsets.symmetric(horizontal: 16), + minLeadingWidth: 0, + horizontalTitleGap: 10, + ), + if (!hideServer && !hideProxy) + Divider(height: 1, indent: 16, endIndent: 16), + if (!hideProxy) + ListTile( + leading: + Icon(Icons.network_ping_outlined, color: _accentColor), + title: Text( + translate('Socks5/Http(s) Proxy'), + style: TextStyle(fontSize: _kContentFontSize), + ), + enabled: !locked, + onTap: changeSocks5Proxy, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + contentPadding: EdgeInsets.symmetric(horizontal: 16), + minLeadingWidth: 0, + horizontalTitleGap: 10, + ), + ], + ), + ), + ], + ); } } diff --git a/flutter/lib/mobile/pages/settings_page.dart b/flutter/lib/mobile/pages/settings_page.dart index eb3865933..9e265810f 100644 --- a/flutter/lib/mobile/pages/settings_page.dart +++ b/flutter/lib/mobile/pages/settings_page.dart @@ -828,11 +828,6 @@ class _SettingsState extends State with WidgetsBindingObserver { } } -void showServerSettings(OverlayDialogManager dialogManager) async { - Map options = jsonDecode(await bind.mainGetOptions()); - showServerSettingsWithValue(ServerConfig.fromOptions(options), dialogManager); -} - void showLanguageSettings(OverlayDialogManager dialogManager) async { try { final langs = json.decode(await bind.mainGetLangs()) as List; diff --git a/flutter/lib/mobile/widgets/dialog.dart b/flutter/lib/mobile/widgets/dialog.dart index 2d17f3b54..1c8b4dd3d 100644 --- a/flutter/lib/mobile/widgets/dialog.dart +++ b/flutter/lib/mobile/widgets/dialog.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter_hbb/common/widgets/setting_widgets.dart'; import 'package:flutter_hbb/common/widgets/toolbar.dart'; @@ -146,6 +147,16 @@ void setTemporaryPasswordLengthDialog( }, backDismiss: true, clickMaskDismiss: true); } +void showServerSettings(OverlayDialogManager dialogManager) async { + Map options = {}; + try { + options = jsonDecode(await bind.mainGetOptions()); + } catch (e) { + print("Invalid server config: $e"); + } + showServerSettingsWithValue(ServerConfig.fromOptions(options), dialogManager); +} + void showServerSettingsWithValue( ServerConfig serverConfig, OverlayDialogManager dialogManager) async { var isInProgress = false; @@ -184,6 +195,43 @@ void showServerSettingsWithValue( return ret; } + Widget buildField( + String label, TextEditingController controller, String errorMsg, + {String? Function(String?)? validator, bool autofocus = false}) { + if (isDesktop || isWeb) { + return Row( + children: [ + SizedBox( + width: 120, + child: Text(label), + ), + SizedBox(width: 8), + Expanded( + child: TextFormField( + controller: controller, + decoration: InputDecoration( + errorText: errorMsg.isEmpty ? null : errorMsg, + contentPadding: + EdgeInsets.symmetric(horizontal: 8, vertical: 12), + ), + validator: validator, + autofocus: autofocus, + ), + ), + ], + ); + } + + return TextFormField( + controller: controller, + decoration: InputDecoration( + labelText: label, + errorText: errorMsg.isEmpty ? null : errorMsg, + ), + validator: validator, + ); + } + return CustomAlertDialog( title: Row( children: [ @@ -191,56 +239,45 @@ void showServerSettingsWithValue( ...ServerConfigImportExportWidgets(controllers, errMsgs), ], ), - content: Form( + content: ConstrainedBox( + constraints: const BoxConstraints(minWidth: 500), + child: Form( child: Obx(() => Column( - mainAxisSize: MainAxisSize.min, - children: [ - TextFormField( - controller: idCtrl, - decoration: InputDecoration( - labelText: translate('ID Server'), - errorText: idServerMsg.value.isEmpty - ? null - : idServerMsg.value), - ) - ] + - [ - if (isAndroid) - TextFormField( - controller: relayCtrl, - decoration: InputDecoration( - labelText: translate('Relay Server'), - errorText: relayServerMsg.value.isEmpty - ? null - : relayServerMsg.value), - ) - ] + - [ - TextFormField( - controller: apiCtrl, - decoration: InputDecoration( - labelText: translate('API Server'), - ), - autovalidateMode: AutovalidateMode.onUserInteraction, - validator: (v) { - if (v != null && v.isNotEmpty) { - if (!(v.startsWith('http://') || - v.startsWith("https://"))) { - return translate("invalid_http"); - } + mainAxisSize: MainAxisSize.min, + children: [ + buildField(translate('ID Server'), idCtrl, idServerMsg.value, + autofocus: true), + SizedBox(height: 8), + if (!isIOS && !isWeb) ...[ + buildField(translate('Relay Server'), relayCtrl, + relayServerMsg.value), + SizedBox(height: 8), + ], + buildField( + translate('API Server'), + apiCtrl, + apiServerMsg.value, + validator: (v) { + if (v != null && v.isNotEmpty) { + if (!(v.startsWith('http://') || + v.startsWith("https://"))) { + return translate("invalid_http"); } - return null; - }, + } + return null; + }, + ), + SizedBox(height: 8), + buildField('Key', keyCtrl, ''), + if (isInProgress) + Padding( + padding: EdgeInsets.only(top: 8), + child: LinearProgressIndicator(), ), - TextFormField( - controller: keyCtrl, - decoration: InputDecoration( - labelText: 'Key', - ), - ), - // NOT use Offstage to wrap LinearProgressIndicator - if (isInProgress) const LinearProgressIndicator(), - ]))), + ], + )), + ), + ), actions: [ dialogButton('Cancel', onPressed: () { close();