From b80051bb35d6088e4befa7e3778732203e871a13 Mon Sep 17 00:00:00 2001 From: dignow Date: Sun, 23 Jul 2023 17:07:30 +0800 Subject: [PATCH] fix, settings on main window Signed-off-by: dignow --- flutter/lib/common.dart | 13 + .../desktop/pages/desktop_setting_page.dart | 682 +++++++++--------- src/flutter_ffi.rs | 8 + 3 files changed, 347 insertions(+), 356 deletions(-) diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index 4d25e6d0d..71194b42a 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -1269,6 +1269,19 @@ String bool2option(String option, bool b) { return res; } +mainSetBoolOption(String key, bool value) async { + String v = bool2option(key, value); + await bind.mainSetOption(key: key, value: v); +} + +Future mainGetBoolOption(String key) async { + return option2bool(key, await bind.mainGetOption(key: key)); +} + +bool mainGetBoolOptionSync(String key) { + return option2bool(key, bind.mainGetOptionSync(key: key)); +} + Future matchPeer(String searchText, Peer peer) async { if (searchText.isEmpty) { return true; diff --git a/flutter/lib/desktop/pages/desktop_setting_page.dart b/flutter/lib/desktop/pages/desktop_setting_page.dart index 1ff8bc09e..d5a7c58fc 100644 --- a/flutter/lib/desktop/pages/desktop_setting_page.dart +++ b/flutter/lib/desktop/pages/desktop_setting_page.dart @@ -385,20 +385,17 @@ class _GeneralState extends State<_General> { Widget record(BuildContext context) { return futureBuilder(future: () async { - String customDirectory = - await bind.mainGetOption(key: 'video-save-directory'); String defaultDirectory = await bind.mainDefaultVideoSaveDirectory(); - String dir; - if (customDirectory.isNotEmpty) { - dir = customDirectory; - } else { - dir = defaultDirectory; - } // canLaunchUrl blocked on windows portable, user SYSTEM - return {'dir': dir, 'canlaunch': true}; + return {'dir': defaultDirectory, 'canlaunch': true}; }(), hasData: (data) { Map map = data as Map; String dir = map['dir']!; + String customDirectory = + bind.mainGetOptionSync(key: 'video-save-directory'); + if (customDirectory.isNotEmpty) { + dir = customDirectory; + } bool canlaunch = map['canlaunch']! as bool; return _Card(title: 'Recording', children: [ @@ -444,8 +441,7 @@ class _GeneralState extends State<_General> { Widget language() { return futureBuilder(future: () async { String langs = await bind.mainGetLangs(); - String lang = bind.mainGetLocalOption(key: kCommConfKeyLang); - return {'langs': langs, 'lang': lang}; + return {'langs': langs}; }(), hasData: (res) { Map data = res as Map; List langsList = jsonDecode(data['langs']!); @@ -454,7 +450,7 @@ class _GeneralState extends State<_General> { List values = langsMap.values.toList(); keys.insert(0, ''); values.insert(0, translate('Default')); - String currentKey = data['lang']!; + String currentKey = bind.mainGetLocalOption(key: kCommConfKeyLang); if (!keys.contains(currentKey)) { currentKey = ''; } @@ -529,79 +525,75 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin { Widget permissions(context) { bool enabled = !locked; - return futureBuilder(future: () async { - return await bind.mainGetOption(key: 'access-mode'); - }(), hasData: (data) { - String accessMode = data! as String; - _AccessMode mode; - if (accessMode == 'full') { - mode = _AccessMode.full; - } else if (accessMode == 'view') { - mode = _AccessMode.view; - } else { - mode = _AccessMode.custom; - } - String initialKey; - bool? fakeValue; - switch (mode) { - case _AccessMode.custom: - initialKey = ''; - fakeValue = null; - break; - case _AccessMode.full: - initialKey = 'full'; - fakeValue = true; - break; - case _AccessMode.view: - initialKey = 'view'; - fakeValue = false; - break; - } + String accessMode = bind.mainGetOptionSync(key: 'access-mode'); + _AccessMode mode; + if (accessMode == 'full') { + mode = _AccessMode.full; + } else if (accessMode == 'view') { + mode = _AccessMode.view; + } else { + mode = _AccessMode.custom; + } + String initialKey; + bool? fakeValue; + switch (mode) { + case _AccessMode.custom: + initialKey = ''; + fakeValue = null; + break; + case _AccessMode.full: + initialKey = 'full'; + fakeValue = true; + break; + case _AccessMode.view: + initialKey = 'view'; + fakeValue = false; + break; + } - return _Card(title: 'Permissions', children: [ - _ComboBox( - keys: [ - '', - 'full', - 'view', - ], - values: [ - translate('Custom'), - translate('Full Access'), - translate('Screen Share'), - ], - enabled: enabled, - initialKey: initialKey, - onChanged: (mode) async { - await bind.mainSetOption(key: 'access-mode', value: mode); - setState(() {}); - }).marginOnly(left: _kContentHMargin), - Column( - children: [ - _OptionCheckBox(context, 'Enable Keyboard/Mouse', 'enable-keyboard', - enabled: enabled, fakeValue: fakeValue), - _OptionCheckBox(context, 'Enable Clipboard', 'enable-clipboard', - enabled: enabled, fakeValue: fakeValue), - _OptionCheckBox( - context, 'Enable File Transfer', 'enable-file-transfer', - enabled: enabled, fakeValue: fakeValue), - _OptionCheckBox(context, 'Enable Audio', 'enable-audio', - enabled: enabled, fakeValue: fakeValue), - _OptionCheckBox(context, 'Enable TCP Tunneling', 'enable-tunnel', - enabled: enabled, fakeValue: fakeValue), - _OptionCheckBox( - context, 'Enable Remote Restart', 'enable-remote-restart', - enabled: enabled, fakeValue: fakeValue), - _OptionCheckBox( - context, 'Enable Recording Session', 'enable-record-session', - enabled: enabled, fakeValue: fakeValue), - _OptionCheckBox(context, 'Enable remote configuration modification', - 'allow-remote-config-modification', - enabled: enabled, fakeValue: fakeValue), + return _Card(title: 'Permissions', children: [ + _ComboBox( + keys: [ + '', + 'full', + 'view', ], - ), - ]); - }); + values: [ + translate('Custom'), + translate('Full Access'), + translate('Screen Share'), + ], + enabled: enabled, + initialKey: initialKey, + onChanged: (mode) async { + await bind.mainSetOption(key: 'access-mode', value: mode); + setState(() {}); + }).marginOnly(left: _kContentHMargin), + Column( + children: [ + _OptionCheckBox(context, 'Enable Keyboard/Mouse', 'enable-keyboard', + enabled: enabled, fakeValue: fakeValue), + _OptionCheckBox(context, 'Enable Clipboard', 'enable-clipboard', + enabled: enabled, fakeValue: fakeValue), + _OptionCheckBox( + context, 'Enable File Transfer', 'enable-file-transfer', + enabled: enabled, fakeValue: fakeValue), + _OptionCheckBox(context, 'Enable Audio', 'enable-audio', + enabled: enabled, fakeValue: fakeValue), + _OptionCheckBox(context, 'Enable TCP Tunneling', 'enable-tunnel', + enabled: enabled, fakeValue: fakeValue), + _OptionCheckBox( + context, 'Enable Remote Restart', 'enable-remote-restart', + enabled: enabled, fakeValue: fakeValue), + _OptionCheckBox( + context, 'Enable Recording Session', 'enable-record-session', + enabled: enabled, fakeValue: fakeValue), + _OptionCheckBox(context, 'Enable remote configuration modification', + 'allow-remote-config-modification', + enabled: enabled, fakeValue: fakeValue), + ], + ), + ]); } Widget password(BuildContext context) { @@ -759,106 +751,94 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin { return [ _OptionCheckBox(context, 'Enable Direct IP Access', 'direct-server', update: update, enabled: !locked), - futureBuilder( - future: () async { - String enabled = await bind.mainGetOption(key: 'direct-server'); - String port = await bind.mainGetOption(key: 'direct-access-port'); - return {'enabled': enabled, 'port': port}; - }(), - hasData: (data) { - bool enabled = - option2bool('direct-server', data['enabled'].toString()); - if (!enabled) applyEnabled.value = false; - controller.text = data['port'].toString(); - return Offstage( - offstage: !enabled, - child: _SubLabeledWidget( - context, - 'Port', - Row(children: [ - SizedBox( - width: 95, - child: TextField( - controller: controller, - enabled: enabled && !locked, - onChanged: (_) => applyEnabled.value = true, - inputFormatters: [ - FilteringTextInputFormatter.allow(RegExp( - r'^([0-9]|[1-9]\d|[1-9]\d{2}|[1-9]\d{3}|[1-5]\d{4}|6[0-4]\d{3}|65[0-4]\d{2}|655[0-2]\d|6553[0-5])$')), - ], - decoration: const InputDecoration( - hintText: '21118', - contentPadding: - EdgeInsets.symmetric(vertical: 12, horizontal: 12), + () { + bool enabled = option2bool( + 'direct-server', bind.mainGetOptionSync(key: 'direct-server')); + if (!enabled) applyEnabled.value = false; + controller.text = bind.mainGetOptionSync(key: 'direct-access-port'); + return Offstage( + offstage: !enabled, + child: _SubLabeledWidget( + context, + 'Port', + Row(children: [ + SizedBox( + width: 95, + child: TextField( + controller: controller, + enabled: enabled && !locked, + onChanged: (_) => applyEnabled.value = true, + inputFormatters: [ + FilteringTextInputFormatter.allow(RegExp( + r'^([0-9]|[1-9]\d|[1-9]\d{2}|[1-9]\d{3}|[1-5]\d{4}|6[0-4]\d{3}|65[0-4]\d{2}|655[0-2]\d|6553[0-5])$')), + ], + decoration: const InputDecoration( + hintText: '21118', + contentPadding: + EdgeInsets.symmetric(vertical: 12, horizontal: 12), + ), + ).marginOnly(right: 15), + ), + Obx(() => ElevatedButton( + onPressed: applyEnabled.value && enabled && !locked + ? () async { + applyEnabled.value = false; + await bind.mainSetOption( + key: 'direct-access-port', + value: controller.text); + } + : null, + child: Text( + translate('Apply'), ), - ).marginOnly(right: 15), - ), - Obx(() => ElevatedButton( - onPressed: applyEnabled.value && enabled && !locked - ? () async { - applyEnabled.value = false; - await bind.mainSetOption( - key: 'direct-access-port', - value: controller.text); - } - : null, - child: Text( - translate('Apply'), - ), - )) - ]), - enabled: enabled && !locked, - ), - ); - }, - ), + )) + ]), + enabled: enabled && !locked, + ), + ); + }(), ]; } Widget whitelist() { bool enabled = !locked; - return futureBuilder(future: () async { - return await bind.mainGetOption(key: 'whitelist'); - }(), hasData: (data) { - RxBool hasWhitelist = (data as String).isNotEmpty.obs; - update() async { - hasWhitelist.value = - (await bind.mainGetOption(key: 'whitelist')).isNotEmpty; - } + RxBool hasWhitelist = + bind.mainGetOptionSync(key: 'whitelist').isNotEmpty.obs; + update() async { + hasWhitelist.value = bind.mainGetOptionSync(key: 'whitelist').isNotEmpty; + } - onChanged(bool? checked) async { - changeWhiteList(callback: update); - } + onChanged(bool? checked) async { + changeWhiteList(callback: update); + } - return GestureDetector( - child: Tooltip( - message: translate('whitelist_tip'), - child: Obx(() => Row( - children: [ - Checkbox( - value: hasWhitelist.value, - onChanged: enabled ? onChanged : null) + return GestureDetector( + child: Tooltip( + message: translate('whitelist_tip'), + child: Obx(() => Row( + children: [ + Checkbox( + value: hasWhitelist.value, + onChanged: enabled ? onChanged : null) + .marginOnly(right: 5), + Offstage( + offstage: !hasWhitelist.value, + child: const Icon(Icons.warning_amber_rounded, + color: Color.fromARGB(255, 255, 204, 0)) .marginOnly(right: 5), - Offstage( - offstage: !hasWhitelist.value, - child: const Icon(Icons.warning_amber_rounded, - color: Color.fromARGB(255, 255, 204, 0)) - .marginOnly(right: 5), - ), - Expanded( - child: Text( - translate('Use IP Whitelisting'), - style: - TextStyle(color: _disabledTextColor(context, enabled)), - )) - ], - )), - ), - onTap: () { - onChanged(!hasWhitelist.value); - }, - ).marginOnly(left: _kCheckBoxLeftMargin); - }); + ), + Expanded( + child: Text( + translate('Use IP Whitelisting'), + style: TextStyle(color: _disabledTextColor(context, enabled)), + )) + ], + )), + ), + onTap: () { + onChanged(!hasWhitelist.value); + }, + ).marginOnly(left: _kCheckBoxLeftMargin); } Widget hide_cm(bool enabled) { @@ -943,154 +923,150 @@ class _NetworkState extends State<_Network> with AutomaticKeepAliveClientMixin { } server(bool enabled) { - return futureBuilder(future: () async { - return await bind.mainGetOptions(); - }(), hasData: (data) { - // Setting page is not modal, oldOptions should only be used when getting options, never when setting. - Map oldOptions = jsonDecode(data! as String); - old(String key) { - return (oldOptions[key] ?? '').trim(); - } + // Setting page is not modal, oldOptions should only be used when getting options, never when setting. + Map oldOptions = + jsonDecode(bind.mainGetOptionsSync() as String); + old(String key) { + return (oldOptions[key] ?? '').trim(); + } - RxString idErrMsg = ''.obs; - RxString relayErrMsg = ''.obs; - RxString apiErrMsg = ''.obs; - var idController = - TextEditingController(text: old('custom-rendezvous-server')); - var relayController = TextEditingController(text: old('relay-server')); - var apiController = TextEditingController(text: old('api-server')); - var keyController = TextEditingController(text: old('key')); + RxString idErrMsg = ''.obs; + RxString relayErrMsg = ''.obs; + RxString apiErrMsg = ''.obs; + var idController = + TextEditingController(text: old('custom-rendezvous-server')); + var relayController = TextEditingController(text: old('relay-server')); + var apiController = TextEditingController(text: old('api-server')); + var keyController = TextEditingController(text: old('key')); - set(String idServer, String relayServer, String apiServer, - String key) async { - idServer = idServer.trim(); - relayServer = relayServer.trim(); - apiServer = apiServer.trim(); - key = key.trim(); - if (idServer.isNotEmpty) { - idErrMsg.value = - translate(await bind.mainTestIfValidServer(server: idServer)); - if (idErrMsg.isNotEmpty) { - return false; - } - } - if (relayServer.isNotEmpty) { - relayErrMsg.value = - translate(await bind.mainTestIfValidServer(server: relayServer)); - if (relayErrMsg.isNotEmpty) { - return false; - } - } - if (apiServer.isNotEmpty) { - if (!apiServer.startsWith('http://') && - !apiServer.startsWith('https://')) { - apiErrMsg.value = - '${translate("API Server")}: ${translate("invalid_http")}'; - return false; - } - } - final old = await bind.mainGetOption(key: 'custom-rendezvous-server'); - if (old.isNotEmpty && old != idServer) { - await gFFI.userModel.logOut(); - } - // should set one by one - await bind.mainSetOption( - key: 'custom-rendezvous-server', value: idServer); - await bind.mainSetOption(key: 'relay-server', value: relayServer); - await bind.mainSetOption(key: 'api-server', value: apiServer); - await bind.mainSetOption(key: 'key', value: key); - return true; - } - - submit() async { - bool result = await set(idController.text, relayController.text, - apiController.text, keyController.text); - if (result) { - setState(() {}); - showToast(translate('Successful')); - } else { - showToast(translate('Failed')); + set(String idServer, String relayServer, String apiServer, + String key) async { + idServer = idServer.trim(); + relayServer = relayServer.trim(); + apiServer = apiServer.trim(); + key = key.trim(); + if (idServer.isNotEmpty) { + idErrMsg.value = + translate(await bind.mainTestIfValidServer(server: idServer)); + if (idErrMsg.isNotEmpty) { + return false; } } + if (relayServer.isNotEmpty) { + relayErrMsg.value = + translate(await bind.mainTestIfValidServer(server: relayServer)); + if (relayErrMsg.isNotEmpty) { + return false; + } + } + if (apiServer.isNotEmpty) { + if (!apiServer.startsWith('http://') && + !apiServer.startsWith('https://')) { + apiErrMsg.value = + '${translate("API Server")}: ${translate("invalid_http")}'; + return false; + } + } + final old = await bind.mainGetOption(key: 'custom-rendezvous-server'); + if (old.isNotEmpty && old != idServer) { + await gFFI.userModel.logOut(); + } + // should set one by one + await bind.mainSetOption( + key: 'custom-rendezvous-server', value: idServer); + await bind.mainSetOption(key: 'relay-server', value: relayServer); + await bind.mainSetOption(key: 'api-server', value: apiServer); + await bind.mainSetOption(key: 'key', value: key); + return true; + } - import() { - Clipboard.getData(Clipboard.kTextPlain).then((value) { - final text = value?.text; - if (text != null && text.isNotEmpty) { - try { - final sc = ServerConfig.decode(text); - if (sc.idServer.isNotEmpty) { - idController.text = sc.idServer; - relayController.text = sc.relayServer; - apiController.text = sc.apiServer; - keyController.text = sc.key; - Future success = - set(sc.idServer, sc.relayServer, sc.apiServer, sc.key); - success.then((value) { - if (value) { - showToast( - translate('Import server configuration successfully')); - } else { - showToast(translate('Invalid server configuration')); - } - }); - } else { - showToast(translate('Invalid server configuration')); - } - } catch (e) { + submit() async { + bool result = await set(idController.text, relayController.text, + apiController.text, keyController.text); + if (result) { + setState(() {}); + showToast(translate('Successful')); + } else { + showToast(translate('Failed')); + } + } + + import() { + Clipboard.getData(Clipboard.kTextPlain).then((value) { + final text = value?.text; + if (text != null && text.isNotEmpty) { + try { + final sc = ServerConfig.decode(text); + if (sc.idServer.isNotEmpty) { + idController.text = sc.idServer; + relayController.text = sc.relayServer; + apiController.text = sc.apiServer; + keyController.text = sc.key; + Future success = + set(sc.idServer, sc.relayServer, sc.apiServer, sc.key); + success.then((value) { + if (value) { + showToast( + translate('Import server configuration successfully')); + } else { + showToast(translate('Invalid server configuration')); + } + }); + } else { showToast(translate('Invalid server configuration')); } - } else { - showToast(translate('Clipboard is empty')); + } catch (e) { + showToast(translate('Invalid server configuration')); } - }); - } + } else { + showToast(translate('Clipboard is empty')); + } + }); + } - export() { - final text = ServerConfig( - idServer: idController.text, - relayServer: relayController.text, - apiServer: apiController.text, - key: keyController.text) - .encode(); - debugPrint("ServerConfig export: $text"); + export() { + final text = ServerConfig( + idServer: idController.text, + relayServer: relayController.text, + apiServer: apiController.text, + key: keyController.text) + .encode(); + debugPrint("ServerConfig export: $text"); - Clipboard.setData(ClipboardData(text: text)); - showToast(translate('Export server configuration successfully')); - } + Clipboard.setData(ClipboardData(text: text)); + showToast(translate('Export server configuration successfully')); + } - bool secure = !enabled; - return _Card(title: 'ID/Relay Server', title_suffix: [ - Tooltip( - message: translate('Import Server Config'), + bool secure = !enabled; + return _Card(title: 'ID/Relay Server', title_suffix: [ + Tooltip( + message: translate('Import Server Config'), + child: IconButton( + icon: Icon(Icons.paste, color: Colors.grey), + onPressed: enabled ? import : null), + ), + Tooltip( + message: translate('Export Server Config'), child: IconButton( - icon: Icon(Icons.paste, color: Colors.grey), - onPressed: enabled ? import : null), - ), - Tooltip( - message: translate('Export Server Config'), - child: IconButton( - icon: Icon(Icons.copy, color: Colors.grey), - onPressed: enabled ? export : null)), - ], children: [ - Column( - children: [ - Obx(() => _LabeledTextField(context, 'ID Server', idController, - idErrMsg.value, enabled, secure)), - 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), - ], - ) - ]); - }); + icon: Icon(Icons.copy, color: Colors.grey), + onPressed: enabled ? export : null)), + ], children: [ + Column( + children: [ + Obx(() => _LabeledTextField(context, 'ID Server', idController, + idErrMsg.value, enabled, secure)), + 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), + ], + ) + ]); } } @@ -1662,53 +1638,47 @@ Widget _OptionCheckBox(BuildContext context, String label, String key, bool enabled = true, Icon? checkedIcon, bool? fakeValue}) { - return futureBuilder( - future: bind.mainGetOption(key: key), - hasData: (data) { - bool value = option2bool(key, data.toString()); - if (reverse) value = !value; - var ref = value.obs; - onChanged(option) async { - if (option != null) { - ref.value = option; - if (reverse) option = !option; - String value = bool2option(key, option); - await bind.mainSetOption(key: key, value: value); - update?.call(); + bool value = mainGetBoolOptionSync(key); + if (reverse) value = !value; + var ref = value.obs; + onChanged(option) async { + if (option != null) { + if (reverse) option = !option; + await mainSetBoolOption(key, option); + ref.value = mainGetBoolOptionSync(key); + update?.call(); + } + } + + if (fakeValue != null) { + ref.value = fakeValue; + enabled = false; + } + + return GestureDetector( + child: Obx( + () => Row( + children: [ + Checkbox(value: ref.value, onChanged: enabled ? onChanged : null) + .marginOnly(right: 5), + Offstage( + offstage: !ref.value || checkedIcon == null, + child: checkedIcon?.marginOnly(right: 5), + ), + Expanded( + child: Text( + translate(label), + style: TextStyle(color: _disabledTextColor(context, enabled)), + )) + ], + ), + ).marginOnly(left: _kCheckBoxLeftMargin), + onTap: enabled + ? () { + onChanged(!ref.value); } - } - - if (fakeValue != null) { - ref.value = fakeValue; - enabled = false; - } - - return GestureDetector( - child: Obx( - () => Row( - children: [ - Checkbox( - value: ref.value, onChanged: enabled ? onChanged : null) - .marginOnly(right: 5), - Offstage( - offstage: !ref.value || checkedIcon == null, - child: checkedIcon?.marginOnly(right: 5), - ), - Expanded( - child: Text( - translate(label), - style: TextStyle(color: _disabledTextColor(context, enabled)), - )) - ], - ), - ).marginOnly(left: _kCheckBoxLeftMargin), - onTap: enabled - ? () { - onChanged(!ref.value); - } - : null, - ); - }); + : null, + ); } // ignore: non_constant_identifier_names diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index 011c1b098..c25ac78b0 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -606,6 +606,10 @@ pub fn main_get_option(key: String) -> String { get_option(key) } +pub fn main_get_option_sync(key: String) -> SyncReturn { + SyncReturn(get_option(key)) +} + pub fn main_get_error() -> String { get_error() } @@ -626,6 +630,10 @@ pub fn main_get_options() -> String { get_options() } +pub fn main_get_options_sync() -> SyncReturn { + SyncReturn(get_options()) +} + pub fn main_set_options(json: String) { let map: HashMap = serde_json::from_str(&json).unwrap_or(HashMap::new()); if !map.is_empty() {