2023-10-11 19:03:34 +08:00
|
|
|
import 'dart:async';
|
2022-08-13 12:43:35 +08:00
|
|
|
import 'dart:convert';
|
2022-09-08 21:40:43 +08:00
|
|
|
import 'dart:io';
|
2022-08-13 12:43:35 +08:00
|
|
|
|
2022-09-15 17:31:28 +08:00
|
|
|
import 'package:file_picker/file_picker.dart';
|
2022-08-13 12:43:35 +08:00
|
|
|
import 'package:flutter/material.dart';
|
2022-08-15 11:08:42 +08:00
|
|
|
import 'package:flutter/services.dart';
|
2022-08-13 12:43:35 +08:00
|
|
|
import 'package:flutter_hbb/common.dart';
|
2023-08-31 20:30:20 +08:00
|
|
|
import 'package:flutter_hbb/common/widgets/setting_widgets.dart';
|
2022-11-10 21:25:12 +08:00
|
|
|
import 'package:flutter_hbb/consts.dart';
|
2022-08-13 12:43:35 +08:00
|
|
|
import 'package:flutter_hbb/desktop/pages/desktop_home_page.dart';
|
2022-09-23 18:28:40 +08:00
|
|
|
import 'package:flutter_hbb/desktop/pages/desktop_tab_page.dart';
|
2022-08-13 12:43:35 +08:00
|
|
|
import 'package:flutter_hbb/models/platform_model.dart';
|
|
|
|
import 'package:flutter_hbb/models/server_model.dart';
|
2023-05-09 19:47:26 +08:00
|
|
|
import 'package:flutter_hbb/plugin/manager.dart';
|
2023-05-10 18:58:45 +08:00
|
|
|
import 'package:flutter_hbb/plugin/widgets/desktop_settings.dart';
|
2022-08-13 12:43:35 +08:00
|
|
|
import 'package:get/get.dart';
|
|
|
|
import 'package:provider/provider.dart';
|
2022-09-15 17:31:28 +08:00
|
|
|
import 'package:url_launcher/url_launcher.dart';
|
2022-08-15 11:08:42 +08:00
|
|
|
import 'package:url_launcher/url_launcher_string.dart';
|
2022-09-21 18:49:28 +08:00
|
|
|
import 'package:flutter_hbb/desktop/widgets/scroll_wrapper.dart';
|
2022-08-15 11:08:42 +08:00
|
|
|
|
2022-09-16 21:52:08 +08:00
|
|
|
import '../../common/widgets/dialog.dart';
|
2023-01-08 22:30:34 +08:00
|
|
|
import '../../common/widgets/login.dart';
|
2022-09-16 21:52:08 +08:00
|
|
|
|
2023-02-28 15:23:25 +08:00
|
|
|
const double _kTabWidth = 200;
|
2022-08-15 11:08:42 +08:00
|
|
|
const double _kTabHeight = 42;
|
2022-09-24 09:58:57 +08:00
|
|
|
const double _kCardFixedWidth = 540;
|
2022-08-15 11:08:42 +08:00
|
|
|
const double _kCardLeftMargin = 15;
|
|
|
|
const double _kContentHMargin = 15;
|
|
|
|
const double _kContentHSubMargin = _kContentHMargin + 33;
|
|
|
|
const double _kCheckBoxLeftMargin = 10;
|
|
|
|
const double _kRadioLeftMargin = 10;
|
|
|
|
const double _kListViewBottomMargin = 15;
|
|
|
|
const double _kTitleFontSize = 20;
|
|
|
|
const double _kContentFontSize = 15;
|
|
|
|
const Color _accentColor = MyTheme.accent;
|
2022-11-10 21:25:12 +08:00
|
|
|
const String _kSettingPageControllerTag = 'settingPageController';
|
|
|
|
const String _kSettingPageIndexTag = 'settingPageIndex';
|
2023-02-01 19:56:57 +08:00
|
|
|
const int _kPageCount = 6;
|
2022-08-15 11:08:42 +08:00
|
|
|
|
|
|
|
class _TabInfo {
|
|
|
|
late final String label;
|
|
|
|
late final IconData unselected;
|
|
|
|
late final IconData selected;
|
2022-08-23 19:49:11 +08:00
|
|
|
_TabInfo(this.label, this.unselected, this.selected);
|
2022-08-15 11:08:42 +08:00
|
|
|
}
|
2022-08-11 16:03:04 +08:00
|
|
|
|
|
|
|
class DesktopSettingPage extends StatefulWidget {
|
2022-09-23 18:28:40 +08:00
|
|
|
final int initialPage;
|
|
|
|
|
|
|
|
const DesktopSettingPage({Key? key, required this.initialPage})
|
|
|
|
: super(key: key);
|
2022-08-11 16:03:04 +08:00
|
|
|
|
|
|
|
@override
|
|
|
|
State<DesktopSettingPage> createState() => _DesktopSettingPageState();
|
2022-09-23 18:28:40 +08:00
|
|
|
|
|
|
|
static void switch2page(int page) {
|
2023-02-01 19:56:57 +08:00
|
|
|
if (page >= _kPageCount) return;
|
2022-09-23 18:28:40 +08:00
|
|
|
try {
|
|
|
|
if (Get.isRegistered<PageController>(tag: _kSettingPageControllerTag)) {
|
|
|
|
DesktopTabPage.onAddSetting(initialPage: page);
|
|
|
|
PageController controller = Get.find(tag: _kSettingPageControllerTag);
|
|
|
|
RxInt selectedIndex = Get.find(tag: _kSettingPageIndexTag);
|
|
|
|
selectedIndex.value = page;
|
|
|
|
controller.jumpToPage(page);
|
|
|
|
} else {
|
|
|
|
DesktopTabPage.onAddSetting(initialPage: page);
|
|
|
|
}
|
|
|
|
} catch (e) {
|
2022-12-09 10:49:47 +08:00
|
|
|
debugPrintStack(label: '$e');
|
2022-09-23 18:28:40 +08:00
|
|
|
}
|
|
|
|
}
|
2022-08-11 16:03:04 +08:00
|
|
|
}
|
|
|
|
|
2022-08-13 12:43:35 +08:00
|
|
|
class _DesktopSettingPageState extends State<DesktopSettingPage>
|
|
|
|
with TickerProviderStateMixin, AutomaticKeepAliveClientMixin {
|
2022-08-15 11:08:42 +08:00
|
|
|
late PageController controller;
|
2022-09-23 18:28:40 +08:00
|
|
|
late RxInt selectedIndex;
|
2022-08-13 12:43:35 +08:00
|
|
|
|
|
|
|
@override
|
|
|
|
bool get wantKeepAlive => true;
|
|
|
|
|
|
|
|
@override
|
|
|
|
void initState() {
|
|
|
|
super.initState();
|
2023-02-01 19:56:57 +08:00
|
|
|
selectedIndex =
|
|
|
|
(widget.initialPage < _kPageCount ? widget.initialPage : 0).obs;
|
2022-09-23 18:28:40 +08:00
|
|
|
Get.put<RxInt>(selectedIndex, tag: _kSettingPageIndexTag);
|
|
|
|
controller = PageController(initialPage: widget.initialPage);
|
|
|
|
Get.put<PageController>(controller, tag: _kSettingPageControllerTag);
|
2023-09-26 19:58:51 +08:00
|
|
|
controller.addListener(() {
|
|
|
|
if (controller.page != null) {
|
|
|
|
selectedIndex.value = controller.page!.toInt();
|
|
|
|
}
|
|
|
|
});
|
2022-09-23 18:28:40 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
void dispose() {
|
|
|
|
super.dispose();
|
|
|
|
Get.delete<PageController>(tag: _kSettingPageControllerTag);
|
|
|
|
Get.delete<RxInt>(tag: _kSettingPageIndexTag);
|
2022-08-13 12:43:35 +08:00
|
|
|
}
|
|
|
|
|
2023-04-22 22:21:02 +08:00
|
|
|
List<_TabInfo> _settingTabs() {
|
|
|
|
final List<_TabInfo> settingTabs = <_TabInfo>[
|
|
|
|
_TabInfo('General', Icons.settings_outlined, Icons.settings),
|
2024-03-15 00:26:53 +08:00
|
|
|
if (!bind.isOutgoingOnly() && !bind.isDisableSettings())
|
Fix/custom client styles (#7373)
* Fix. qs styles
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* custom client, options
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Move logo.svg to icon.svg
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Refact. Custom client, connection status ui.
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Custom client ui. Disable settings, hide "Change password"
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Custom client, logo align center
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Custom client, refact, outgoing ui
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Custom client, outgoing, settings icon
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Custom client, powered by RustDesk
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Custom client, remove unused SizeBox
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Update config.rs
* Update flutter_ffi.rs
---------
Signed-off-by: fufesou <shuanglongchen@yeah.net>
Co-authored-by: RustDesk <71636191+rustdesk@users.noreply.github.com>
2024-03-14 11:36:14 +08:00
|
|
|
_TabInfo('Security', Icons.enhanced_encryption_outlined,
|
|
|
|
Icons.enhanced_encryption),
|
2024-03-15 00:26:53 +08:00
|
|
|
if (!bind.isDisableSettings())
|
|
|
|
_TabInfo('Network', Icons.link_outlined, Icons.link),
|
Fix/custom client styles (#7373)
* Fix. qs styles
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* custom client, options
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Move logo.svg to icon.svg
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Refact. Custom client, connection status ui.
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Custom client ui. Disable settings, hide "Change password"
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Custom client, logo align center
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Custom client, refact, outgoing ui
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Custom client, outgoing, settings icon
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Custom client, powered by RustDesk
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Custom client, remove unused SizeBox
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Update config.rs
* Update flutter_ffi.rs
---------
Signed-off-by: fufesou <shuanglongchen@yeah.net>
Co-authored-by: RustDesk <71636191+rustdesk@users.noreply.github.com>
2024-03-14 11:36:14 +08:00
|
|
|
if (!bind.isIncomingOnly())
|
|
|
|
_TabInfo(
|
|
|
|
'Display', Icons.desktop_windows_outlined, Icons.desktop_windows),
|
2024-03-28 11:38:11 +08:00
|
|
|
if (!isWeb && !bind.isIncomingOnly() && bind.pluginFeatureIsEnabled())
|
Fix/custom client styles (#7373)
* Fix. qs styles
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* custom client, options
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Move logo.svg to icon.svg
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Refact. Custom client, connection status ui.
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Custom client ui. Disable settings, hide "Change password"
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Custom client, logo align center
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Custom client, refact, outgoing ui
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Custom client, outgoing, settings icon
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Custom client, powered by RustDesk
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Custom client, remove unused SizeBox
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Update config.rs
* Update flutter_ffi.rs
---------
Signed-off-by: fufesou <shuanglongchen@yeah.net>
Co-authored-by: RustDesk <71636191+rustdesk@users.noreply.github.com>
2024-03-14 11:36:14 +08:00
|
|
|
_TabInfo('Plugin', Icons.extension_outlined, Icons.extension),
|
|
|
|
if (!bind.isDisableAccount())
|
|
|
|
_TabInfo('Account', Icons.person_outline, Icons.person),
|
2023-04-22 22:21:02 +08:00
|
|
|
_TabInfo('About', Icons.info_outline, Icons.info)
|
|
|
|
];
|
|
|
|
return settingTabs;
|
|
|
|
}
|
|
|
|
|
|
|
|
List<Widget> _children() {
|
|
|
|
final children = [
|
|
|
|
_General(),
|
2024-03-15 00:26:53 +08:00
|
|
|
if (!bind.isOutgoingOnly() && !bind.isDisableSettings()) _Safety(),
|
|
|
|
if (!bind.isDisableSettings()) _Network(),
|
Fix/custom client styles (#7373)
* Fix. qs styles
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* custom client, options
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Move logo.svg to icon.svg
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Refact. Custom client, connection status ui.
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Custom client ui. Disable settings, hide "Change password"
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Custom client, logo align center
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Custom client, refact, outgoing ui
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Custom client, outgoing, settings icon
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Custom client, powered by RustDesk
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Custom client, remove unused SizeBox
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Update config.rs
* Update flutter_ffi.rs
---------
Signed-off-by: fufesou <shuanglongchen@yeah.net>
Co-authored-by: RustDesk <71636191+rustdesk@users.noreply.github.com>
2024-03-14 11:36:14 +08:00
|
|
|
if (!bind.isIncomingOnly()) _Display(),
|
2024-03-28 11:38:11 +08:00
|
|
|
if (!isWeb && !bind.isIncomingOnly() && bind.pluginFeatureIsEnabled())
|
|
|
|
_Plugin(),
|
Fix/custom client styles (#7373)
* Fix. qs styles
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* custom client, options
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Move logo.svg to icon.svg
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Refact. Custom client, connection status ui.
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Custom client ui. Disable settings, hide "Change password"
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Custom client, logo align center
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Custom client, refact, outgoing ui
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Custom client, outgoing, settings icon
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Custom client, powered by RustDesk
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Custom client, remove unused SizeBox
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Update config.rs
* Update flutter_ffi.rs
---------
Signed-off-by: fufesou <shuanglongchen@yeah.net>
Co-authored-by: RustDesk <71636191+rustdesk@users.noreply.github.com>
2024-03-14 11:36:14 +08:00
|
|
|
if (!bind.isDisableAccount()) _Account(),
|
2023-04-22 22:21:02 +08:00
|
|
|
_About(),
|
|
|
|
];
|
|
|
|
return children;
|
|
|
|
}
|
|
|
|
|
2022-08-13 12:43:35 +08:00
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
super.build(context);
|
|
|
|
return Scaffold(
|
2023-02-23 23:49:31 +08:00
|
|
|
backgroundColor: Theme.of(context).colorScheme.background,
|
2022-08-13 12:43:35 +08:00
|
|
|
body: Row(
|
|
|
|
children: <Widget>[
|
2022-09-08 21:40:43 +08:00
|
|
|
SizedBox(
|
2022-08-15 11:08:42 +08:00
|
|
|
width: _kTabWidth,
|
|
|
|
child: Column(
|
|
|
|
children: [
|
|
|
|
_header(),
|
2023-04-22 22:21:02 +08:00
|
|
|
Flexible(child: _listView(tabs: _settingTabs())),
|
2022-08-15 11:08:42 +08:00
|
|
|
],
|
|
|
|
),
|
2022-08-13 12:43:35 +08:00
|
|
|
),
|
2023-03-04 14:28:43 +08:00
|
|
|
const VerticalDivider(width: 1),
|
2022-08-13 12:43:35 +08:00
|
|
|
Expanded(
|
2022-08-20 19:57:16 +08:00
|
|
|
child: Container(
|
2022-09-23 16:31:50 +08:00
|
|
|
color: Theme.of(context).scaffoldBackgroundColor,
|
2022-09-21 18:49:28 +08:00
|
|
|
child: DesktopScrollWrapper(
|
|
|
|
scrollController: controller,
|
|
|
|
child: PageView(
|
|
|
|
controller: controller,
|
2023-09-26 19:58:51 +08:00
|
|
|
physics: NeverScrollableScrollPhysics(),
|
2023-04-22 22:21:02 +08:00
|
|
|
children: _children(),
|
2022-09-21 18:49:28 +08:00
|
|
|
)),
|
2022-08-13 12:43:35 +08:00
|
|
|
),
|
|
|
|
)
|
|
|
|
],
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-08-15 11:08:42 +08:00
|
|
|
Widget _header() {
|
|
|
|
return Row(
|
|
|
|
children: [
|
|
|
|
SizedBox(
|
|
|
|
height: 62,
|
|
|
|
child: Text(
|
|
|
|
translate('Settings'),
|
|
|
|
textAlign: TextAlign.left,
|
2022-09-08 21:40:43 +08:00
|
|
|
style: const TextStyle(
|
2022-08-15 11:08:42 +08:00
|
|
|
color: _accentColor,
|
|
|
|
fontSize: _kTitleFontSize,
|
|
|
|
fontWeight: FontWeight.w400,
|
|
|
|
),
|
|
|
|
),
|
|
|
|
).marginOnly(left: 20, top: 10),
|
2022-09-08 21:40:43 +08:00
|
|
|
const Spacer(),
|
2022-08-15 11:08:42 +08:00
|
|
|
],
|
2022-08-13 12:43:35 +08:00
|
|
|
);
|
|
|
|
}
|
2022-08-15 11:08:42 +08:00
|
|
|
|
|
|
|
Widget _listView({required List<_TabInfo> tabs}) {
|
2022-09-21 18:49:28 +08:00
|
|
|
final scrollController = ScrollController();
|
|
|
|
return DesktopScrollWrapper(
|
|
|
|
scrollController: scrollController,
|
|
|
|
child: ListView(
|
2023-02-13 16:18:46 +08:00
|
|
|
physics: DraggableNeverScrollableScrollPhysics(),
|
2022-09-21 18:49:28 +08:00
|
|
|
controller: scrollController,
|
|
|
|
children: tabs
|
|
|
|
.asMap()
|
|
|
|
.entries
|
|
|
|
.map((tab) => _listItem(tab: tab.value, index: tab.key))
|
|
|
|
.toList(),
|
|
|
|
));
|
2022-08-15 11:08:42 +08:00
|
|
|
}
|
|
|
|
|
2022-08-23 19:49:11 +08:00
|
|
|
Widget _listItem({required _TabInfo tab, required int index}) {
|
2022-08-15 11:08:42 +08:00
|
|
|
return Obx(() {
|
2022-09-08 21:40:43 +08:00
|
|
|
bool selected = index == selectedIndex.value;
|
|
|
|
return SizedBox(
|
2022-08-15 11:08:42 +08:00
|
|
|
width: _kTabWidth,
|
|
|
|
height: _kTabHeight,
|
|
|
|
child: InkWell(
|
|
|
|
onTap: () {
|
2022-09-08 21:40:43 +08:00
|
|
|
if (selectedIndex.value != index) {
|
2022-08-23 19:49:11 +08:00
|
|
|
controller.jumpToPage(index);
|
2022-08-15 11:08:42 +08:00
|
|
|
}
|
2022-09-08 21:40:43 +08:00
|
|
|
selectedIndex.value = index;
|
2022-08-15 11:08:42 +08:00
|
|
|
},
|
|
|
|
child: Row(children: [
|
|
|
|
Container(
|
|
|
|
width: 4,
|
|
|
|
height: _kTabHeight * 0.7,
|
|
|
|
color: selected ? _accentColor : null,
|
|
|
|
),
|
|
|
|
Icon(
|
|
|
|
selected ? tab.selected : tab.unselected,
|
|
|
|
color: selected ? _accentColor : null,
|
|
|
|
size: 20,
|
|
|
|
).marginOnly(left: 13, right: 10),
|
|
|
|
Text(
|
|
|
|
translate(tab.label),
|
|
|
|
style: TextStyle(
|
|
|
|
color: selected ? _accentColor : null,
|
|
|
|
fontWeight: FontWeight.w400,
|
|
|
|
fontSize: _kContentFontSize),
|
|
|
|
),
|
|
|
|
]),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
});
|
|
|
|
}
|
2022-08-13 12:43:35 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
//#region pages
|
|
|
|
|
2022-09-08 21:40:43 +08:00
|
|
|
class _General extends StatefulWidget {
|
|
|
|
const _General({Key? key}) : super(key: key);
|
|
|
|
|
|
|
|
@override
|
|
|
|
State<_General> createState() => _GeneralState();
|
|
|
|
}
|
|
|
|
|
|
|
|
class _GeneralState extends State<_General> {
|
2023-06-06 21:51:40 +08:00
|
|
|
final RxBool serviceStop = Get.find<RxBool>(tag: 'stop-service');
|
2023-08-03 23:14:40 +08:00
|
|
|
RxBool serviceBtnEnabled = true.obs;
|
2023-06-06 21:51:40 +08:00
|
|
|
|
2022-09-08 21:40:43 +08:00
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
2022-09-21 18:49:28 +08:00
|
|
|
final scrollController = ScrollController();
|
|
|
|
return DesktopScrollWrapper(
|
|
|
|
scrollController: scrollController,
|
|
|
|
child: ListView(
|
2023-02-13 16:18:46 +08:00
|
|
|
physics: DraggableNeverScrollableScrollPhysics(),
|
2022-09-21 18:49:28 +08:00
|
|
|
controller: scrollController,
|
|
|
|
children: [
|
2023-06-06 21:51:40 +08:00
|
|
|
service(),
|
2022-09-21 18:49:28 +08:00
|
|
|
theme(),
|
|
|
|
hwcodec(),
|
|
|
|
audio(context),
|
2022-09-15 17:31:28 +08:00
|
|
|
record(context),
|
2022-09-21 18:49:28 +08:00
|
|
|
_Card(title: 'Language', children: [language()]),
|
2022-10-13 20:19:05 +08:00
|
|
|
other()
|
2022-09-21 18:49:28 +08:00
|
|
|
],
|
|
|
|
).marginOnly(bottom: _kListViewBottomMargin));
|
2022-09-08 21:40:43 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
Widget theme() {
|
2022-09-21 23:32:59 +08:00
|
|
|
final current = MyTheme.getThemeModePreference().toShortString();
|
|
|
|
onChanged(String value) {
|
|
|
|
MyTheme.changeDarkMode(MyTheme.themeModeFromString(value));
|
2022-09-08 21:40:43 +08:00
|
|
|
setState(() {});
|
|
|
|
}
|
|
|
|
|
|
|
|
return _Card(title: 'Theme', children: [
|
2022-09-21 23:32:59 +08:00
|
|
|
_Radio<String>(context,
|
2022-11-10 21:25:12 +08:00
|
|
|
value: 'light',
|
2022-09-21 23:32:59 +08:00
|
|
|
groupValue: current,
|
2022-11-10 21:25:12 +08:00
|
|
|
label: 'Light',
|
2022-09-21 23:32:59 +08:00
|
|
|
onChanged: onChanged),
|
|
|
|
_Radio<String>(context,
|
2022-11-10 21:25:12 +08:00
|
|
|
value: 'dark',
|
2022-09-21 23:32:59 +08:00
|
|
|
groupValue: current,
|
2022-11-10 21:25:12 +08:00
|
|
|
label: 'Dark',
|
2022-09-21 23:32:59 +08:00
|
|
|
onChanged: onChanged),
|
|
|
|
_Radio<String>(context,
|
2022-11-10 21:25:12 +08:00
|
|
|
value: 'system',
|
2022-09-21 23:32:59 +08:00
|
|
|
groupValue: current,
|
2022-11-10 21:25:12 +08:00
|
|
|
label: 'Follow System',
|
2022-09-21 23:32:59 +08:00
|
|
|
onChanged: onChanged),
|
2022-09-08 21:40:43 +08:00
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
2023-06-06 21:51:40 +08:00
|
|
|
Widget service() {
|
Fix/custom client styles (#7373)
* Fix. qs styles
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* custom client, options
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Move logo.svg to icon.svg
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Refact. Custom client, connection status ui.
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Custom client ui. Disable settings, hide "Change password"
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Custom client, logo align center
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Custom client, refact, outgoing ui
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Custom client, outgoing, settings icon
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Custom client, powered by RustDesk
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Custom client, remove unused SizeBox
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Update config.rs
* Update flutter_ffi.rs
---------
Signed-off-by: fufesou <shuanglongchen@yeah.net>
Co-authored-by: RustDesk <71636191+rustdesk@users.noreply.github.com>
2024-03-14 11:36:14 +08:00
|
|
|
if (bind.isOutgoingOnly()) {
|
|
|
|
return const Offstage();
|
|
|
|
}
|
|
|
|
|
2023-06-06 21:51:40 +08:00
|
|
|
return _Card(title: 'Service', children: [
|
|
|
|
Obx(() => _Button(serviceStop.value ? 'Start' : 'Stop', () {
|
|
|
|
() async {
|
2023-08-03 23:14:40 +08:00
|
|
|
serviceBtnEnabled.value = false;
|
2023-06-06 21:51:40 +08:00
|
|
|
await start_service(serviceStop.value);
|
|
|
|
// enable the button after 1 second
|
|
|
|
Future.delayed(const Duration(seconds: 1), () {
|
2023-08-03 23:14:40 +08:00
|
|
|
serviceBtnEnabled.value = true;
|
2023-06-06 21:51:40 +08:00
|
|
|
});
|
|
|
|
}();
|
2023-08-03 23:14:40 +08:00
|
|
|
}, enabled: serviceBtnEnabled.value))
|
2023-06-06 21:51:40 +08:00
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
2022-10-13 20:19:05 +08:00
|
|
|
Widget other() {
|
Fix/custom client styles (#7373)
* Fix. qs styles
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* custom client, options
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Move logo.svg to icon.svg
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Refact. Custom client, connection status ui.
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Custom client ui. Disable settings, hide "Change password"
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Custom client, logo align center
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Custom client, refact, outgoing ui
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Custom client, outgoing, settings icon
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Custom client, powered by RustDesk
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Custom client, remove unused SizeBox
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Update config.rs
* Update flutter_ffi.rs
---------
Signed-off-by: fufesou <shuanglongchen@yeah.net>
Co-authored-by: RustDesk <71636191+rustdesk@users.noreply.github.com>
2024-03-14 11:36:14 +08:00
|
|
|
final children = <Widget>[
|
|
|
|
if (!bind.isIncomingOnly())
|
|
|
|
_OptionCheckBox(context, 'Confirm before closing multiple tabs',
|
|
|
|
'enable-confirm-closing-tabs',
|
|
|
|
isServer: false),
|
2023-08-06 14:05:48 +08:00
|
|
|
_OptionCheckBox(context, 'Adaptive bitrate', 'enable-abr'),
|
Fix/custom client styles (#7373)
* Fix. qs styles
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* custom client, options
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Move logo.svg to icon.svg
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Refact. Custom client, connection status ui.
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Custom client ui. Disable settings, hide "Change password"
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Custom client, logo align center
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Custom client, refact, outgoing ui
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Custom client, outgoing, settings icon
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Custom client, powered by RustDesk
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Custom client, remove unused SizeBox
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Update config.rs
* Update flutter_ffi.rs
---------
Signed-off-by: fufesou <shuanglongchen@yeah.net>
Co-authored-by: RustDesk <71636191+rustdesk@users.noreply.github.com>
2024-03-14 11:36:14 +08:00
|
|
|
wallpaper(),
|
|
|
|
if (!bind.isIncomingOnly()) ...[
|
Feat. Quick support, ui (#7267)
* Feat. QS ui
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Remove 'Quick support'
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* add help card
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* use addPostFrameCallback to get child size
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Fix. qs, set home window size
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Qs, set setResizable for settings page
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Qs, help cards margin bottom
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Qs, online status, padding
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Qs, online status, padding
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Qs, online status, use margin instead of padding
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Qs, fix, start cm window
Signed-off-by: fufesou <shuanglongchen@yeah.net>
---------
Signed-off-by: fufesou <shuanglongchen@yeah.net>
2024-02-27 17:02:10 +08:00
|
|
|
_OptionCheckBox(
|
|
|
|
context,
|
|
|
|
'Open connection in new tab',
|
|
|
|
kOptionOpenNewConnInTabs,
|
|
|
|
isServer: false,
|
|
|
|
),
|
|
|
|
// though this is related to GUI, but opengl problem affects all users, so put in config rather than local
|
|
|
|
Tooltip(
|
|
|
|
message: translate('software_render_tip'),
|
|
|
|
child: _OptionCheckBox(context, "Always use software rendering",
|
|
|
|
'allow-always-software-render'),
|
|
|
|
),
|
|
|
|
_OptionCheckBox(
|
|
|
|
context,
|
|
|
|
'Check for software update on startup',
|
|
|
|
'enable-check-update',
|
|
|
|
isServer: false,
|
|
|
|
)
|
Fix/custom client styles (#7373)
* Fix. qs styles
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* custom client, options
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Move logo.svg to icon.svg
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Refact. Custom client, connection status ui.
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Custom client ui. Disable settings, hide "Change password"
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Custom client, logo align center
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Custom client, refact, outgoing ui
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Custom client, outgoing, settings icon
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Custom client, powered by RustDesk
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Custom client, remove unused SizeBox
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Update config.rs
* Update flutter_ffi.rs
---------
Signed-off-by: fufesou <shuanglongchen@yeah.net>
Co-authored-by: RustDesk <71636191+rustdesk@users.noreply.github.com>
2024-03-14 11:36:14 +08:00
|
|
|
],
|
|
|
|
];
|
2023-07-29 15:45:16 +08:00
|
|
|
if (bind.mainShowOption(key: 'allow-linux-headless')) {
|
|
|
|
children.add(_OptionCheckBox(
|
|
|
|
context, 'Allow linux headless', 'allow-linux-headless'));
|
|
|
|
}
|
|
|
|
return _Card(title: 'Other', children: children);
|
2022-09-08 21:40:43 +08:00
|
|
|
}
|
|
|
|
|
2023-10-14 18:50:41 +08:00
|
|
|
Widget wallpaper() {
|
Fix/custom client styles (#7373)
* Fix. qs styles
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* custom client, options
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Move logo.svg to icon.svg
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Refact. Custom client, connection status ui.
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Custom client ui. Disable settings, hide "Change password"
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Custom client, logo align center
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Custom client, refact, outgoing ui
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Custom client, outgoing, settings icon
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Custom client, powered by RustDesk
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Custom client, remove unused SizeBox
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Update config.rs
* Update flutter_ffi.rs
---------
Signed-off-by: fufesou <shuanglongchen@yeah.net>
Co-authored-by: RustDesk <71636191+rustdesk@users.noreply.github.com>
2024-03-14 11:36:14 +08:00
|
|
|
if (bind.isOutgoingOnly()) {
|
|
|
|
return const Offstage();
|
|
|
|
}
|
|
|
|
|
2023-10-14 18:50:41 +08:00
|
|
|
return futureBuilder(future: () async {
|
|
|
|
final support = await bind.mainSupportRemoveWallpaper();
|
|
|
|
return support;
|
|
|
|
}(), hasData: (data) {
|
|
|
|
if (data is bool && data == true) {
|
|
|
|
final option = 'allow-remove-wallpaper';
|
|
|
|
bool value = mainGetBoolOptionSync(option);
|
|
|
|
return Row(
|
|
|
|
children: [
|
|
|
|
Flexible(
|
|
|
|
child: _OptionCheckBox(
|
|
|
|
context,
|
|
|
|
'Remove wallpaper during incoming sessions',
|
|
|
|
option,
|
|
|
|
update: () {
|
|
|
|
setState(() {});
|
|
|
|
},
|
|
|
|
),
|
|
|
|
),
|
|
|
|
if (value)
|
|
|
|
_CountDownButton(
|
|
|
|
text: 'Test',
|
|
|
|
second: 5,
|
|
|
|
onPressed: () {
|
|
|
|
bind.mainTestWallpaper(second: 5);
|
|
|
|
},
|
|
|
|
)
|
|
|
|
],
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return Offstage();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2022-09-08 21:40:43 +08:00
|
|
|
Widget hwcodec() {
|
2024-01-02 16:58:10 +08:00
|
|
|
final hwcodec = bind.mainHasHwcodec();
|
2024-04-12 17:26:24 +08:00
|
|
|
final vram = bind.mainHasVram();
|
2022-09-16 19:43:28 +08:00
|
|
|
return Offstage(
|
2024-04-12 17:26:24 +08:00
|
|
|
offstage: !(hwcodec || vram),
|
2022-09-16 19:43:28 +08:00
|
|
|
child: _Card(title: 'Hardware Codec', children: [
|
2024-04-18 13:12:45 +08:00
|
|
|
_OptionCheckBox(
|
|
|
|
context,
|
|
|
|
'Enable hardware codec',
|
|
|
|
'enable-hwcodec',
|
|
|
|
update: () {
|
|
|
|
if (mainGetBoolOptionSync('enable-hwcodec')) {
|
|
|
|
bind.mainCheckHwcodec();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
)
|
2022-09-16 19:43:28 +08:00
|
|
|
]),
|
|
|
|
);
|
2022-09-08 21:40:43 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
Widget audio(BuildContext context) {
|
Fix/custom client styles (#7373)
* Fix. qs styles
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* custom client, options
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Move logo.svg to icon.svg
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Refact. Custom client, connection status ui.
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Custom client ui. Disable settings, hide "Change password"
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Custom client, logo align center
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Custom client, refact, outgoing ui
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Custom client, outgoing, settings icon
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Custom client, powered by RustDesk
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Custom client, remove unused SizeBox
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Update config.rs
* Update flutter_ffi.rs
---------
Signed-off-by: fufesou <shuanglongchen@yeah.net>
Co-authored-by: RustDesk <71636191+rustdesk@users.noreply.github.com>
2024-03-14 11:36:14 +08:00
|
|
|
if (bind.isOutgoingOnly()) {
|
|
|
|
return const Offstage();
|
|
|
|
}
|
|
|
|
|
2022-09-08 21:40:43 +08:00
|
|
|
String getDefault() {
|
2024-03-24 11:23:06 +08:00
|
|
|
if (isWindows) return translate('System Sound');
|
2022-11-10 21:25:12 +08:00
|
|
|
return '';
|
2022-09-08 21:40:43 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
Future<String> getValue() async {
|
|
|
|
String device = await bind.mainGetOption(key: 'audio-input');
|
|
|
|
if (device.isNotEmpty) {
|
|
|
|
return device;
|
|
|
|
} else {
|
|
|
|
return getDefault();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
setDevice(String device) {
|
2022-11-10 21:25:12 +08:00
|
|
|
if (device == getDefault()) device = '';
|
2022-09-08 21:40:43 +08:00
|
|
|
bind.mainSetOption(key: 'audio-input', value: device);
|
|
|
|
}
|
|
|
|
|
2023-02-23 14:30:29 +08:00
|
|
|
return futureBuilder(future: () async {
|
2022-09-08 21:40:43 +08:00
|
|
|
List<String> devices = (await bind.mainGetSoundInputs()).toList();
|
2024-03-24 11:23:06 +08:00
|
|
|
if (isWindows) {
|
2023-04-13 16:06:46 +08:00
|
|
|
devices.insert(0, translate('System Sound'));
|
2022-09-08 21:40:43 +08:00
|
|
|
}
|
|
|
|
String current = await getValue();
|
|
|
|
return {'devices': devices, 'current': current};
|
|
|
|
}(), hasData: (data) {
|
|
|
|
String currentDevice = data['current'];
|
|
|
|
List<String> devices = data['devices'] as List<String>;
|
|
|
|
if (devices.isEmpty) {
|
|
|
|
return const Offstage();
|
|
|
|
}
|
|
|
|
return _Card(title: 'Audio Input Device', children: [
|
2022-09-13 19:38:50 +08:00
|
|
|
...devices.map((device) => _Radio<String>(context,
|
|
|
|
value: device,
|
|
|
|
groupValue: currentDevice,
|
2022-10-22 12:03:57 +08:00
|
|
|
autoNewLine: false,
|
2022-09-13 19:38:50 +08:00
|
|
|
label: device, onChanged: (value) {
|
|
|
|
setDevice(value);
|
|
|
|
setState(() {});
|
|
|
|
}))
|
2022-09-08 21:40:43 +08:00
|
|
|
]);
|
|
|
|
});
|
|
|
|
}
|
2022-08-13 12:43:35 +08:00
|
|
|
|
2022-09-15 17:31:28 +08:00
|
|
|
Widget record(BuildContext context) {
|
2023-02-23 14:30:29 +08:00
|
|
|
return futureBuilder(future: () async {
|
2022-09-15 17:31:28 +08:00
|
|
|
String defaultDirectory = await bind.mainDefaultVideoSaveDirectory();
|
2022-10-12 16:06:15 +08:00
|
|
|
// canLaunchUrl blocked on windows portable, user SYSTEM
|
2023-07-23 17:07:30 +08:00
|
|
|
return {'dir': defaultDirectory, 'canlaunch': true};
|
2022-09-15 17:31:28 +08:00
|
|
|
}(), hasData: (data) {
|
|
|
|
Map<String, dynamic> map = data as Map<String, dynamic>;
|
|
|
|
String dir = map['dir']!;
|
2023-07-23 17:07:30 +08:00
|
|
|
String customDirectory =
|
|
|
|
bind.mainGetOptionSync(key: 'video-save-directory');
|
|
|
|
if (customDirectory.isNotEmpty) {
|
|
|
|
dir = customDirectory;
|
|
|
|
}
|
2022-09-15 17:31:28 +08:00
|
|
|
bool canlaunch = map['canlaunch']! as bool;
|
|
|
|
|
|
|
|
return _Card(title: 'Recording', children: [
|
|
|
|
_OptionCheckBox(context, 'Automatically record incoming sessions',
|
|
|
|
'allow-auto-record-incoming'),
|
|
|
|
Row(
|
|
|
|
children: [
|
2022-11-10 21:25:12 +08:00
|
|
|
Text('${translate("Directory")}:'),
|
2022-09-15 17:31:28 +08:00
|
|
|
Expanded(
|
|
|
|
child: GestureDetector(
|
|
|
|
onTap: canlaunch ? () => launchUrl(Uri.file(dir)) : null,
|
|
|
|
child: Text(
|
|
|
|
dir,
|
|
|
|
softWrap: true,
|
|
|
|
style:
|
|
|
|
const TextStyle(decoration: TextDecoration.underline),
|
|
|
|
)).marginOnly(left: 10),
|
|
|
|
),
|
|
|
|
ElevatedButton(
|
|
|
|
onPressed: () async {
|
2023-03-10 21:32:43 +08:00
|
|
|
String? initialDirectory;
|
|
|
|
if (await Directory.fromUri(Uri.directory(dir))
|
|
|
|
.exists()) {
|
|
|
|
initialDirectory = dir;
|
|
|
|
}
|
2022-09-15 17:31:28 +08:00
|
|
|
String? selectedDirectory = await FilePicker.platform
|
2023-03-10 21:32:43 +08:00
|
|
|
.getDirectoryPath(initialDirectory: initialDirectory);
|
2022-09-15 17:31:28 +08:00
|
|
|
if (selectedDirectory != null) {
|
|
|
|
await bind.mainSetOption(
|
|
|
|
key: 'video-save-directory',
|
|
|
|
value: selectedDirectory);
|
|
|
|
setState(() {});
|
|
|
|
}
|
|
|
|
},
|
|
|
|
child: Text(translate('Change')))
|
|
|
|
.marginOnly(left: 5),
|
|
|
|
],
|
|
|
|
).marginOnly(left: _kContentHMargin),
|
|
|
|
]);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2022-08-13 12:43:35 +08:00
|
|
|
Widget language() {
|
2023-02-23 14:30:29 +08:00
|
|
|
return futureBuilder(future: () async {
|
2022-08-13 12:43:35 +08:00
|
|
|
String langs = await bind.mainGetLangs();
|
2023-07-23 17:07:30 +08:00
|
|
|
return {'langs': langs};
|
2022-08-13 12:43:35 +08:00
|
|
|
}(), hasData: (res) {
|
|
|
|
Map<String, String> data = res as Map<String, String>;
|
2022-11-10 21:25:12 +08:00
|
|
|
List<dynamic> langsList = jsonDecode(data['langs']!);
|
2022-08-13 12:43:35 +08:00
|
|
|
Map<String, String> langsMap = {for (var v in langsList) v[0]: v[1]};
|
|
|
|
List<String> keys = langsMap.keys.toList();
|
|
|
|
List<String> values = langsMap.values.toList();
|
2022-11-10 21:25:12 +08:00
|
|
|
keys.insert(0, '');
|
2023-04-13 16:06:46 +08:00
|
|
|
values.insert(0, translate('Default'));
|
2023-07-23 17:07:30 +08:00
|
|
|
String currentKey = bind.mainGetLocalOption(key: kCommConfKeyLang);
|
2022-08-13 12:43:35 +08:00
|
|
|
if (!keys.contains(currentKey)) {
|
2022-11-10 21:25:12 +08:00
|
|
|
currentKey = '';
|
2022-08-13 12:43:35 +08:00
|
|
|
}
|
refactor windows specific session (#7170)
1. Modify the process to have the control side lead the session switching: After the control side sends a `LoginRequest`, the controlled side will add all session information and the current session ID in the `LoginResponse`. Upon receiving the `LoginResponse`, the control side will check if the current session ID matches the ID in the `LoginConfigHandler`. If they match, the control side will send the current session ID. If they don't match, a session selection dialog will pop up, the selected session id will be sent. Upon receiving this message, the controlled side will restart if different or sub service if same .
2. Always show physical console session on the top
3. Show running session and distinguish sessions with the same name
4. Not sub service until correct session id is ensured
5. Fix switch sides not work for multisession session
6. Remove all session string join/split except get_available_sessions in
windows.rs
7. Fix prelogin, when share rdp is enabled and there is a rdp session,
the console is in login screen, get_active_username will be the rdp's
username and prelogin will be false, cm can't be created an that
causes disconnection in a loop
8. Rename all user session to windows session
Known issue:
1. Use current process session id for `run_as_user`, sahil says it can
be wrong but I didn't reproduce.
2. Have not change tray process to current session
3. File transfer doesn't update home directory when session changed
4. When it's in login screen, remote file directory is empty, because cm
have not start up
Signed-off-by: 21pages <pages21@163.com>
2024-02-18 22:08:25 +08:00
|
|
|
return ComboBox(
|
2022-08-15 11:08:42 +08:00
|
|
|
keys: keys,
|
|
|
|
values: values,
|
|
|
|
initialKey: currentKey,
|
|
|
|
onChanged: (key) async {
|
2022-11-10 21:25:12 +08:00
|
|
|
await bind.mainSetLocalOption(key: kCommConfKeyLang, value: key);
|
2022-10-26 14:39:13 +08:00
|
|
|
reloadAllWindows();
|
2022-09-08 08:52:56 +08:00
|
|
|
bind.mainChangeLanguage(lang: key);
|
2022-08-15 11:08:42 +08:00
|
|
|
},
|
|
|
|
).marginOnly(left: _kContentHMargin);
|
2022-08-13 12:43:35 +08:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-17 14:35:44 +08:00
|
|
|
enum _AccessMode {
|
|
|
|
custom,
|
|
|
|
full,
|
|
|
|
view,
|
|
|
|
}
|
|
|
|
|
2022-08-13 12:43:35 +08:00
|
|
|
class _Safety extends StatefulWidget {
|
|
|
|
const _Safety({Key? key}) : super(key: key);
|
|
|
|
|
|
|
|
@override
|
|
|
|
State<_Safety> createState() => _SafetyState();
|
|
|
|
}
|
|
|
|
|
|
|
|
class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
|
|
|
|
@override
|
|
|
|
bool get wantKeepAlive => true;
|
2022-09-22 16:18:06 +08:00
|
|
|
bool locked = bind.mainIsInstalled();
|
2022-09-21 18:49:28 +08:00
|
|
|
final scrollController = ScrollController();
|
2022-08-13 12:43:35 +08:00
|
|
|
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
super.build(context);
|
2022-09-21 18:49:28 +08:00
|
|
|
return DesktopScrollWrapper(
|
|
|
|
scrollController: scrollController,
|
|
|
|
child: SingleChildScrollView(
|
2023-02-13 16:18:46 +08:00
|
|
|
physics: DraggableNeverScrollableScrollPhysics(),
|
2022-09-21 18:49:28 +08:00
|
|
|
controller: scrollController,
|
|
|
|
child: Column(
|
|
|
|
children: [
|
|
|
|
_lock(locked, 'Unlock Security Settings', () {
|
|
|
|
locked = false;
|
|
|
|
setState(() => {});
|
|
|
|
}),
|
|
|
|
AbsorbPointer(
|
|
|
|
absorbing: locked,
|
|
|
|
child: Column(children: [
|
|
|
|
permissions(context),
|
|
|
|
password(context),
|
2024-01-19 15:35:58 +08:00
|
|
|
_Card(title: '2FA', children: [tfa()]),
|
2022-09-21 18:49:28 +08:00
|
|
|
_Card(title: 'ID', children: [changeId()]),
|
2022-09-21 19:18:40 +08:00
|
|
|
more(context),
|
2022-09-21 18:49:28 +08:00
|
|
|
]),
|
|
|
|
),
|
|
|
|
],
|
|
|
|
)).marginOnly(bottom: _kListViewBottomMargin));
|
|
|
|
}
|
|
|
|
|
2024-01-19 15:35:58 +08:00
|
|
|
Widget tfa() {
|
|
|
|
bool enabled = !locked;
|
|
|
|
// Simple temp wrapper for PR check
|
|
|
|
tmpWrapper() {
|
|
|
|
RxBool has2fa = bind.mainHasValid2FaSync().obs;
|
|
|
|
update() async {
|
|
|
|
has2fa.value = bind.mainHasValid2FaSync();
|
|
|
|
}
|
|
|
|
|
|
|
|
onChanged(bool? checked) async {
|
|
|
|
change2fa(callback: update);
|
|
|
|
}
|
|
|
|
|
|
|
|
return GestureDetector(
|
|
|
|
child: InkWell(
|
|
|
|
child: Obx(() => Row(
|
|
|
|
children: [
|
|
|
|
Checkbox(
|
|
|
|
value: has2fa.value,
|
|
|
|
onChanged: enabled ? onChanged : null)
|
|
|
|
.marginOnly(right: 5),
|
|
|
|
Expanded(
|
|
|
|
child: Text(
|
|
|
|
translate('enable-2fa-title'),
|
|
|
|
style:
|
refactor windows specific session (#7170)
1. Modify the process to have the control side lead the session switching: After the control side sends a `LoginRequest`, the controlled side will add all session information and the current session ID in the `LoginResponse`. Upon receiving the `LoginResponse`, the control side will check if the current session ID matches the ID in the `LoginConfigHandler`. If they match, the control side will send the current session ID. If they don't match, a session selection dialog will pop up, the selected session id will be sent. Upon receiving this message, the controlled side will restart if different or sub service if same .
2. Always show physical console session on the top
3. Show running session and distinguish sessions with the same name
4. Not sub service until correct session id is ensured
5. Fix switch sides not work for multisession session
6. Remove all session string join/split except get_available_sessions in
windows.rs
7. Fix prelogin, when share rdp is enabled and there is a rdp session,
the console is in login screen, get_active_username will be the rdp's
username and prelogin will be false, cm can't be created an that
causes disconnection in a loop
8. Rename all user session to windows session
Known issue:
1. Use current process session id for `run_as_user`, sahil says it can
be wrong but I didn't reproduce.
2. Have not change tray process to current session
3. File transfer doesn't update home directory when session changed
4. When it's in login screen, remote file directory is empty, because cm
have not start up
Signed-off-by: 21pages <pages21@163.com>
2024-02-18 22:08:25 +08:00
|
|
|
TextStyle(color: disabledTextColor(context, enabled)),
|
2024-01-19 15:35:58 +08:00
|
|
|
))
|
|
|
|
],
|
|
|
|
)),
|
|
|
|
),
|
|
|
|
onTap: () {
|
|
|
|
onChanged(!has2fa.value);
|
|
|
|
},
|
|
|
|
).marginOnly(left: _kCheckBoxLeftMargin);
|
|
|
|
}
|
|
|
|
|
|
|
|
return tmpWrapper();
|
|
|
|
}
|
|
|
|
|
2022-09-21 18:49:28 +08:00
|
|
|
Widget changeId() {
|
2023-06-26 23:27:18 +08:00
|
|
|
return ChangeNotifierProvider.value(
|
|
|
|
value: gFFI.serverModel,
|
|
|
|
child: Consumer<ServerModel>(builder: ((context, model, child) {
|
|
|
|
return _Button('Change ID', changeIdDialog,
|
|
|
|
enabled: !locked && model.connectStatus > 0);
|
|
|
|
})));
|
2022-08-13 12:43:35 +08:00
|
|
|
}
|
|
|
|
|
2022-08-20 19:57:16 +08:00
|
|
|
Widget permissions(context) {
|
2023-06-06 21:56:31 +08:00
|
|
|
bool enabled = !locked;
|
2023-07-23 18:01:00 +08:00
|
|
|
// Simple temp wrapper for PR check
|
|
|
|
tmpWrapper() {
|
|
|
|
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;
|
|
|
|
}
|
2022-10-17 14:35:44 +08:00
|
|
|
|
2023-07-23 18:01:00 +08:00
|
|
|
return _Card(title: 'Permissions', children: [
|
refactor windows specific session (#7170)
1. Modify the process to have the control side lead the session switching: After the control side sends a `LoginRequest`, the controlled side will add all session information and the current session ID in the `LoginResponse`. Upon receiving the `LoginResponse`, the control side will check if the current session ID matches the ID in the `LoginConfigHandler`. If they match, the control side will send the current session ID. If they don't match, a session selection dialog will pop up, the selected session id will be sent. Upon receiving this message, the controlled side will restart if different or sub service if same .
2. Always show physical console session on the top
3. Show running session and distinguish sessions with the same name
4. Not sub service until correct session id is ensured
5. Fix switch sides not work for multisession session
6. Remove all session string join/split except get_available_sessions in
windows.rs
7. Fix prelogin, when share rdp is enabled and there is a rdp session,
the console is in login screen, get_active_username will be the rdp's
username and prelogin will be false, cm can't be created an that
causes disconnection in a loop
8. Rename all user session to windows session
Known issue:
1. Use current process session id for `run_as_user`, sahil says it can
be wrong but I didn't reproduce.
2. Have not change tray process to current session
3. File transfer doesn't update home directory when session changed
4. When it's in login screen, remote file directory is empty, because cm
have not start up
Signed-off-by: 21pages <pages21@163.com>
2024-02-18 22:08:25 +08:00
|
|
|
ComboBox(
|
2023-07-23 18:01:00 +08:00
|
|
|
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: [
|
2023-11-06 20:12:01 +08:00
|
|
|
_OptionCheckBox(context, 'Enable keyboard/mouse', 'enable-keyboard',
|
2023-07-23 18:01:00 +08:00
|
|
|
enabled: enabled, fakeValue: fakeValue),
|
2023-11-06 20:12:01 +08:00
|
|
|
_OptionCheckBox(context, 'Enable clipboard', 'enable-clipboard',
|
2023-07-23 18:01:00 +08:00
|
|
|
enabled: enabled, fakeValue: fakeValue),
|
|
|
|
_OptionCheckBox(
|
2023-11-06 20:12:01 +08:00
|
|
|
context, 'Enable file transfer', 'enable-file-transfer',
|
2023-07-23 18:01:00 +08:00
|
|
|
enabled: enabled, fakeValue: fakeValue),
|
2023-11-06 20:12:01 +08:00
|
|
|
_OptionCheckBox(context, 'Enable audio', 'enable-audio',
|
2023-07-23 18:01:00 +08:00
|
|
|
enabled: enabled, fakeValue: fakeValue),
|
2023-11-06 20:12:01 +08:00
|
|
|
_OptionCheckBox(context, 'Enable TCP tunneling', 'enable-tunnel',
|
2023-07-23 18:01:00 +08:00
|
|
|
enabled: enabled, fakeValue: fakeValue),
|
|
|
|
_OptionCheckBox(
|
2023-11-06 20:12:01 +08:00
|
|
|
context, 'Enable remote restart', 'enable-remote-restart',
|
2023-07-23 18:01:00 +08:00
|
|
|
enabled: enabled, fakeValue: fakeValue),
|
|
|
|
_OptionCheckBox(
|
2023-11-06 20:12:01 +08:00
|
|
|
context, 'Enable recording session', 'enable-record-session',
|
2023-07-23 18:01:00 +08:00
|
|
|
enabled: enabled, fakeValue: fakeValue),
|
2024-03-24 11:23:06 +08:00
|
|
|
if (isWindows)
|
2023-11-05 21:53:21 +08:00
|
|
|
_OptionCheckBox(
|
2023-11-06 16:55:19 +08:00
|
|
|
context, 'Enable blocking user input', 'enable-block-input',
|
2023-11-05 21:53:21 +08:00
|
|
|
enabled: enabled, fakeValue: fakeValue),
|
2023-07-23 18:01:00 +08:00
|
|
|
_OptionCheckBox(context, 'Enable remote configuration modification',
|
|
|
|
'allow-remote-config-modification',
|
|
|
|
enabled: enabled, fakeValue: fakeValue),
|
2023-07-23 17:07:30 +08:00
|
|
|
],
|
2023-07-23 18:01:00 +08:00
|
|
|
),
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
|
|
|
return tmpWrapper();
|
2022-08-13 12:43:35 +08:00
|
|
|
}
|
|
|
|
|
2022-08-20 19:57:16 +08:00
|
|
|
Widget password(BuildContext context) {
|
2022-08-13 12:43:35 +08:00
|
|
|
return ChangeNotifierProvider.value(
|
|
|
|
value: gFFI.serverModel,
|
2022-08-15 11:08:42 +08:00
|
|
|
child: Consumer<ServerModel>(builder: ((context, model, child) {
|
2022-11-20 15:53:08 +08:00
|
|
|
List<String> passwordKeys = [
|
2022-08-15 11:08:42 +08:00
|
|
|
kUseTemporaryPassword,
|
|
|
|
kUsePermanentPassword,
|
|
|
|
kUseBothPasswords,
|
|
|
|
];
|
2022-11-20 15:53:08 +08:00
|
|
|
List<String> passwordValues = [
|
2022-11-21 14:06:32 +08:00
|
|
|
translate('Use one-time password'),
|
2022-11-10 21:25:12 +08:00
|
|
|
translate('Use permanent password'),
|
|
|
|
translate('Use both passwords'),
|
2022-08-15 11:08:42 +08:00
|
|
|
];
|
2022-09-07 12:20:53 +08:00
|
|
|
bool tmpEnabled = model.verificationMethod != kUsePermanentPassword;
|
|
|
|
bool permEnabled = model.verificationMethod != kUseTemporaryPassword;
|
2022-11-20 15:53:08 +08:00
|
|
|
String currentValue =
|
|
|
|
passwordValues[passwordKeys.indexOf(model.verificationMethod)];
|
|
|
|
List<Widget> radios = passwordValues
|
2022-08-15 11:08:42 +08:00
|
|
|
.map((value) => _Radio<String>(
|
2022-08-20 19:57:16 +08:00
|
|
|
context,
|
2022-08-19 15:44:19 +08:00
|
|
|
value: value,
|
|
|
|
groupValue: currentValue,
|
|
|
|
label: value,
|
|
|
|
onChanged: ((value) {
|
2022-09-07 12:20:53 +08:00
|
|
|
() async {
|
2022-11-20 15:53:08 +08:00
|
|
|
await model.setVerificationMethod(
|
|
|
|
passwordKeys[passwordValues.indexOf(value)]);
|
2022-09-07 12:20:53 +08:00
|
|
|
await model.updatePasswordModel();
|
|
|
|
}();
|
2022-08-19 15:44:19 +08:00
|
|
|
}),
|
|
|
|
enabled: !locked,
|
|
|
|
))
|
2022-08-15 11:08:42 +08:00
|
|
|
.toList();
|
|
|
|
|
2022-09-07 12:20:53 +08:00
|
|
|
var onChanged = tmpEnabled && !locked
|
2022-08-15 11:08:42 +08:00
|
|
|
? (value) {
|
2022-09-07 12:20:53 +08:00
|
|
|
if (value != null) {
|
|
|
|
() async {
|
|
|
|
await model.setTemporaryPasswordLength(value.toString());
|
|
|
|
await model.updatePasswordModel();
|
|
|
|
}();
|
|
|
|
}
|
2022-08-15 11:08:42 +08:00
|
|
|
}
|
|
|
|
: null;
|
|
|
|
List<Widget> lengthRadios = ['6', '8', '10']
|
|
|
|
.map((value) => GestureDetector(
|
|
|
|
child: Row(
|
|
|
|
children: [
|
|
|
|
Radio(
|
|
|
|
value: value,
|
|
|
|
groupValue: model.temporaryPasswordLength,
|
|
|
|
onChanged: onChanged),
|
2022-08-19 15:44:19 +08:00
|
|
|
Text(
|
|
|
|
value,
|
|
|
|
style: TextStyle(
|
refactor windows specific session (#7170)
1. Modify the process to have the control side lead the session switching: After the control side sends a `LoginRequest`, the controlled side will add all session information and the current session ID in the `LoginResponse`. Upon receiving the `LoginResponse`, the control side will check if the current session ID matches the ID in the `LoginConfigHandler`. If they match, the control side will send the current session ID. If they don't match, a session selection dialog will pop up, the selected session id will be sent. Upon receiving this message, the controlled side will restart if different or sub service if same .
2. Always show physical console session on the top
3. Show running session and distinguish sessions with the same name
4. Not sub service until correct session id is ensured
5. Fix switch sides not work for multisession session
6. Remove all session string join/split except get_available_sessions in
windows.rs
7. Fix prelogin, when share rdp is enabled and there is a rdp session,
the console is in login screen, get_active_username will be the rdp's
username and prelogin will be false, cm can't be created an that
causes disconnection in a loop
8. Rename all user session to windows session
Known issue:
1. Use current process session id for `run_as_user`, sahil says it can
be wrong but I didn't reproduce.
2. Have not change tray process to current session
3. File transfer doesn't update home directory when session changed
4. When it's in login screen, remote file directory is empty, because cm
have not start up
Signed-off-by: 21pages <pages21@163.com>
2024-02-18 22:08:25 +08:00
|
|
|
color: disabledTextColor(
|
2022-08-20 19:57:16 +08:00
|
|
|
context, onChanged != null)),
|
2022-08-19 15:44:19 +08:00
|
|
|
),
|
2022-08-15 11:08:42 +08:00
|
|
|
],
|
2023-02-12 19:59:51 +08:00
|
|
|
).paddingOnly(right: 10),
|
2022-08-15 11:08:42 +08:00
|
|
|
onTap: () => onChanged?.call(value),
|
|
|
|
))
|
|
|
|
.toList();
|
|
|
|
|
2022-11-20 15:53:08 +08:00
|
|
|
final modeKeys = ['password', 'click', ''];
|
|
|
|
final modeValues = [
|
|
|
|
translate('Accept sessions via password'),
|
|
|
|
translate('Accept sessions via click'),
|
|
|
|
translate('Accept sessions via both'),
|
|
|
|
];
|
|
|
|
var modeInitialKey = model.approveMode;
|
|
|
|
if (!modeKeys.contains(modeInitialKey)) modeInitialKey = '';
|
|
|
|
final usePassword = model.approveMode != 'click';
|
|
|
|
|
2022-08-15 11:08:42 +08:00
|
|
|
return _Card(title: 'Password', children: [
|
refactor windows specific session (#7170)
1. Modify the process to have the control side lead the session switching: After the control side sends a `LoginRequest`, the controlled side will add all session information and the current session ID in the `LoginResponse`. Upon receiving the `LoginResponse`, the control side will check if the current session ID matches the ID in the `LoginConfigHandler`. If they match, the control side will send the current session ID. If they don't match, a session selection dialog will pop up, the selected session id will be sent. Upon receiving this message, the controlled side will restart if different or sub service if same .
2. Always show physical console session on the top
3. Show running session and distinguish sessions with the same name
4. Not sub service until correct session id is ensured
5. Fix switch sides not work for multisession session
6. Remove all session string join/split except get_available_sessions in
windows.rs
7. Fix prelogin, when share rdp is enabled and there is a rdp session,
the console is in login screen, get_active_username will be the rdp's
username and prelogin will be false, cm can't be created an that
causes disconnection in a loop
8. Rename all user session to windows session
Known issue:
1. Use current process session id for `run_as_user`, sahil says it can
be wrong but I didn't reproduce.
2. Have not change tray process to current session
3. File transfer doesn't update home directory when session changed
4. When it's in login screen, remote file directory is empty, because cm
have not start up
Signed-off-by: 21pages <pages21@163.com>
2024-02-18 22:08:25 +08:00
|
|
|
ComboBox(
|
2023-03-01 16:30:39 +08:00
|
|
|
enabled: !locked,
|
2022-11-20 15:53:08 +08:00
|
|
|
keys: modeKeys,
|
|
|
|
values: modeValues,
|
|
|
|
initialKey: modeInitialKey,
|
|
|
|
onChanged: (key) => model.setApproveMode(key),
|
|
|
|
).marginOnly(left: _kContentHMargin),
|
2022-11-23 09:41:05 +08:00
|
|
|
if (usePassword) radios[0],
|
|
|
|
if (usePassword)
|
|
|
|
_SubLabeledWidget(
|
2023-02-12 19:59:51 +08:00
|
|
|
context,
|
2022-11-21 14:06:32 +08:00
|
|
|
'One-time password length',
|
2022-11-20 15:53:08 +08:00
|
|
|
Row(
|
|
|
|
children: [
|
|
|
|
...lengthRadios,
|
|
|
|
],
|
|
|
|
),
|
|
|
|
enabled: tmpEnabled && !locked),
|
2022-11-23 09:41:05 +08:00
|
|
|
if (usePassword) radios[1],
|
|
|
|
if (usePassword)
|
|
|
|
_SubButton('Set permanent password', setPasswordDialog,
|
2022-11-20 15:53:08 +08:00
|
|
|
permEnabled && !locked),
|
2023-08-30 11:48:42 +08:00
|
|
|
// if (usePassword)
|
|
|
|
// hide_cm(!locked).marginOnly(left: _kContentHSubMargin - 6),
|
2022-11-23 09:41:05 +08:00
|
|
|
if (usePassword) radios[2],
|
2022-08-15 11:08:42 +08:00
|
|
|
]);
|
|
|
|
})));
|
2022-08-13 12:43:35 +08:00
|
|
|
}
|
|
|
|
|
2022-09-21 19:18:40 +08:00
|
|
|
Widget more(BuildContext context) {
|
2022-09-08 21:40:43 +08:00
|
|
|
bool enabled = !locked;
|
2022-09-21 18:49:28 +08:00
|
|
|
return _Card(title: 'Security', children: [
|
2023-02-10 18:42:08 +08:00
|
|
|
shareRdp(context, enabled),
|
2023-11-06 20:12:01 +08:00
|
|
|
_OptionCheckBox(context, 'Deny LAN discovery', 'enable-lan-discovery',
|
2022-09-24 09:58:57 +08:00
|
|
|
reverse: true, enabled: enabled),
|
2022-09-08 21:40:43 +08:00
|
|
|
...directIp(context),
|
|
|
|
whitelist(),
|
2023-09-13 13:45:40 +08:00
|
|
|
...autoDisconnect(context),
|
2024-04-09 18:34:44 +08:00
|
|
|
if (bind.mainIsInstalled())
|
|
|
|
_OptionCheckBox(context, 'allow-only-conn-window-open-tip',
|
|
|
|
'allow-only-conn-window-open',
|
|
|
|
reverse: false, enabled: enabled),
|
2022-08-13 12:43:35 +08:00
|
|
|
]);
|
|
|
|
}
|
2022-09-08 21:40:43 +08:00
|
|
|
|
2023-02-10 18:42:08 +08:00
|
|
|
shareRdp(BuildContext context, bool enabled) {
|
|
|
|
onChanged(bool b) async {
|
|
|
|
await bind.mainSetShareRdp(enable: b);
|
|
|
|
setState(() {});
|
|
|
|
}
|
|
|
|
|
|
|
|
bool value = bind.mainIsShareRdp();
|
|
|
|
return Offstage(
|
2024-03-24 11:23:06 +08:00
|
|
|
offstage: !(isWindows && bind.mainIsInstalled()),
|
2023-02-10 18:42:08 +08:00
|
|
|
child: GestureDetector(
|
|
|
|
child: Row(
|
|
|
|
children: [
|
|
|
|
Checkbox(
|
|
|
|
value: value,
|
|
|
|
onChanged: enabled ? (_) => onChanged(!value) : null)
|
|
|
|
.marginOnly(right: 5),
|
|
|
|
Expanded(
|
|
|
|
child: Text(translate('Enable RDP session sharing'),
|
|
|
|
style:
|
refactor windows specific session (#7170)
1. Modify the process to have the control side lead the session switching: After the control side sends a `LoginRequest`, the controlled side will add all session information and the current session ID in the `LoginResponse`. Upon receiving the `LoginResponse`, the control side will check if the current session ID matches the ID in the `LoginConfigHandler`. If they match, the control side will send the current session ID. If they don't match, a session selection dialog will pop up, the selected session id will be sent. Upon receiving this message, the controlled side will restart if different or sub service if same .
2. Always show physical console session on the top
3. Show running session and distinguish sessions with the same name
4. Not sub service until correct session id is ensured
5. Fix switch sides not work for multisession session
6. Remove all session string join/split except get_available_sessions in
windows.rs
7. Fix prelogin, when share rdp is enabled and there is a rdp session,
the console is in login screen, get_active_username will be the rdp's
username and prelogin will be false, cm can't be created an that
causes disconnection in a loop
8. Rename all user session to windows session
Known issue:
1. Use current process session id for `run_as_user`, sahil says it can
be wrong but I didn't reproduce.
2. Have not change tray process to current session
3. File transfer doesn't update home directory when session changed
4. When it's in login screen, remote file directory is empty, because cm
have not start up
Signed-off-by: 21pages <pages21@163.com>
2024-02-18 22:08:25 +08:00
|
|
|
TextStyle(color: disabledTextColor(context, enabled))),
|
2023-02-10 18:42:08 +08:00
|
|
|
)
|
|
|
|
],
|
|
|
|
).marginOnly(left: _kCheckBoxLeftMargin),
|
|
|
|
onTap: enabled ? () => onChanged(!value) : null),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-09-08 21:40:43 +08:00
|
|
|
List<Widget> directIp(BuildContext context) {
|
|
|
|
TextEditingController controller = TextEditingController();
|
|
|
|
update() => setState(() {});
|
|
|
|
RxBool applyEnabled = false.obs;
|
|
|
|
return [
|
2023-11-06 20:12:01 +08:00
|
|
|
_OptionCheckBox(context, 'Enable direct IP access', 'direct-server',
|
2022-09-08 21:40:43 +08:00
|
|
|
update: update, enabled: !locked),
|
2023-07-23 17:07:30 +08:00
|
|
|
() {
|
2023-07-23 18:01:00 +08:00
|
|
|
// Simple temp wrapper for PR check
|
|
|
|
tmpWrapper() {
|
|
|
|
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),
|
2022-09-08 21:40:43 +08:00
|
|
|
),
|
2023-07-23 18:01:00 +08:00
|
|
|
).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,
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return tmpWrapper();
|
2023-07-23 17:07:30 +08:00
|
|
|
}(),
|
2022-09-08 21:40:43 +08:00
|
|
|
];
|
|
|
|
}
|
|
|
|
|
|
|
|
Widget whitelist() {
|
|
|
|
bool enabled = !locked;
|
2023-07-23 18:01:00 +08:00
|
|
|
// Simple temp wrapper for PR check
|
|
|
|
tmpWrapper() {
|
|
|
|
RxBool hasWhitelist =
|
|
|
|
bind.mainGetOptionSync(key: 'whitelist').isNotEmpty.obs;
|
|
|
|
update() async {
|
|
|
|
hasWhitelist.value =
|
|
|
|
bind.mainGetOptionSync(key: 'whitelist').isNotEmpty;
|
|
|
|
}
|
2022-09-08 21:40:43 +08:00
|
|
|
|
2023-07-23 18:01:00 +08:00
|
|
|
onChanged(bool? checked) async {
|
|
|
|
changeWhiteList(callback: update);
|
|
|
|
}
|
2022-09-08 21:40:43 +08:00
|
|
|
|
2023-07-23 18:01:00 +08:00
|
|
|
return GestureDetector(
|
|
|
|
child: Tooltip(
|
|
|
|
message: translate('whitelist_tip'),
|
|
|
|
child: Obx(() => Row(
|
|
|
|
children: [
|
|
|
|
Checkbox(
|
|
|
|
value: hasWhitelist.value,
|
|
|
|
onChanged: enabled ? onChanged : null)
|
2022-09-08 21:40:43 +08:00
|
|
|
.marginOnly(right: 5),
|
2023-07-23 18:01:00 +08:00
|
|
|
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:
|
refactor windows specific session (#7170)
1. Modify the process to have the control side lead the session switching: After the control side sends a `LoginRequest`, the controlled side will add all session information and the current session ID in the `LoginResponse`. Upon receiving the `LoginResponse`, the control side will check if the current session ID matches the ID in the `LoginConfigHandler`. If they match, the control side will send the current session ID. If they don't match, a session selection dialog will pop up, the selected session id will be sent. Upon receiving this message, the controlled side will restart if different or sub service if same .
2. Always show physical console session on the top
3. Show running session and distinguish sessions with the same name
4. Not sub service until correct session id is ensured
5. Fix switch sides not work for multisession session
6. Remove all session string join/split except get_available_sessions in
windows.rs
7. Fix prelogin, when share rdp is enabled and there is a rdp session,
the console is in login screen, get_active_username will be the rdp's
username and prelogin will be false, cm can't be created an that
causes disconnection in a loop
8. Rename all user session to windows session
Known issue:
1. Use current process session id for `run_as_user`, sahil says it can
be wrong but I didn't reproduce.
2. Have not change tray process to current session
3. File transfer doesn't update home directory when session changed
4. When it's in login screen, remote file directory is empty, because cm
have not start up
Signed-off-by: 21pages <pages21@163.com>
2024-02-18 22:08:25 +08:00
|
|
|
TextStyle(color: disabledTextColor(context, enabled)),
|
2023-07-23 18:01:00 +08:00
|
|
|
))
|
|
|
|
],
|
|
|
|
)),
|
|
|
|
),
|
|
|
|
onTap: () {
|
|
|
|
onChanged(!hasWhitelist.value);
|
|
|
|
},
|
|
|
|
).marginOnly(left: _kCheckBoxLeftMargin);
|
|
|
|
}
|
|
|
|
|
|
|
|
return tmpWrapper();
|
2022-09-08 21:40:43 +08:00
|
|
|
}
|
2022-11-23 09:41:05 +08:00
|
|
|
|
|
|
|
Widget hide_cm(bool enabled) {
|
|
|
|
return ChangeNotifierProvider.value(
|
|
|
|
value: gFFI.serverModel,
|
|
|
|
child: Consumer<ServerModel>(builder: (context, model, child) {
|
|
|
|
final enableHideCm = model.approveMode == 'password' &&
|
|
|
|
model.verificationMethod == kUsePermanentPassword;
|
|
|
|
onHideCmChanged(bool? b) {
|
|
|
|
if (b != null) {
|
|
|
|
bind.mainSetOption(
|
|
|
|
key: 'allow-hide-cm', value: bool2option('allow-hide-cm', b));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return Tooltip(
|
|
|
|
message: enableHideCm ? "" : translate('hide_cm_tip'),
|
|
|
|
child: GestureDetector(
|
|
|
|
onTap:
|
|
|
|
enableHideCm ? () => onHideCmChanged(!model.hideCm) : null,
|
|
|
|
child: Row(
|
|
|
|
children: [
|
|
|
|
Checkbox(
|
|
|
|
value: model.hideCm,
|
|
|
|
onChanged: enabled && enableHideCm
|
|
|
|
? onHideCmChanged
|
|
|
|
: null)
|
|
|
|
.marginOnly(right: 5),
|
|
|
|
Expanded(
|
|
|
|
child: Text(
|
|
|
|
translate('Hide connection management window'),
|
|
|
|
style: TextStyle(
|
refactor windows specific session (#7170)
1. Modify the process to have the control side lead the session switching: After the control side sends a `LoginRequest`, the controlled side will add all session information and the current session ID in the `LoginResponse`. Upon receiving the `LoginResponse`, the control side will check if the current session ID matches the ID in the `LoginConfigHandler`. If they match, the control side will send the current session ID. If they don't match, a session selection dialog will pop up, the selected session id will be sent. Upon receiving this message, the controlled side will restart if different or sub service if same .
2. Always show physical console session on the top
3. Show running session and distinguish sessions with the same name
4. Not sub service until correct session id is ensured
5. Fix switch sides not work for multisession session
6. Remove all session string join/split except get_available_sessions in
windows.rs
7. Fix prelogin, when share rdp is enabled and there is a rdp session,
the console is in login screen, get_active_username will be the rdp's
username and prelogin will be false, cm can't be created an that
causes disconnection in a loop
8. Rename all user session to windows session
Known issue:
1. Use current process session id for `run_as_user`, sahil says it can
be wrong but I didn't reproduce.
2. Have not change tray process to current session
3. File transfer doesn't update home directory when session changed
4. When it's in login screen, remote file directory is empty, because cm
have not start up
Signed-off-by: 21pages <pages21@163.com>
2024-02-18 22:08:25 +08:00
|
|
|
color: disabledTextColor(
|
2022-11-23 09:41:05 +08:00
|
|
|
context, enabled && enableHideCm)),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
],
|
|
|
|
),
|
|
|
|
));
|
|
|
|
}));
|
|
|
|
}
|
2023-09-13 13:45:40 +08:00
|
|
|
|
|
|
|
List<Widget> autoDisconnect(BuildContext context) {
|
|
|
|
TextEditingController controller = TextEditingController();
|
|
|
|
update() => setState(() {});
|
|
|
|
RxBool applyEnabled = false.obs;
|
|
|
|
final optionKey = 'allow-auto-disconnect';
|
|
|
|
final timeoutKey = 'auto-disconnect-timeout';
|
|
|
|
return [
|
|
|
|
_OptionCheckBox(context, 'auto_disconnect_option_tip', optionKey,
|
|
|
|
update: update, enabled: !locked),
|
|
|
|
() {
|
|
|
|
bool enabled =
|
|
|
|
option2bool(optionKey, bind.mainGetOptionSync(key: optionKey));
|
|
|
|
if (!enabled) applyEnabled.value = false;
|
|
|
|
controller.text = bind.mainGetOptionSync(key: timeoutKey);
|
|
|
|
return Offstage(
|
|
|
|
offstage: !enabled,
|
|
|
|
child: _SubLabeledWidget(
|
|
|
|
context,
|
|
|
|
'Timeout in minutes',
|
|
|
|
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: '10',
|
|
|
|
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: timeoutKey, value: controller.text);
|
|
|
|
}
|
|
|
|
: null,
|
|
|
|
child: Text(
|
|
|
|
translate('Apply'),
|
|
|
|
),
|
|
|
|
))
|
|
|
|
]),
|
|
|
|
enabled: enabled && !locked,
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}(),
|
|
|
|
];
|
|
|
|
}
|
2022-08-13 12:43:35 +08:00
|
|
|
}
|
|
|
|
|
2022-09-08 21:40:43 +08:00
|
|
|
class _Network extends StatefulWidget {
|
|
|
|
const _Network({Key? key}) : super(key: key);
|
2022-08-13 12:43:35 +08:00
|
|
|
|
|
|
|
@override
|
2022-09-08 21:40:43 +08:00
|
|
|
State<_Network> createState() => _NetworkState();
|
2022-08-13 12:43:35 +08:00
|
|
|
}
|
|
|
|
|
2022-09-08 21:40:43 +08:00
|
|
|
class _NetworkState extends State<_Network> with AutomaticKeepAliveClientMixin {
|
2022-08-13 12:43:35 +08:00
|
|
|
@override
|
|
|
|
bool get wantKeepAlive => true;
|
2022-10-12 16:06:15 +08:00
|
|
|
bool locked = bind.mainIsInstalled();
|
2022-08-13 12:43:35 +08:00
|
|
|
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
super.build(context);
|
2022-08-19 15:44:19 +08:00
|
|
|
bool enabled = !locked;
|
2022-09-21 18:49:28 +08:00
|
|
|
final scrollController = ScrollController();
|
|
|
|
return DesktopScrollWrapper(
|
|
|
|
scrollController: scrollController,
|
|
|
|
child: ListView(
|
|
|
|
controller: scrollController,
|
2023-02-13 16:18:46 +08:00
|
|
|
physics: DraggableNeverScrollableScrollPhysics(),
|
2022-09-21 18:49:28 +08:00
|
|
|
children: [
|
|
|
|
_lock(locked, 'Unlock Network Settings', () {
|
|
|
|
locked = false;
|
|
|
|
setState(() => {});
|
|
|
|
}),
|
|
|
|
AbsorbPointer(
|
|
|
|
absorbing: locked,
|
|
|
|
child: Column(children: [
|
2022-10-22 12:03:57 +08:00
|
|
|
server(enabled),
|
2022-09-21 18:49:28 +08:00
|
|
|
_Card(title: 'Proxy', children: [
|
|
|
|
_Button('Socks5 Proxy', changeSocks5Proxy,
|
|
|
|
enabled: enabled),
|
|
|
|
]),
|
|
|
|
]),
|
|
|
|
),
|
|
|
|
]).marginOnly(bottom: _kListViewBottomMargin));
|
2022-08-13 12:43:35 +08:00
|
|
|
}
|
2022-10-22 12:03:57 +08:00
|
|
|
|
|
|
|
server(bool enabled) {
|
2023-07-23 18:01:00 +08:00
|
|
|
// Simple temp wrapper for PR check
|
|
|
|
tmpWrapper() {
|
|
|
|
// Setting page is not modal, oldOptions should only be used when getting options, never when setting.
|
2023-12-11 15:32:13 +08:00
|
|
|
Map<String, dynamic> oldOptions = jsonDecode(bind.mainGetOptionsSync());
|
2023-07-23 18:01:00 +08:00
|
|
|
old(String key) {
|
|
|
|
return (oldOptions[key] ?? '').trim();
|
|
|
|
}
|
2022-10-22 12:03:57 +08:00
|
|
|
|
2023-07-23 18:01:00 +08:00
|
|
|
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'));
|
2023-08-31 20:30:20 +08:00
|
|
|
final controllers = [
|
|
|
|
idController,
|
|
|
|
relayController,
|
|
|
|
apiController,
|
|
|
|
keyController,
|
|
|
|
];
|
|
|
|
final errMsgs = [
|
|
|
|
idErrMsg,
|
|
|
|
relayErrMsg,
|
|
|
|
apiErrMsg,
|
|
|
|
];
|
2022-10-22 12:03:57 +08:00
|
|
|
|
2023-07-23 18:01:00 +08:00
|
|
|
submit() async {
|
2023-08-31 20:30:20 +08:00
|
|
|
bool result = await setServerConfig(
|
2023-12-03 20:31:48 +08:00
|
|
|
null,
|
2023-08-31 20:30:20 +08:00
|
|
|
errMsgs,
|
|
|
|
ServerConfig(
|
|
|
|
idServer: idController.text,
|
|
|
|
relayServer: relayController.text,
|
|
|
|
apiServer: apiController.text,
|
|
|
|
key: keyController.text));
|
2023-07-23 18:01:00 +08:00
|
|
|
if (result) {
|
|
|
|
setState(() {});
|
|
|
|
showToast(translate('Successful'));
|
|
|
|
} else {
|
|
|
|
showToast(translate('Failed'));
|
|
|
|
}
|
2022-10-22 12:03:57 +08:00
|
|
|
}
|
|
|
|
|
2023-07-23 18:01:00 +08:00
|
|
|
bool secure = !enabled;
|
2023-08-31 20:30:20 +08:00
|
|
|
return _Card(
|
|
|
|
title: 'ID/Relay Server',
|
|
|
|
title_suffix: ServerConfigImportExportWidgets(controllers, errMsgs),
|
2023-07-23 18:01:00 +08:00
|
|
|
children: [
|
2023-08-31 20:30:20 +08:00
|
|
|
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),
|
|
|
|
],
|
|
|
|
)
|
|
|
|
]);
|
2023-07-23 18:01:00 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return tmpWrapper();
|
2022-10-22 12:03:57 +08:00
|
|
|
}
|
2022-08-13 12:43:35 +08:00
|
|
|
}
|
|
|
|
|
2023-02-01 19:56:57 +08:00
|
|
|
class _Display extends StatefulWidget {
|
|
|
|
const _Display({Key? key}) : super(key: key);
|
|
|
|
|
|
|
|
@override
|
|
|
|
State<_Display> createState() => _DisplayState();
|
|
|
|
}
|
|
|
|
|
|
|
|
class _DisplayState extends State<_Display> {
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
final scrollController = ScrollController();
|
|
|
|
return DesktopScrollWrapper(
|
|
|
|
scrollController: scrollController,
|
|
|
|
child: ListView(
|
|
|
|
controller: scrollController,
|
2023-02-13 16:18:46 +08:00
|
|
|
physics: DraggableNeverScrollableScrollPhysics(),
|
2023-02-01 19:56:57 +08:00
|
|
|
children: [
|
|
|
|
viewStyle(context),
|
|
|
|
scrollStyle(context),
|
|
|
|
imageQuality(context),
|
|
|
|
codec(context),
|
2023-11-14 12:11:38 +08:00
|
|
|
privacyModeImpl(context),
|
2023-02-02 09:39:14 +08:00
|
|
|
other(context),
|
2023-02-01 19:56:57 +08:00
|
|
|
]).marginOnly(bottom: _kListViewBottomMargin));
|
|
|
|
}
|
|
|
|
|
|
|
|
Widget viewStyle(BuildContext context) {
|
|
|
|
final key = 'view_style';
|
|
|
|
onChanged(String value) async {
|
|
|
|
await bind.mainSetUserDefaultOption(key: key, value: value);
|
|
|
|
setState(() {});
|
|
|
|
}
|
|
|
|
|
|
|
|
final groupValue = bind.mainGetUserDefaultOption(key: key);
|
|
|
|
return _Card(title: 'Default View Style', children: [
|
|
|
|
_Radio(context,
|
|
|
|
value: kRemoteViewStyleOriginal,
|
|
|
|
groupValue: groupValue,
|
|
|
|
label: 'Scale original',
|
|
|
|
onChanged: onChanged),
|
|
|
|
_Radio(context,
|
|
|
|
value: kRemoteViewStyleAdaptive,
|
|
|
|
groupValue: groupValue,
|
|
|
|
label: 'Scale adaptive',
|
|
|
|
onChanged: onChanged),
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
|
|
|
Widget scrollStyle(BuildContext context) {
|
|
|
|
final key = 'scroll_style';
|
|
|
|
onChanged(String value) async {
|
|
|
|
await bind.mainSetUserDefaultOption(key: key, value: value);
|
|
|
|
setState(() {});
|
|
|
|
}
|
|
|
|
|
|
|
|
final groupValue = bind.mainGetUserDefaultOption(key: key);
|
|
|
|
return _Card(title: 'Default Scroll Style', children: [
|
|
|
|
_Radio(context,
|
|
|
|
value: kRemoteScrollStyleAuto,
|
|
|
|
groupValue: groupValue,
|
|
|
|
label: 'ScrollAuto',
|
|
|
|
onChanged: onChanged),
|
|
|
|
_Radio(context,
|
|
|
|
value: kRemoteScrollStyleBar,
|
|
|
|
groupValue: groupValue,
|
|
|
|
label: 'Scrollbar',
|
|
|
|
onChanged: onChanged),
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
|
|
|
Widget imageQuality(BuildContext context) {
|
|
|
|
final key = 'image_quality';
|
|
|
|
onChanged(String value) async {
|
|
|
|
await bind.mainSetUserDefaultOption(key: key, value: value);
|
|
|
|
setState(() {});
|
|
|
|
}
|
|
|
|
|
|
|
|
final groupValue = bind.mainGetUserDefaultOption(key: key);
|
|
|
|
return _Card(title: 'Default Image Quality', children: [
|
|
|
|
_Radio(context,
|
|
|
|
value: kRemoteImageQualityBest,
|
|
|
|
groupValue: groupValue,
|
|
|
|
label: 'Good image quality',
|
|
|
|
onChanged: onChanged),
|
|
|
|
_Radio(context,
|
|
|
|
value: kRemoteImageQualityBalanced,
|
|
|
|
groupValue: groupValue,
|
|
|
|
label: 'Balanced',
|
|
|
|
onChanged: onChanged),
|
|
|
|
_Radio(context,
|
|
|
|
value: kRemoteImageQualityLow,
|
|
|
|
groupValue: groupValue,
|
|
|
|
label: 'Optimize reaction time',
|
|
|
|
onChanged: onChanged),
|
|
|
|
_Radio(context,
|
|
|
|
value: kRemoteImageQualityCustom,
|
|
|
|
groupValue: groupValue,
|
|
|
|
label: 'Custom',
|
|
|
|
onChanged: onChanged),
|
|
|
|
Offstage(
|
|
|
|
offstage: groupValue != kRemoteImageQualityCustom,
|
2023-08-31 20:30:20 +08:00
|
|
|
child: customImageQualitySetting(),
|
2023-02-01 19:56:57 +08:00
|
|
|
)
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
|
|
|
Widget codec(BuildContext context) {
|
|
|
|
final key = 'codec-preference';
|
|
|
|
onChanged(String value) async {
|
|
|
|
await bind.mainSetUserDefaultOption(key: key, value: value);
|
|
|
|
setState(() {});
|
|
|
|
}
|
|
|
|
|
|
|
|
final groupValue = bind.mainGetUserDefaultOption(key: key);
|
2023-03-31 16:10:52 +08:00
|
|
|
var hwRadios = [];
|
|
|
|
try {
|
|
|
|
final Map codecsJson = jsonDecode(bind.mainSupportedHwdecodings());
|
|
|
|
final h264 = codecsJson['h264'] ?? false;
|
|
|
|
final h265 = codecsJson['h265'] ?? false;
|
|
|
|
if (h264) {
|
|
|
|
hwRadios.add(_Radio(context,
|
|
|
|
value: 'h264',
|
|
|
|
groupValue: groupValue,
|
|
|
|
label: 'H264',
|
|
|
|
onChanged: onChanged));
|
|
|
|
}
|
|
|
|
if (h265) {
|
|
|
|
hwRadios.add(_Radio(context,
|
|
|
|
value: 'h265',
|
|
|
|
groupValue: groupValue,
|
|
|
|
label: 'H265',
|
|
|
|
onChanged: onChanged));
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
debugPrint("failed to parse supported hwdecodings, err=$e");
|
|
|
|
}
|
2023-02-01 19:56:57 +08:00
|
|
|
return _Card(title: 'Default Codec', children: [
|
|
|
|
_Radio(context,
|
|
|
|
value: 'auto',
|
|
|
|
groupValue: groupValue,
|
|
|
|
label: 'Auto',
|
|
|
|
onChanged: onChanged),
|
|
|
|
_Radio(context,
|
2023-03-31 16:10:52 +08:00
|
|
|
value: 'vp8',
|
2023-02-01 19:56:57 +08:00
|
|
|
groupValue: groupValue,
|
2023-03-31 16:10:52 +08:00
|
|
|
label: 'VP8',
|
2023-02-01 19:56:57 +08:00
|
|
|
onChanged: onChanged),
|
|
|
|
_Radio(context,
|
2023-03-31 16:10:52 +08:00
|
|
|
value: 'vp9',
|
2023-04-01 10:13:39 +08:00
|
|
|
groupValue: groupValue,
|
2023-03-31 16:10:52 +08:00
|
|
|
label: 'VP9',
|
2023-02-01 19:56:57 +08:00
|
|
|
onChanged: onChanged),
|
2023-05-08 20:35:24 +08:00
|
|
|
_Radio(context,
|
|
|
|
value: 'av1',
|
|
|
|
groupValue: groupValue,
|
|
|
|
label: 'AV1',
|
|
|
|
onChanged: onChanged),
|
2023-03-31 16:10:52 +08:00
|
|
|
...hwRadios,
|
2023-02-01 19:56:57 +08:00
|
|
|
]);
|
|
|
|
}
|
2023-02-02 09:39:14 +08:00
|
|
|
|
2023-11-14 12:11:38 +08:00
|
|
|
Widget privacyModeImpl(BuildContext context) {
|
|
|
|
final supportedPrivacyModeImpls = bind.mainSupportedPrivacyModeImpls();
|
|
|
|
late final List<dynamic> privacyModeImpls;
|
|
|
|
try {
|
|
|
|
privacyModeImpls = jsonDecode(supportedPrivacyModeImpls);
|
|
|
|
} catch (e) {
|
|
|
|
debugPrint('failed to parse supported privacy mode impls, err=$e');
|
|
|
|
return Offstage();
|
|
|
|
}
|
|
|
|
if (privacyModeImpls.length < 2) {
|
|
|
|
return Offstage();
|
|
|
|
}
|
|
|
|
|
|
|
|
final key = 'privacy-mode-impl-key';
|
|
|
|
onChanged(String value) async {
|
|
|
|
await bind.mainSetOption(key: key, value: value);
|
|
|
|
setState(() {});
|
|
|
|
}
|
|
|
|
|
|
|
|
String groupValue = bind.mainGetOptionSync(key: key);
|
|
|
|
if (groupValue.isEmpty) {
|
|
|
|
groupValue = bind.mainDefaultPrivacyModeImpl();
|
|
|
|
}
|
|
|
|
return _Card(
|
|
|
|
title: 'Privacy mode',
|
|
|
|
children: privacyModeImpls.map((impl) {
|
|
|
|
final d = impl as List<dynamic>;
|
|
|
|
return _Radio(context,
|
|
|
|
value: d[0] as String,
|
|
|
|
groupValue: groupValue,
|
|
|
|
label: d[1] as String,
|
|
|
|
onChanged: onChanged);
|
|
|
|
}).toList(),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2023-02-02 09:39:14 +08:00
|
|
|
Widget otherRow(String label, String key) {
|
|
|
|
final value = bind.mainGetUserDefaultOption(key: key) == 'Y';
|
|
|
|
onChanged(bool b) async {
|
|
|
|
await bind.mainSetUserDefaultOption(key: key, value: b ? 'Y' : '');
|
|
|
|
setState(() {});
|
|
|
|
}
|
|
|
|
|
|
|
|
return GestureDetector(
|
|
|
|
child: Row(
|
|
|
|
children: [
|
|
|
|
Checkbox(value: value, onChanged: (_) => onChanged(!value))
|
|
|
|
.marginOnly(right: 5),
|
|
|
|
Expanded(
|
|
|
|
child: Text(translate(label)),
|
|
|
|
)
|
|
|
|
],
|
|
|
|
).marginOnly(left: _kCheckBoxLeftMargin),
|
|
|
|
onTap: () => onChanged(!value));
|
|
|
|
}
|
|
|
|
|
|
|
|
Widget other(BuildContext context) {
|
2023-12-11 15:32:13 +08:00
|
|
|
final children =
|
|
|
|
otherDefaultSettings().map((e) => otherRow(e.$1, e.$2)).toList();
|
2023-10-16 07:34:38 +08:00
|
|
|
return _Card(title: 'Other Default Options', children: children);
|
2023-09-10 14:14:57 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-22 17:38:18 +08:00
|
|
|
class _Account extends StatefulWidget {
|
|
|
|
const _Account({Key? key}) : super(key: key);
|
2022-08-13 12:43:35 +08:00
|
|
|
|
|
|
|
@override
|
2022-09-22 17:38:18 +08:00
|
|
|
State<_Account> createState() => _AccountState();
|
2022-08-13 12:43:35 +08:00
|
|
|
}
|
|
|
|
|
2022-09-22 17:38:18 +08:00
|
|
|
class _AccountState extends State<_Account> {
|
2022-08-13 12:43:35 +08:00
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
2022-09-21 18:49:28 +08:00
|
|
|
final scrollController = ScrollController();
|
|
|
|
return DesktopScrollWrapper(
|
|
|
|
scrollController: scrollController,
|
|
|
|
child: ListView(
|
2023-02-13 16:18:46 +08:00
|
|
|
physics: DraggableNeverScrollableScrollPhysics(),
|
2022-09-21 18:49:28 +08:00
|
|
|
controller: scrollController,
|
|
|
|
children: [
|
2023-06-18 13:53:03 +08:00
|
|
|
_Card(title: 'Account', children: [accountAction(), useInfo()]),
|
2022-09-21 18:49:28 +08:00
|
|
|
],
|
|
|
|
).marginOnly(bottom: _kListViewBottomMargin));
|
2022-08-15 11:08:42 +08:00
|
|
|
}
|
|
|
|
|
2022-09-22 17:38:18 +08:00
|
|
|
Widget accountAction() {
|
2022-12-11 21:40:35 +08:00
|
|
|
return Obx(() => _Button(
|
|
|
|
gFFI.userModel.userName.value.isEmpty ? 'Login' : 'Logout',
|
|
|
|
() => {
|
|
|
|
gFFI.userModel.userName.value.isEmpty
|
|
|
|
? loginDialog()
|
2023-07-15 07:21:54 +08:00
|
|
|
: logOutConfirmDialog()
|
2022-12-11 21:40:35 +08:00
|
|
|
}));
|
2022-08-13 12:43:35 +08:00
|
|
|
}
|
2023-06-18 13:53:03 +08:00
|
|
|
|
|
|
|
Widget useInfo() {
|
|
|
|
text(String key, String value) {
|
|
|
|
return Align(
|
|
|
|
alignment: Alignment.centerLeft,
|
|
|
|
child: SelectionArea(child: Text('${translate(key)}: $value'))
|
|
|
|
.marginSymmetric(vertical: 4),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return Obx(() => Offstage(
|
|
|
|
offstage: gFFI.userModel.userName.value.isEmpty,
|
|
|
|
child: Column(
|
|
|
|
children: [
|
|
|
|
text('Username', gFFI.userModel.userName.value),
|
2023-06-29 06:25:17 +08:00
|
|
|
// text('Group', gFFI.groupModel.groupName.value),
|
2023-06-18 13:53:03 +08:00
|
|
|
],
|
|
|
|
),
|
|
|
|
)).marginOnly(left: 18, top: 16);
|
|
|
|
}
|
2022-08-15 11:08:42 +08:00
|
|
|
}
|
|
|
|
|
2023-04-24 18:45:22 +08:00
|
|
|
class _Checkbox extends StatefulWidget {
|
|
|
|
final String label;
|
|
|
|
final bool Function() getValue;
|
|
|
|
final Future<void> Function(bool) setValue;
|
|
|
|
|
|
|
|
const _Checkbox(
|
|
|
|
{Key? key,
|
|
|
|
required this.label,
|
|
|
|
required this.getValue,
|
|
|
|
required this.setValue})
|
|
|
|
: super(key: key);
|
2023-04-22 22:21:02 +08:00
|
|
|
|
|
|
|
@override
|
2023-04-24 18:45:22 +08:00
|
|
|
State<_Checkbox> createState() => _CheckboxState();
|
2023-04-22 22:21:02 +08:00
|
|
|
}
|
|
|
|
|
2023-04-24 18:45:22 +08:00
|
|
|
class _CheckboxState extends State<_Checkbox> {
|
|
|
|
var value = false;
|
|
|
|
|
|
|
|
@override
|
|
|
|
initState() {
|
|
|
|
super.initState();
|
|
|
|
value = widget.getValue();
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
2023-04-22 22:21:02 +08:00
|
|
|
onChanged(bool b) async {
|
2023-04-24 18:45:22 +08:00
|
|
|
await widget.setValue(b);
|
|
|
|
setState(() {
|
|
|
|
value = widget.getValue();
|
|
|
|
});
|
2023-04-22 22:21:02 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return GestureDetector(
|
2023-04-24 18:45:22 +08:00
|
|
|
child: Row(
|
|
|
|
children: [
|
|
|
|
Checkbox(
|
|
|
|
value: value,
|
|
|
|
onChanged: (_) => onChanged(!value),
|
|
|
|
).marginOnly(right: 5),
|
|
|
|
Expanded(
|
|
|
|
child: Text(translate(widget.label)),
|
|
|
|
)
|
|
|
|
],
|
|
|
|
).marginOnly(left: _kCheckBoxLeftMargin),
|
|
|
|
onTap: () => onChanged(!value),
|
|
|
|
);
|
2023-04-22 22:21:02 +08:00
|
|
|
}
|
2023-04-24 18:45:22 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
class _Plugin extends StatefulWidget {
|
|
|
|
const _Plugin({Key? key}) : super(key: key);
|
|
|
|
|
|
|
|
@override
|
|
|
|
State<_Plugin> createState() => _PluginState();
|
|
|
|
}
|
|
|
|
|
|
|
|
class _PluginState extends State<_Plugin> {
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
2023-05-10 18:58:45 +08:00
|
|
|
bind.pluginListReload();
|
2023-04-24 18:45:22 +08:00
|
|
|
final scrollController = ScrollController();
|
2023-04-22 22:21:02 +08:00
|
|
|
return DesktopScrollWrapper(
|
|
|
|
scrollController: scrollController,
|
|
|
|
child: ChangeNotifierProvider.value(
|
2023-05-09 19:47:26 +08:00
|
|
|
value: pluginManager,
|
|
|
|
child: Consumer<PluginManager>(builder: (context, model, child) {
|
2023-04-22 22:21:02 +08:00
|
|
|
return ListView(
|
|
|
|
physics: DraggableNeverScrollableScrollPhysics(),
|
|
|
|
controller: scrollController,
|
2023-05-10 23:57:46 +08:00
|
|
|
children: model.plugins.map((entry) => pluginCard(entry)).toList(),
|
2023-04-22 22:21:02 +08:00
|
|
|
).marginOnly(bottom: _kListViewBottomMargin);
|
|
|
|
}),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2023-05-10 23:57:46 +08:00
|
|
|
Widget pluginCard(PluginInfo plugin) {
|
|
|
|
return ChangeNotifierProvider.value(
|
|
|
|
value: plugin,
|
|
|
|
child: Consumer<PluginInfo>(
|
|
|
|
builder: (context, model, child) => DesktopSettingsCard(plugin: model),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2023-04-22 22:21:02 +08:00
|
|
|
Widget accountAction() {
|
|
|
|
return Obx(() => _Button(
|
|
|
|
gFFI.userModel.userName.value.isEmpty ? 'Login' : 'Logout',
|
|
|
|
() => {
|
|
|
|
gFFI.userModel.userName.value.isEmpty
|
|
|
|
? loginDialog()
|
2023-07-15 07:21:54 +08:00
|
|
|
: logOutConfirmDialog()
|
2023-04-22 22:21:02 +08:00
|
|
|
}));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-15 11:08:42 +08:00
|
|
|
class _About extends StatefulWidget {
|
|
|
|
const _About({Key? key}) : super(key: key);
|
|
|
|
|
|
|
|
@override
|
|
|
|
State<_About> createState() => _AboutState();
|
|
|
|
}
|
|
|
|
|
|
|
|
class _AboutState extends State<_About> {
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
2023-02-23 14:30:29 +08:00
|
|
|
return futureBuilder(future: () async {
|
2022-08-15 11:08:42 +08:00
|
|
|
final license = await bind.mainGetLicense();
|
|
|
|
final version = await bind.mainGetVersion();
|
2022-11-17 16:36:07 +08:00
|
|
|
final buildDate = await bind.mainGetBuildDate();
|
2023-04-19 14:39:22 +08:00
|
|
|
final fingerprint = await bind.mainGetFingerprint();
|
|
|
|
return {
|
|
|
|
'license': license,
|
|
|
|
'version': version,
|
|
|
|
'buildDate': buildDate,
|
|
|
|
'fingerprint': fingerprint
|
|
|
|
};
|
2022-08-15 11:08:42 +08:00
|
|
|
}(), hasData: (data) {
|
|
|
|
final license = data['license'].toString();
|
|
|
|
final version = data['version'].toString();
|
2022-11-17 16:36:07 +08:00
|
|
|
final buildDate = data['buildDate'].toString();
|
2023-04-19 14:39:22 +08:00
|
|
|
final fingerprint = data['fingerprint'].toString();
|
2022-09-08 21:40:43 +08:00
|
|
|
const linkStyle = TextStyle(decoration: TextDecoration.underline);
|
2022-09-21 18:49:28 +08:00
|
|
|
final scrollController = ScrollController();
|
|
|
|
return DesktopScrollWrapper(
|
|
|
|
scrollController: scrollController,
|
|
|
|
child: SingleChildScrollView(
|
|
|
|
controller: scrollController,
|
2023-02-13 16:18:46 +08:00
|
|
|
physics: DraggableNeverScrollableScrollPhysics(),
|
2022-12-27 23:26:11 +08:00
|
|
|
child: _Card(title: '${translate('About')} RustDesk', children: [
|
2022-09-21 18:49:28 +08:00
|
|
|
Column(
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
|
children: [
|
|
|
|
const SizedBox(
|
|
|
|
height: 8.0,
|
|
|
|
),
|
2023-02-01 21:28:26 +08:00
|
|
|
SelectionArea(
|
|
|
|
child: Text('${translate('Version')}: $version')
|
|
|
|
.marginSymmetric(vertical: 4.0)),
|
|
|
|
SelectionArea(
|
|
|
|
child: Text('${translate('Build Date')}: $buildDate')
|
|
|
|
.marginSymmetric(vertical: 4.0)),
|
2023-04-19 14:39:22 +08:00
|
|
|
SelectionArea(
|
|
|
|
child: Text('${translate('Fingerprint')}: $fingerprint')
|
|
|
|
.marginSymmetric(vertical: 4.0)),
|
2022-09-21 18:49:28 +08:00
|
|
|
InkWell(
|
|
|
|
onTap: () {
|
2023-07-08 16:37:56 +08:00
|
|
|
launchUrlString('https://rustdesk.com/privacy.html');
|
2022-09-21 18:49:28 +08:00
|
|
|
},
|
2022-12-17 21:40:57 +08:00
|
|
|
child: Text(
|
|
|
|
translate('Privacy Statement'),
|
2022-09-21 18:49:28 +08:00
|
|
|
style: linkStyle,
|
|
|
|
).marginSymmetric(vertical: 4.0)),
|
|
|
|
InkWell(
|
|
|
|
onTap: () {
|
2022-11-10 21:25:12 +08:00
|
|
|
launchUrlString('https://rustdesk.com');
|
2022-09-21 18:49:28 +08:00
|
|
|
},
|
2022-12-17 21:40:57 +08:00
|
|
|
child: Text(
|
|
|
|
translate('Website'),
|
2022-09-21 18:49:28 +08:00
|
|
|
style: linkStyle,
|
|
|
|
).marginSymmetric(vertical: 4.0)),
|
|
|
|
Container(
|
|
|
|
decoration: const BoxDecoration(color: Color(0xFF2c8cff)),
|
|
|
|
padding:
|
|
|
|
const EdgeInsets.symmetric(vertical: 24, horizontal: 8),
|
2023-02-01 21:28:26 +08:00
|
|
|
child: SelectionArea(
|
|
|
|
child: Row(
|
2022-09-21 18:49:28 +08:00
|
|
|
children: [
|
|
|
|
Expanded(
|
|
|
|
child: Column(
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
|
children: [
|
|
|
|
Text(
|
2024-02-20 18:00:07 +08:00
|
|
|
'Copyright © ${DateTime.now().toString().substring(0, 4)} Purslane Ltd.\n$license',
|
2022-09-21 18:49:28 +08:00
|
|
|
style: const TextStyle(color: Colors.white),
|
|
|
|
),
|
2022-12-17 21:40:57 +08:00
|
|
|
Text(
|
|
|
|
translate('Slogan_tip'),
|
2022-09-21 18:49:28 +08:00
|
|
|
style: TextStyle(
|
|
|
|
fontWeight: FontWeight.w800,
|
|
|
|
color: Colors.white),
|
|
|
|
)
|
|
|
|
],
|
2022-08-15 11:08:42 +08:00
|
|
|
),
|
2022-09-21 18:49:28 +08:00
|
|
|
),
|
|
|
|
],
|
2023-02-01 21:28:26 +08:00
|
|
|
)),
|
2022-09-21 18:49:28 +08:00
|
|
|
).marginSymmetric(vertical: 4.0)
|
|
|
|
],
|
|
|
|
).marginOnly(left: _kContentHMargin)
|
|
|
|
]),
|
|
|
|
));
|
2022-08-15 11:08:42 +08:00
|
|
|
});
|
2022-08-11 16:03:04 +08:00
|
|
|
}
|
|
|
|
}
|
2022-08-13 12:43:35 +08:00
|
|
|
|
|
|
|
//#endregion
|
|
|
|
|
|
|
|
//#region components
|
|
|
|
|
2022-09-08 21:40:43 +08:00
|
|
|
// ignore: non_constant_identifier_names
|
2022-10-22 12:03:57 +08:00
|
|
|
Widget _Card(
|
|
|
|
{required String title,
|
|
|
|
required List<Widget> children,
|
|
|
|
List<Widget>? title_suffix}) {
|
2022-10-13 10:23:52 +08:00
|
|
|
return Row(
|
|
|
|
children: [
|
2022-10-22 12:03:57 +08:00
|
|
|
Flexible(
|
|
|
|
child: SizedBox(
|
|
|
|
width: _kCardFixedWidth,
|
|
|
|
child: Card(
|
|
|
|
child: Column(
|
|
|
|
children: [
|
|
|
|
Row(
|
|
|
|
children: [
|
|
|
|
Expanded(
|
|
|
|
child: Text(
|
|
|
|
translate(title),
|
|
|
|
textAlign: TextAlign.start,
|
|
|
|
style: const TextStyle(
|
|
|
|
fontSize: _kTitleFontSize,
|
|
|
|
),
|
|
|
|
)),
|
|
|
|
...?title_suffix
|
|
|
|
],
|
|
|
|
).marginOnly(left: _kContentHMargin, top: 10, bottom: 10),
|
2022-10-13 10:23:52 +08:00
|
|
|
...children
|
|
|
|
.map((e) => e.marginOnly(top: 4, right: _kContentHMargin)),
|
2022-10-22 12:03:57 +08:00
|
|
|
],
|
|
|
|
).marginOnly(bottom: 10),
|
|
|
|
).marginOnly(left: _kCardLeftMargin, top: 15),
|
|
|
|
),
|
2022-10-13 10:23:52 +08:00
|
|
|
),
|
|
|
|
],
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-09-08 21:40:43 +08:00
|
|
|
// ignore: non_constant_identifier_names
|
2022-08-20 19:57:16 +08:00
|
|
|
Widget _OptionCheckBox(BuildContext context, String label, String key,
|
2022-09-08 21:40:43 +08:00
|
|
|
{Function()? update,
|
|
|
|
bool reverse = false,
|
|
|
|
bool enabled = true,
|
2022-10-17 14:35:44 +08:00
|
|
|
Icon? checkedIcon,
|
2023-07-30 12:16:00 +08:00
|
|
|
bool? fakeValue,
|
|
|
|
bool isServer = true}) {
|
|
|
|
bool value =
|
|
|
|
isServer ? mainGetBoolOptionSync(key) : mainGetLocalBoolOptionSync(key);
|
2023-07-23 17:07:30 +08:00
|
|
|
if (reverse) value = !value;
|
|
|
|
var ref = value.obs;
|
|
|
|
onChanged(option) async {
|
|
|
|
if (option != null) {
|
|
|
|
if (reverse) option = !option;
|
2023-07-30 12:16:00 +08:00
|
|
|
isServer
|
|
|
|
? await mainSetBoolOption(key, option)
|
|
|
|
: await mainSetLocalBoolOption(key, option);
|
2023-09-11 16:42:01 +08:00
|
|
|
final readOption = isServer
|
2023-08-04 00:09:22 +08:00
|
|
|
? mainGetBoolOptionSync(key)
|
|
|
|
: mainGetLocalBoolOptionSync(key);
|
2023-09-11 16:42:01 +08:00
|
|
|
if (reverse) {
|
|
|
|
ref.value = !readOption;
|
|
|
|
} else {
|
|
|
|
ref.value = readOption;
|
|
|
|
}
|
2023-07-23 17:07:30 +08:00
|
|
|
update?.call();
|
|
|
|
}
|
|
|
|
}
|
2022-09-08 21:40:43 +08:00
|
|
|
|
2023-07-23 17:07:30 +08:00
|
|
|
if (fakeValue != null) {
|
|
|
|
ref.value = fakeValue;
|
|
|
|
enabled = false;
|
|
|
|
}
|
2022-10-17 14:35:44 +08:00
|
|
|
|
2023-07-23 17:07:30 +08:00
|
|
|
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),
|
refactor windows specific session (#7170)
1. Modify the process to have the control side lead the session switching: After the control side sends a `LoginRequest`, the controlled side will add all session information and the current session ID in the `LoginResponse`. Upon receiving the `LoginResponse`, the control side will check if the current session ID matches the ID in the `LoginConfigHandler`. If they match, the control side will send the current session ID. If they don't match, a session selection dialog will pop up, the selected session id will be sent. Upon receiving this message, the controlled side will restart if different or sub service if same .
2. Always show physical console session on the top
3. Show running session and distinguish sessions with the same name
4. Not sub service until correct session id is ensured
5. Fix switch sides not work for multisession session
6. Remove all session string join/split except get_available_sessions in
windows.rs
7. Fix prelogin, when share rdp is enabled and there is a rdp session,
the console is in login screen, get_active_username will be the rdp's
username and prelogin will be false, cm can't be created an that
causes disconnection in a loop
8. Rename all user session to windows session
Known issue:
1. Use current process session id for `run_as_user`, sahil says it can
be wrong but I didn't reproduce.
2. Have not change tray process to current session
3. File transfer doesn't update home directory when session changed
4. When it's in login screen, remote file directory is empty, because cm
have not start up
Signed-off-by: 21pages <pages21@163.com>
2024-02-18 22:08:25 +08:00
|
|
|
style: TextStyle(color: disabledTextColor(context, enabled)),
|
2023-07-23 17:07:30 +08:00
|
|
|
))
|
|
|
|
],
|
|
|
|
),
|
|
|
|
).marginOnly(left: _kCheckBoxLeftMargin),
|
|
|
|
onTap: enabled
|
|
|
|
? () {
|
|
|
|
onChanged(!ref.value);
|
|
|
|
}
|
|
|
|
: null,
|
|
|
|
);
|
2022-08-13 12:43:35 +08:00
|
|
|
}
|
|
|
|
|
2022-09-08 21:40:43 +08:00
|
|
|
// ignore: non_constant_identifier_names
|
2022-08-20 19:57:16 +08:00
|
|
|
Widget _Radio<T>(BuildContext context,
|
2022-08-19 15:44:19 +08:00
|
|
|
{required T value,
|
|
|
|
required T groupValue,
|
|
|
|
required String label,
|
|
|
|
required Function(T value) onChanged,
|
2022-10-22 12:03:57 +08:00
|
|
|
bool autoNewLine = true,
|
2022-08-19 15:44:19 +08:00
|
|
|
bool enabled = true}) {
|
2022-09-08 21:40:43 +08:00
|
|
|
var onChange = enabled
|
2022-08-19 15:44:19 +08:00
|
|
|
? (T? value) {
|
|
|
|
if (value != null) {
|
|
|
|
onChanged(value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
: null;
|
2022-08-15 11:08:42 +08:00
|
|
|
return GestureDetector(
|
|
|
|
child: Row(
|
|
|
|
children: [
|
2022-09-08 21:40:43 +08:00
|
|
|
Radio<T>(value: value, groupValue: groupValue, onChanged: onChange),
|
2022-08-15 11:08:42 +08:00
|
|
|
Expanded(
|
|
|
|
child: Text(translate(label),
|
2022-10-22 12:03:57 +08:00
|
|
|
overflow: autoNewLine ? null : TextOverflow.ellipsis,
|
2022-08-19 15:44:19 +08:00
|
|
|
style: TextStyle(
|
|
|
|
fontSize: _kContentFontSize,
|
refactor windows specific session (#7170)
1. Modify the process to have the control side lead the session switching: After the control side sends a `LoginRequest`, the controlled side will add all session information and the current session ID in the `LoginResponse`. Upon receiving the `LoginResponse`, the control side will check if the current session ID matches the ID in the `LoginConfigHandler`. If they match, the control side will send the current session ID. If they don't match, a session selection dialog will pop up, the selected session id will be sent. Upon receiving this message, the controlled side will restart if different or sub service if same .
2. Always show physical console session on the top
3. Show running session and distinguish sessions with the same name
4. Not sub service until correct session id is ensured
5. Fix switch sides not work for multisession session
6. Remove all session string join/split except get_available_sessions in
windows.rs
7. Fix prelogin, when share rdp is enabled and there is a rdp session,
the console is in login screen, get_active_username will be the rdp's
username and prelogin will be false, cm can't be created an that
causes disconnection in a loop
8. Rename all user session to windows session
Known issue:
1. Use current process session id for `run_as_user`, sahil says it can
be wrong but I didn't reproduce.
2. Have not change tray process to current session
3. File transfer doesn't update home directory when session changed
4. When it's in login screen, remote file directory is empty, because cm
have not start up
Signed-off-by: 21pages <pages21@163.com>
2024-02-18 22:08:25 +08:00
|
|
|
color: disabledTextColor(context, enabled)))
|
2022-08-15 11:08:42 +08:00
|
|
|
.marginOnly(left: 5),
|
|
|
|
),
|
|
|
|
],
|
|
|
|
).marginOnly(left: _kRadioLeftMargin),
|
2022-09-08 21:40:43 +08:00
|
|
|
onTap: () => onChange?.call(value),
|
2022-08-15 11:08:42 +08:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-09-08 21:40:43 +08:00
|
|
|
// ignore: non_constant_identifier_names
|
2022-08-15 11:08:42 +08:00
|
|
|
Widget _Button(String label, Function() onPressed,
|
|
|
|
{bool enabled = true, String? tip}) {
|
|
|
|
var button = ElevatedButton(
|
2022-10-22 12:03:57 +08:00
|
|
|
onPressed: enabled ? onPressed : null,
|
|
|
|
child: Text(
|
|
|
|
translate(label),
|
|
|
|
).marginSymmetric(horizontal: 15),
|
|
|
|
);
|
2022-09-08 21:40:43 +08:00
|
|
|
StatefulWidget child;
|
2022-08-15 11:08:42 +08:00
|
|
|
if (tip == null) {
|
|
|
|
child = button;
|
|
|
|
} else {
|
|
|
|
child = Tooltip(message: translate(tip), child: button);
|
|
|
|
}
|
2022-08-13 12:43:35 +08:00
|
|
|
return Row(children: [
|
2022-08-15 11:08:42 +08:00
|
|
|
child,
|
|
|
|
]).marginOnly(left: _kContentHMargin);
|
2022-08-13 12:43:35 +08:00
|
|
|
}
|
|
|
|
|
2022-09-08 21:40:43 +08:00
|
|
|
// ignore: non_constant_identifier_names
|
2022-08-15 11:08:42 +08:00
|
|
|
Widget _SubButton(String label, Function() onPressed, [bool enabled = true]) {
|
|
|
|
return Row(
|
|
|
|
children: [
|
|
|
|
ElevatedButton(
|
2022-10-22 12:03:57 +08:00
|
|
|
onPressed: enabled ? onPressed : null,
|
|
|
|
child: Text(
|
|
|
|
translate(label),
|
|
|
|
).marginSymmetric(horizontal: 15),
|
|
|
|
),
|
2022-08-15 11:08:42 +08:00
|
|
|
],
|
|
|
|
).marginOnly(left: _kContentHSubMargin);
|
2022-08-13 12:43:35 +08:00
|
|
|
}
|
|
|
|
|
2022-09-08 21:40:43 +08:00
|
|
|
// ignore: non_constant_identifier_names
|
2023-02-12 19:59:51 +08:00
|
|
|
Widget _SubLabeledWidget(BuildContext context, String label, Widget child,
|
|
|
|
{bool enabled = true}) {
|
2022-08-13 12:43:35 +08:00
|
|
|
return Row(
|
|
|
|
children: [
|
2023-02-12 19:59:51 +08:00
|
|
|
Text(
|
|
|
|
'${translate(label)}: ',
|
refactor windows specific session (#7170)
1. Modify the process to have the control side lead the session switching: After the control side sends a `LoginRequest`, the controlled side will add all session information and the current session ID in the `LoginResponse`. Upon receiving the `LoginResponse`, the control side will check if the current session ID matches the ID in the `LoginConfigHandler`. If they match, the control side will send the current session ID. If they don't match, a session selection dialog will pop up, the selected session id will be sent. Upon receiving this message, the controlled side will restart if different or sub service if same .
2. Always show physical console session on the top
3. Show running session and distinguish sessions with the same name
4. Not sub service until correct session id is ensured
5. Fix switch sides not work for multisession session
6. Remove all session string join/split except get_available_sessions in
windows.rs
7. Fix prelogin, when share rdp is enabled and there is a rdp session,
the console is in login screen, get_active_username will be the rdp's
username and prelogin will be false, cm can't be created an that
causes disconnection in a loop
8. Rename all user session to windows session
Known issue:
1. Use current process session id for `run_as_user`, sahil says it can
be wrong but I didn't reproduce.
2. Have not change tray process to current session
3. File transfer doesn't update home directory when session changed
4. When it's in login screen, remote file directory is empty, because cm
have not start up
Signed-off-by: 21pages <pages21@163.com>
2024-02-18 22:08:25 +08:00
|
|
|
style: TextStyle(color: disabledTextColor(context, enabled)),
|
2023-02-12 19:59:51 +08:00
|
|
|
),
|
|
|
|
SizedBox(
|
|
|
|
width: 10,
|
|
|
|
),
|
|
|
|
child,
|
2022-08-13 12:43:35 +08:00
|
|
|
],
|
2022-08-15 11:08:42 +08:00
|
|
|
).marginOnly(left: _kContentHSubMargin);
|
2022-08-13 12:43:35 +08:00
|
|
|
}
|
|
|
|
|
2022-08-19 15:44:19 +08:00
|
|
|
Widget _lock(
|
|
|
|
bool locked,
|
|
|
|
String label,
|
|
|
|
Function() onUnlock,
|
|
|
|
) {
|
|
|
|
return Offstage(
|
|
|
|
offstage: !locked,
|
|
|
|
child: Row(
|
|
|
|
children: [
|
2022-10-22 12:03:57 +08:00
|
|
|
Flexible(
|
|
|
|
child: SizedBox(
|
|
|
|
width: _kCardFixedWidth,
|
|
|
|
child: Card(
|
|
|
|
child: ElevatedButton(
|
|
|
|
child: SizedBox(
|
|
|
|
height: 25,
|
|
|
|
child: Row(
|
|
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
|
|
children: [
|
|
|
|
const Icon(
|
|
|
|
Icons.security_sharp,
|
|
|
|
size: 20,
|
|
|
|
),
|
|
|
|
Text(translate(label)).marginOnly(left: 5),
|
|
|
|
]).marginSymmetric(vertical: 2)),
|
|
|
|
onPressed: () async {
|
2023-08-13 17:50:19 +08:00
|
|
|
bool checked = await callMainCheckSuperUserPermission();
|
2022-10-22 12:03:57 +08:00
|
|
|
if (checked) {
|
|
|
|
onUnlock();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
).marginSymmetric(horizontal: 2, vertical: 4),
|
|
|
|
).marginOnly(left: _kCardLeftMargin),
|
|
|
|
).marginOnly(top: 10),
|
|
|
|
),
|
2022-08-19 15:44:19 +08:00
|
|
|
],
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
2022-10-22 12:03:57 +08:00
|
|
|
_LabeledTextField(
|
|
|
|
BuildContext context,
|
2023-01-09 15:30:29 +08:00
|
|
|
String label,
|
2022-10-22 12:03:57 +08:00
|
|
|
TextEditingController controller,
|
|
|
|
String errorText,
|
|
|
|
bool enabled,
|
|
|
|
bool secure) {
|
|
|
|
return Row(
|
|
|
|
children: [
|
2023-02-20 01:00:58 +08:00
|
|
|
ConstrainedBox(
|
|
|
|
constraints: const BoxConstraints(minWidth: 140),
|
|
|
|
child: Text(
|
|
|
|
'${translate(label)}:',
|
|
|
|
textAlign: TextAlign.right,
|
|
|
|
style: TextStyle(
|
refactor windows specific session (#7170)
1. Modify the process to have the control side lead the session switching: After the control side sends a `LoginRequest`, the controlled side will add all session information and the current session ID in the `LoginResponse`. Upon receiving the `LoginResponse`, the control side will check if the current session ID matches the ID in the `LoginConfigHandler`. If they match, the control side will send the current session ID. If they don't match, a session selection dialog will pop up, the selected session id will be sent. Upon receiving this message, the controlled side will restart if different or sub service if same .
2. Always show physical console session on the top
3. Show running session and distinguish sessions with the same name
4. Not sub service until correct session id is ensured
5. Fix switch sides not work for multisession session
6. Remove all session string join/split except get_available_sessions in
windows.rs
7. Fix prelogin, when share rdp is enabled and there is a rdp session,
the console is in login screen, get_active_username will be the rdp's
username and prelogin will be false, cm can't be created an that
causes disconnection in a loop
8. Rename all user session to windows session
Known issue:
1. Use current process session id for `run_as_user`, sahil says it can
be wrong but I didn't reproduce.
2. Have not change tray process to current session
3. File transfer doesn't update home directory when session changed
4. When it's in login screen, remote file directory is empty, because cm
have not start up
Signed-off-by: 21pages <pages21@163.com>
2024-02-18 22:08:25 +08:00
|
|
|
fontSize: 16, color: disabledTextColor(context, enabled)),
|
2023-02-20 01:00:58 +08:00
|
|
|
).marginOnly(right: 10)),
|
2022-10-22 12:03:57 +08:00
|
|
|
Expanded(
|
|
|
|
child: TextField(
|
|
|
|
controller: controller,
|
|
|
|
enabled: enabled,
|
|
|
|
obscureText: secure,
|
|
|
|
decoration: InputDecoration(
|
|
|
|
errorText: errorText.isNotEmpty ? errorText : null),
|
|
|
|
style: TextStyle(
|
refactor windows specific session (#7170)
1. Modify the process to have the control side lead the session switching: After the control side sends a `LoginRequest`, the controlled side will add all session information and the current session ID in the `LoginResponse`. Upon receiving the `LoginResponse`, the control side will check if the current session ID matches the ID in the `LoginConfigHandler`. If they match, the control side will send the current session ID. If they don't match, a session selection dialog will pop up, the selected session id will be sent. Upon receiving this message, the controlled side will restart if different or sub service if same .
2. Always show physical console session on the top
3. Show running session and distinguish sessions with the same name
4. Not sub service until correct session id is ensured
5. Fix switch sides not work for multisession session
6. Remove all session string join/split except get_available_sessions in
windows.rs
7. Fix prelogin, when share rdp is enabled and there is a rdp session,
the console is in login screen, get_active_username will be the rdp's
username and prelogin will be false, cm can't be created an that
causes disconnection in a loop
8. Rename all user session to windows session
Known issue:
1. Use current process session id for `run_as_user`, sahil says it can
be wrong but I didn't reproduce.
2. Have not change tray process to current session
3. File transfer doesn't update home directory when session changed
4. When it's in login screen, remote file directory is empty, because cm
have not start up
Signed-off-by: 21pages <pages21@163.com>
2024-02-18 22:08:25 +08:00
|
|
|
color: disabledTextColor(context, enabled),
|
2022-10-22 12:03:57 +08:00
|
|
|
)),
|
|
|
|
),
|
|
|
|
],
|
2023-02-20 01:00:58 +08:00
|
|
|
).marginOnly(bottom: 8);
|
2022-10-22 12:03:57 +08:00
|
|
|
}
|
|
|
|
|
2023-10-11 19:03:34 +08:00
|
|
|
class _CountDownButton extends StatefulWidget {
|
|
|
|
_CountDownButton({
|
|
|
|
Key? key,
|
|
|
|
required this.text,
|
|
|
|
required this.second,
|
|
|
|
required this.onPressed,
|
|
|
|
}) : super(key: key);
|
|
|
|
final String text;
|
|
|
|
final VoidCallback? onPressed;
|
|
|
|
final int second;
|
|
|
|
|
|
|
|
@override
|
|
|
|
State<_CountDownButton> createState() => _CountDownButtonState();
|
|
|
|
}
|
|
|
|
|
|
|
|
class _CountDownButtonState extends State<_CountDownButton> {
|
|
|
|
bool _isButtonDisabled = false;
|
|
|
|
|
|
|
|
late int _countdownSeconds = widget.second;
|
|
|
|
|
|
|
|
Timer? _timer;
|
|
|
|
|
|
|
|
@override
|
|
|
|
void dispose() {
|
|
|
|
_timer?.cancel();
|
|
|
|
super.dispose();
|
|
|
|
}
|
|
|
|
|
|
|
|
void _startCountdownTimer() {
|
|
|
|
_timer = Timer.periodic(Duration(seconds: 1), (timer) {
|
|
|
|
if (_countdownSeconds <= 0) {
|
|
|
|
setState(() {
|
|
|
|
_isButtonDisabled = false;
|
|
|
|
});
|
|
|
|
timer.cancel();
|
|
|
|
} else {
|
|
|
|
setState(() {
|
|
|
|
_countdownSeconds--;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
return ElevatedButton(
|
|
|
|
onPressed: _isButtonDisabled
|
|
|
|
? null
|
|
|
|
: () {
|
|
|
|
widget.onPressed?.call();
|
|
|
|
setState(() {
|
|
|
|
_isButtonDisabled = true;
|
|
|
|
_countdownSeconds = widget.second;
|
|
|
|
});
|
|
|
|
_startCountdownTimer();
|
|
|
|
},
|
|
|
|
child: Text(
|
|
|
|
_isButtonDisabled ? '$_countdownSeconds s' : translate(widget.text),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-13 12:43:35 +08:00
|
|
|
//#endregion
|
|
|
|
|
|
|
|
//#region dialogs
|
|
|
|
|
|
|
|
void changeSocks5Proxy() async {
|
|
|
|
var socks = await bind.mainGetSocks();
|
|
|
|
|
2022-11-10 21:25:12 +08:00
|
|
|
String proxy = '';
|
|
|
|
String proxyMsg = '';
|
|
|
|
String username = '';
|
|
|
|
String password = '';
|
2022-08-13 12:43:35 +08:00
|
|
|
if (socks.length == 3) {
|
|
|
|
proxy = socks[0];
|
|
|
|
username = socks[1];
|
|
|
|
password = socks[2];
|
|
|
|
}
|
2022-08-28 21:43:18 +08:00
|
|
|
var proxyController = TextEditingController(text: proxy);
|
|
|
|
var userController = TextEditingController(text: username);
|
|
|
|
var pwdController = TextEditingController(text: password);
|
2023-02-16 23:23:46 +08:00
|
|
|
RxBool obscure = true.obs;
|
2022-08-13 12:43:35 +08:00
|
|
|
|
|
|
|
var isInProgress = false;
|
2023-05-08 12:34:19 +08:00
|
|
|
gFFI.dialogManager.show((setState, close, context) {
|
2022-09-03 18:19:50 +08:00
|
|
|
submit() async {
|
|
|
|
setState(() {
|
2022-11-10 21:25:12 +08:00
|
|
|
proxyMsg = '';
|
2022-09-03 18:19:50 +08:00
|
|
|
isInProgress = true;
|
|
|
|
});
|
|
|
|
cancel() {
|
|
|
|
setState(() {
|
|
|
|
isInProgress = false;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
proxy = proxyController.text.trim();
|
|
|
|
username = userController.text.trim();
|
|
|
|
password = pwdController.text.trim();
|
|
|
|
|
|
|
|
if (proxy.isNotEmpty) {
|
|
|
|
proxyMsg = translate(await bind.mainTestIfValidServer(server: proxy));
|
|
|
|
if (proxyMsg.isEmpty) {
|
|
|
|
// ignore
|
|
|
|
} else {
|
|
|
|
cancel();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
await bind.mainSetSocks(
|
|
|
|
proxy: proxy, username: username, password: password);
|
|
|
|
close();
|
|
|
|
}
|
|
|
|
|
2022-08-13 12:43:35 +08:00
|
|
|
return CustomAlertDialog(
|
2022-11-10 21:25:12 +08:00
|
|
|
title: Text(translate('Socks5 Proxy')),
|
2022-08-13 12:43:35 +08:00
|
|
|
content: ConstrainedBox(
|
2022-09-03 18:19:50 +08:00
|
|
|
constraints: const BoxConstraints(minWidth: 500),
|
2022-08-13 12:43:35 +08:00
|
|
|
child: Column(
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
|
children: [
|
|
|
|
Row(
|
|
|
|
children: [
|
|
|
|
ConstrainedBox(
|
2023-02-15 20:43:38 +08:00
|
|
|
constraints: const BoxConstraints(minWidth: 140),
|
|
|
|
child: Text(
|
2024-03-23 17:10:52 +08:00
|
|
|
'${translate("Server")}:',
|
2023-02-15 20:43:38 +08:00
|
|
|
textAlign: TextAlign.right,
|
|
|
|
).marginOnly(right: 10)),
|
2022-08-13 12:43:35 +08:00
|
|
|
Expanded(
|
|
|
|
child: TextField(
|
|
|
|
decoration: InputDecoration(
|
|
|
|
errorText: proxyMsg.isNotEmpty ? proxyMsg : null),
|
2022-08-28 21:43:18 +08:00
|
|
|
controller: proxyController,
|
2023-02-10 16:31:43 +08:00
|
|
|
autofocus: true,
|
2022-08-13 12:43:35 +08:00
|
|
|
),
|
|
|
|
),
|
|
|
|
],
|
2023-02-15 20:43:38 +08:00
|
|
|
).marginOnly(bottom: 8),
|
2022-08-13 12:43:35 +08:00
|
|
|
Row(
|
|
|
|
children: [
|
|
|
|
ConstrainedBox(
|
2023-02-15 20:43:38 +08:00
|
|
|
constraints: const BoxConstraints(minWidth: 140),
|
|
|
|
child: Text(
|
|
|
|
'${translate("Username")}:',
|
|
|
|
textAlign: TextAlign.right,
|
|
|
|
).marginOnly(right: 10)),
|
2022-08-13 12:43:35 +08:00
|
|
|
Expanded(
|
|
|
|
child: TextField(
|
2022-08-28 21:43:18 +08:00
|
|
|
controller: userController,
|
2022-08-13 12:43:35 +08:00
|
|
|
),
|
|
|
|
),
|
|
|
|
],
|
2023-02-15 20:43:38 +08:00
|
|
|
).marginOnly(bottom: 8),
|
2022-08-13 12:43:35 +08:00
|
|
|
Row(
|
|
|
|
children: [
|
|
|
|
ConstrainedBox(
|
2023-02-15 20:43:38 +08:00
|
|
|
constraints: const BoxConstraints(minWidth: 140),
|
|
|
|
child: Text(
|
|
|
|
'${translate("Password")}:',
|
|
|
|
textAlign: TextAlign.right,
|
|
|
|
).marginOnly(right: 10)),
|
2022-08-13 12:43:35 +08:00
|
|
|
Expanded(
|
2023-02-16 23:23:46 +08:00
|
|
|
child: Obx(() => TextField(
|
|
|
|
obscureText: obscure.value,
|
|
|
|
decoration: InputDecoration(
|
|
|
|
suffixIcon: IconButton(
|
|
|
|
onPressed: () => obscure.value = !obscure.value,
|
|
|
|
icon: Icon(obscure.value
|
|
|
|
? Icons.visibility_off
|
|
|
|
: Icons.visibility))),
|
|
|
|
controller: pwdController,
|
|
|
|
)),
|
2022-08-13 12:43:35 +08:00
|
|
|
),
|
|
|
|
],
|
2023-06-07 02:47:57 +08:00
|
|
|
),
|
2023-08-18 16:13:24 +08:00
|
|
|
// NOT use Offstage to wrap LinearProgressIndicator
|
|
|
|
if (isInProgress)
|
|
|
|
const LinearProgressIndicator().marginOnly(top: 8),
|
2022-08-13 12:43:35 +08:00
|
|
|
],
|
|
|
|
),
|
|
|
|
),
|
|
|
|
actions: [
|
2023-01-15 19:46:16 +08:00
|
|
|
dialogButton('Cancel', onPressed: close, isOutline: true),
|
|
|
|
dialogButton('OK', onPressed: submit),
|
2022-08-13 12:43:35 +08:00
|
|
|
],
|
2022-09-03 18:19:50 +08:00
|
|
|
onSubmit: submit,
|
|
|
|
onCancel: close,
|
2022-08-13 12:43:35 +08:00
|
|
|
);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
//#endregion
|