123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550 |
- import 'dart:async';
- import 'dart:convert';
- import 'dart:io';
- import 'package:file_picker/file_picker.dart';
- import 'package:flutter/material.dart';
- import 'package:flutter/services.dart';
- import 'package:flutter_hbb/common.dart';
- import 'package:flutter_hbb/common/widgets/audio_input.dart';
- import 'package:flutter_hbb/common/widgets/setting_widgets.dart';
- import 'package:flutter_hbb/consts.dart';
- import 'package:flutter_hbb/desktop/pages/desktop_home_page.dart';
- import 'package:flutter_hbb/desktop/pages/desktop_tab_page.dart';
- import 'package:flutter_hbb/models/platform_model.dart';
- import 'package:flutter_hbb/models/server_model.dart';
- import 'package:flutter_hbb/plugin/manager.dart';
- import 'package:flutter_hbb/plugin/widgets/desktop_settings.dart';
- import 'package:get/get.dart';
- import 'package:provider/provider.dart';
- import 'package:url_launcher/url_launcher.dart';
- import 'package:url_launcher/url_launcher_string.dart';
- import 'package:flutter_hbb/desktop/widgets/scroll_wrapper.dart';
- import '../../common/widgets/dialog.dart';
- import '../../common/widgets/login.dart';
- const double _kTabWidth = 200;
- const double _kTabHeight = 42;
- const double _kCardFixedWidth = 540;
- 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;
- const String _kSettingPageControllerTag = 'settingPageController';
- const String _kSettingPageTabKeyTag = 'settingPageTabKey';
- class _TabInfo {
- late final SettingsTabKey key;
- late final String label;
- late final IconData unselected;
- late final IconData selected;
- _TabInfo(this.key, this.label, this.unselected, this.selected);
- }
- enum SettingsTabKey {
- general,
- safety,
- network,
- display,
- plugin,
- account,
- about,
- }
- class DesktopSettingPage extends StatefulWidget {
- final SettingsTabKey initialTabkey;
- static final List<SettingsTabKey> tabKeys = [
- SettingsTabKey.general,
- if (!isWeb &&
- !bind.isOutgoingOnly() &&
- !bind.isDisableSettings() &&
- bind.mainGetBuildinOption(key: kOptionHideSecuritySetting) != 'Y')
- SettingsTabKey.safety,
- if (!bind.isDisableSettings() &&
- bind.mainGetBuildinOption(key: kOptionHideNetworkSetting) != 'Y')
- SettingsTabKey.network,
- if (!bind.isIncomingOnly()) SettingsTabKey.display,
- if (!isWeb && !bind.isIncomingOnly() && bind.pluginFeatureIsEnabled())
- SettingsTabKey.plugin,
- if (!bind.isDisableAccount()) SettingsTabKey.account,
- SettingsTabKey.about,
- ];
- DesktopSettingPage({Key? key, required this.initialTabkey}) : super(key: key);
- @override
- State<DesktopSettingPage> createState() =>
- _DesktopSettingPageState(initialTabkey);
- static void switch2page(SettingsTabKey page) {
- try {
- int index = tabKeys.indexOf(page);
- if (index == -1) {
- return;
- }
- if (Get.isRegistered<PageController>(tag: _kSettingPageControllerTag)) {
- DesktopTabPage.onAddSetting(initialPage: page);
- PageController controller =
- Get.find<PageController>(tag: _kSettingPageControllerTag);
- Rx<SettingsTabKey> selected =
- Get.find<Rx<SettingsTabKey>>(tag: _kSettingPageTabKeyTag);
- selected.value = page;
- controller.jumpToPage(index);
- } else {
- DesktopTabPage.onAddSetting(initialPage: page);
- }
- } catch (e) {
- debugPrintStack(label: '$e');
- }
- }
- }
- class _DesktopSettingPageState extends State<DesktopSettingPage>
- with TickerProviderStateMixin, AutomaticKeepAliveClientMixin {
- late PageController controller;
- late Rx<SettingsTabKey> selectedTab;
- @override
- bool get wantKeepAlive => true;
- _DesktopSettingPageState(SettingsTabKey initialTabkey) {
- var initialIndex = DesktopSettingPage.tabKeys.indexOf(initialTabkey);
- if (initialIndex == -1) {
- initialIndex = 0;
- }
- selectedTab = DesktopSettingPage.tabKeys[initialIndex].obs;
- Get.put<Rx<SettingsTabKey>>(selectedTab, tag: _kSettingPageTabKeyTag);
- controller = PageController(initialPage: initialIndex);
- Get.put<PageController>(controller, tag: _kSettingPageControllerTag);
- controller.addListener(() {
- if (controller.page != null) {
- int page = controller.page!.toInt();
- if (page < DesktopSettingPage.tabKeys.length) {
- selectedTab.value = DesktopSettingPage.tabKeys[page];
- }
- }
- });
- }
- @override
- void dispose() {
- super.dispose();
- Get.delete<PageController>(tag: _kSettingPageControllerTag);
- Get.delete<RxInt>(tag: _kSettingPageTabKeyTag);
- }
- List<_TabInfo> _settingTabs() {
- final List<_TabInfo> settingTabs = <_TabInfo>[];
- for (final tab in DesktopSettingPage.tabKeys) {
- switch (tab) {
- case SettingsTabKey.general:
- settingTabs.add(_TabInfo(
- tab, 'General', Icons.settings_outlined, Icons.settings));
- break;
- case SettingsTabKey.safety:
- settingTabs.add(_TabInfo(tab, 'Security',
- Icons.enhanced_encryption_outlined, Icons.enhanced_encryption));
- break;
- case SettingsTabKey.network:
- settingTabs
- .add(_TabInfo(tab, 'Network', Icons.link_outlined, Icons.link));
- break;
- case SettingsTabKey.display:
- settingTabs.add(_TabInfo(tab, 'Display',
- Icons.desktop_windows_outlined, Icons.desktop_windows));
- break;
- case SettingsTabKey.plugin:
- settingTabs.add(_TabInfo(
- tab, 'Plugin', Icons.extension_outlined, Icons.extension));
- break;
- case SettingsTabKey.account:
- settingTabs.add(
- _TabInfo(tab, 'Account', Icons.person_outline, Icons.person));
- break;
- case SettingsTabKey.about:
- settingTabs
- .add(_TabInfo(tab, 'About', Icons.info_outline, Icons.info));
- break;
- }
- }
- return settingTabs;
- }
- List<Widget> _children() {
- final children = List<Widget>.empty(growable: true);
- for (final tab in DesktopSettingPage.tabKeys) {
- switch (tab) {
- case SettingsTabKey.general:
- children.add(const _General());
- break;
- case SettingsTabKey.safety:
- children.add(const _Safety());
- break;
- case SettingsTabKey.network:
- children.add(const _Network());
- break;
- case SettingsTabKey.display:
- children.add(const _Display());
- break;
- case SettingsTabKey.plugin:
- children.add(const _Plugin());
- break;
- case SettingsTabKey.account:
- children.add(const _Account());
- break;
- case SettingsTabKey.about:
- children.add(const _About());
- break;
- }
- }
- return children;
- }
- @override
- Widget build(BuildContext context) {
- super.build(context);
- return Scaffold(
- backgroundColor: Theme.of(context).colorScheme.background,
- body: Row(
- children: <Widget>[
- SizedBox(
- width: _kTabWidth,
- child: Column(
- children: [
- _header(context),
- Flexible(child: _listView(tabs: _settingTabs())),
- ],
- ),
- ),
- const VerticalDivider(width: 1),
- Expanded(
- child: Container(
- color: Theme.of(context).scaffoldBackgroundColor,
- child: DesktopScrollWrapper(
- scrollController: controller,
- child: PageView(
- controller: controller,
- physics: NeverScrollableScrollPhysics(),
- children: _children(),
- )),
- ),
- )
- ],
- ),
- );
- }
- Widget _header(BuildContext context) {
- final settingsText = Text(
- translate('Settings'),
- textAlign: TextAlign.left,
- style: const TextStyle(
- color: _accentColor,
- fontSize: _kTitleFontSize,
- fontWeight: FontWeight.w400,
- ),
- );
- return Row(
- children: [
- if (isWeb)
- IconButton(
- onPressed: () {
- if (Navigator.canPop(context)) {
- Navigator.pop(context);
- }
- },
- icon: Icon(Icons.arrow_back),
- ).marginOnly(left: 5),
- if (isWeb)
- SizedBox(
- height: 62,
- child: Align(
- alignment: Alignment.center,
- child: settingsText,
- ),
- ).marginOnly(left: 20),
- if (!isWeb)
- SizedBox(
- height: 62,
- child: settingsText,
- ).marginOnly(left: 20, top: 10),
- const Spacer(),
- ],
- );
- }
- Widget _listView({required List<_TabInfo> tabs}) {
- final scrollController = ScrollController();
- return DesktopScrollWrapper(
- scrollController: scrollController,
- child: ListView(
- physics: DraggableNeverScrollableScrollPhysics(),
- controller: scrollController,
- children: tabs.map((tab) => _listItem(tab: tab)).toList(),
- ));
- }
- Widget _listItem({required _TabInfo tab}) {
- return Obx(() {
- bool selected = tab.key == selectedTab.value;
- return SizedBox(
- width: _kTabWidth,
- height: _kTabHeight,
- child: InkWell(
- onTap: () {
- if (selectedTab.value != tab.key) {
- int index = DesktopSettingPage.tabKeys.indexOf(tab.key);
- if (index == -1) {
- return;
- }
- controller.jumpToPage(index);
- }
- selectedTab.value = tab.key;
- },
- 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),
- ),
- ]),
- ),
- );
- });
- }
- }
- //#region pages
- class _General extends StatefulWidget {
- const _General({Key? key}) : super(key: key);
- @override
- State<_General> createState() => _GeneralState();
- }
- class _GeneralState extends State<_General> {
- final RxBool serviceStop =
- isWeb ? RxBool(false) : Get.find<RxBool>(tag: 'stop-service');
- RxBool serviceBtnEnabled = true.obs;
- @override
- Widget build(BuildContext context) {
- final scrollController = ScrollController();
- return DesktopScrollWrapper(
- scrollController: scrollController,
- child: ListView(
- physics: DraggableNeverScrollableScrollPhysics(),
- controller: scrollController,
- children: [
- if (!isWeb) service(),
- theme(),
- _Card(title: 'Language', children: [language()]),
- if (!isWeb) hwcodec(),
- if (!isWeb) audio(context),
- if (!isWeb) record(context),
- if (!isWeb) WaylandCard(),
- other()
- ],
- ).marginOnly(bottom: _kListViewBottomMargin));
- }
- Widget theme() {
- final current = MyTheme.getThemeModePreference().toShortString();
- onChanged(String value) async {
- await MyTheme.changeDarkMode(MyTheme.themeModeFromString(value));
- setState(() {});
- }
- final isOptFixed = isOptionFixed(kCommConfKeyTheme);
- return _Card(title: 'Theme', children: [
- _Radio<String>(context,
- value: 'light',
- groupValue: current,
- label: 'Light',
- onChanged: isOptFixed ? null : onChanged),
- _Radio<String>(context,
- value: 'dark',
- groupValue: current,
- label: 'Dark',
- onChanged: isOptFixed ? null : onChanged),
- _Radio<String>(context,
- value: 'system',
- groupValue: current,
- label: 'Follow System',
- onChanged: isOptFixed ? null : onChanged),
- ]);
- }
- Widget service() {
- if (bind.isOutgoingOnly()) {
- return const Offstage();
- }
- return _Card(title: 'Service', children: [
- Obx(() => _Button(serviceStop.value ? 'Start' : 'Stop', () {
- () async {
- serviceBtnEnabled.value = false;
- await start_service(serviceStop.value);
- // enable the button after 1 second
- Future.delayed(const Duration(seconds: 1), () {
- serviceBtnEnabled.value = true;
- });
- }();
- }, enabled: serviceBtnEnabled.value))
- ]);
- }
- Widget other() {
- final children = <Widget>[
- if (!isWeb && !bind.isIncomingOnly())
- _OptionCheckBox(context, 'Confirm before closing multiple tabs',
- kOptionEnableConfirmClosingTabs,
- isServer: false),
- _OptionCheckBox(context, 'Adaptive bitrate', kOptionEnableAbr),
- if (!isWeb) wallpaper(),
- if (!isWeb && !bind.isIncomingOnly()) ...[
- _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
- if (isLinux)
- Tooltip(
- message: translate('software_render_tip'),
- child: _OptionCheckBox(
- context,
- "Always use software rendering",
- kOptionAllowAlwaysSoftwareRender,
- ),
- ),
- if (!isWeb)
- Tooltip(
- message: translate('texture_render_tip'),
- child: _OptionCheckBox(
- context,
- "Use texture rendering",
- kOptionTextureRender,
- optGetter: bind.mainGetUseTextureRender,
- optSetter: (k, v) async =>
- await bind.mainSetLocalOption(key: k, value: v ? 'Y' : 'N'),
- ),
- ),
- if (!isWeb && !bind.isCustomClient())
- _OptionCheckBox(
- context,
- 'Check for software update on startup',
- kOptionEnableCheckUpdate,
- isServer: false,
- ),
- if (isWindows && !bind.isOutgoingOnly())
- _OptionCheckBox(
- context,
- 'Capture screen using DirectX',
- kOptionDirectxCapture,
- )
- ],
- ];
- if (!isWeb && bind.mainShowOption(key: kOptionAllowLinuxHeadless)) {
- children.add(_OptionCheckBox(
- context, 'Allow linux headless', kOptionAllowLinuxHeadless));
- }
- return _Card(title: 'Other', children: children);
- }
- Widget wallpaper() {
- if (bind.isOutgoingOnly()) {
- return const Offstage();
- }
- return futureBuilder(future: () async {
- final support = await bind.mainSupportRemoveWallpaper();
- return support;
- }(), hasData: (data) {
- if (data is bool && data == true) {
- bool value = mainGetBoolOptionSync(kOptionAllowRemoveWallpaper);
- return Row(
- children: [
- Flexible(
- child: _OptionCheckBox(
- context,
- 'Remove wallpaper during incoming sessions',
- kOptionAllowRemoveWallpaper,
- update: (bool v) {
- setState(() {});
- },
- ),
- ),
- if (value)
- _CountDownButton(
- text: 'Test',
- second: 5,
- onPressed: () {
- bind.mainTestWallpaper(second: 5);
- },
- )
- ],
- );
- }
- return Offstage();
- });
- }
- Widget hwcodec() {
- final hwcodec = bind.mainHasHwcodec();
- final vram = bind.mainHasVram();
- return Offstage(
- offstage: !(hwcodec || vram),
- child: _Card(title: 'Hardware Codec', children: [
- _OptionCheckBox(
- context,
- 'Enable hardware codec',
- kOptionEnableHwcodec,
- update: (bool v) {
- if (v) {
- bind.mainCheckHwcodec();
- }
- },
- )
- ]),
- );
- }
- Widget audio(BuildContext context) {
- if (bind.isOutgoingOnly()) {
- return const Offstage();
- }
- builder(devices, currentDevice, setDevice) {
- final child = ComboBox(
- keys: devices,
- values: devices,
- initialKey: currentDevice,
- onChanged: (key) async {
- setDevice(key);
- setState(() {});
- },
- ).marginOnly(left: _kContentHMargin);
- return _Card(title: 'Audio Input Device', children: [child]);
- }
- return AudioInput(builder: builder, isCm: false, isVoiceCall: false);
- }
- Widget record(BuildContext context) {
- final showRootDir = isWindows && bind.mainIsInstalled();
- return futureBuilder(future: () async {
- String user_dir = bind.mainVideoSaveDirectory(root: false);
- String root_dir =
- showRootDir ? bind.mainVideoSaveDirectory(root: true) : '';
- bool user_dir_exists = await Directory(user_dir).exists();
- bool root_dir_exists =
- showRootDir ? await Directory(root_dir).exists() : false;
- // canLaunchUrl blocked on windows portable, user SYSTEM
- return {
- 'user_dir': user_dir,
- 'root_dir': root_dir,
- 'user_dir_exists': user_dir_exists,
- 'root_dir_exists': root_dir_exists,
- };
- }(), hasData: (data) {
- Map<String, dynamic> map = data as Map<String, dynamic>;
- String user_dir = map['user_dir']!;
- String root_dir = map['root_dir']!;
- bool root_dir_exists = map['root_dir_exists']!;
- bool user_dir_exists = map['user_dir_exists']!;
- return _Card(title: 'Recording', children: [
- if (!bind.isOutgoingOnly())
- _OptionCheckBox(context, 'Automatically record incoming sessions',
- kOptionAllowAutoRecordIncoming),
- if (!bind.isIncomingOnly())
- _OptionCheckBox(context, 'Automatically record outgoing sessions',
- kOptionAllowAutoRecordOutgoing,
- isServer: false),
- if (showRootDir && !bind.isOutgoingOnly())
- Row(
- children: [
- Text(
- '${translate(bind.isIncomingOnly() ? "Directory" : "Incoming")}:'),
- Expanded(
- child: GestureDetector(
- onTap: root_dir_exists
- ? () => launchUrl(Uri.file(root_dir))
- : null,
- child: Text(
- root_dir,
- softWrap: true,
- style: root_dir_exists
- ? const TextStyle(
- decoration: TextDecoration.underline)
- : null,
- )).marginOnly(left: 10),
- ),
- ],
- ).marginOnly(left: _kContentHMargin),
- if (!(showRootDir && bind.isIncomingOnly()))
- Row(
- children: [
- Text(
- '${translate((showRootDir && !bind.isOutgoingOnly()) ? "Outgoing" : "Directory")}:'),
- Expanded(
- child: GestureDetector(
- onTap: user_dir_exists
- ? () => launchUrl(Uri.file(user_dir))
- : null,
- child: Text(
- user_dir,
- softWrap: true,
- style: user_dir_exists
- ? const TextStyle(
- decoration: TextDecoration.underline)
- : null,
- )).marginOnly(left: 10),
- ),
- ElevatedButton(
- onPressed: isOptionFixed(kOptionVideoSaveDirectory)
- ? null
- : () async {
- String? initialDirectory;
- if (await Directory.fromUri(
- Uri.directory(user_dir))
- .exists()) {
- initialDirectory = user_dir;
- }
- String? selectedDirectory =
- await FilePicker.platform.getDirectoryPath(
- initialDirectory: initialDirectory);
- if (selectedDirectory != null) {
- await bind.mainSetLocalOption(
- key: kOptionVideoSaveDirectory,
- value: selectedDirectory);
- setState(() {});
- }
- },
- child: Text(translate('Change')))
- .marginOnly(left: 5),
- ],
- ).marginOnly(left: _kContentHMargin),
- ]);
- });
- }
- Widget language() {
- return futureBuilder(future: () async {
- String langs = await bind.mainGetLangs();
- return {'langs': langs};
- }(), hasData: (res) {
- Map<String, String> data = res as Map<String, String>;
- List<dynamic> langsList = jsonDecode(data['langs']!);
- 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();
- keys.insert(0, defaultOptionLang);
- values.insert(0, translate('Default'));
- String currentKey = bind.mainGetLocalOption(key: kCommConfKeyLang);
- if (!keys.contains(currentKey)) {
- currentKey = defaultOptionLang;
- }
- final isOptFixed = isOptionFixed(kCommConfKeyLang);
- return ComboBox(
- keys: keys,
- values: values,
- initialKey: currentKey,
- onChanged: (key) async {
- await bind.mainSetLocalOption(key: kCommConfKeyLang, value: key);
- if (isWeb) reloadCurrentWindow();
- if (!isWeb) reloadAllWindows();
- if (!isWeb) bind.mainChangeLanguage(lang: key);
- },
- enabled: !isOptFixed,
- ).marginOnly(left: _kContentHMargin);
- });
- }
- }
- enum _AccessMode {
- custom,
- full,
- view,
- }
- 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;
- bool locked = bind.mainIsInstalled();
- final scrollController = ScrollController();
- @override
- Widget build(BuildContext context) {
- super.build(context);
- return DesktopScrollWrapper(
- scrollController: scrollController,
- child: SingleChildScrollView(
- physics: DraggableNeverScrollableScrollPhysics(),
- controller: scrollController,
- child: Column(
- children: [
- _lock(locked, 'Unlock Security Settings', () {
- locked = false;
- setState(() => {});
- }),
- AbsorbPointer(
- absorbing: locked,
- child: Column(children: [
- permissions(context),
- password(context),
- _Card(title: '2FA', children: [tfa()]),
- _Card(title: 'ID', children: [changeId()]),
- more(context),
- ]),
- ),
- ],
- )).marginOnly(bottom: _kListViewBottomMargin));
- }
- Widget tfa() {
- bool enabled = !locked;
- // Simple temp wrapper for PR check
- tmpWrapper() {
- RxBool has2fa = bind.mainHasValid2FaSync().obs;
- RxBool hasBot = bind.mainHasValidBotSync().obs;
- update() async {
- has2fa.value = bind.mainHasValid2FaSync();
- setState(() {});
- }
- onChanged(bool? checked) async {
- if (checked == false) {
- CommonConfirmDialog(
- gFFI.dialogManager, translate('cancel-2fa-confirm-tip'), () {
- change2fa(callback: update);
- });
- } else {
- change2fa(callback: update);
- }
- }
- final tfa = 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:
- TextStyle(color: disabledTextColor(context, enabled)),
- ))
- ],
- )),
- ),
- onTap: () {
- onChanged(!has2fa.value);
- },
- ).marginOnly(left: _kCheckBoxLeftMargin);
- if (!has2fa.value) {
- return tfa;
- }
- updateBot() async {
- hasBot.value = bind.mainHasValidBotSync();
- setState(() {});
- }
- onChangedBot(bool? checked) async {
- if (checked == false) {
- CommonConfirmDialog(
- gFFI.dialogManager, translate('cancel-bot-confirm-tip'), () {
- changeBot(callback: updateBot);
- });
- } else {
- changeBot(callback: updateBot);
- }
- }
- final bot = GestureDetector(
- child: Tooltip(
- waitDuration: Duration(milliseconds: 300),
- message: translate("enable-bot-tip"),
- child: InkWell(
- child: Obx(() => Row(
- children: [
- Checkbox(
- value: hasBot.value,
- onChanged: enabled ? onChangedBot : null)
- .marginOnly(right: 5),
- Expanded(
- child: Text(
- translate('Telegram bot'),
- style: TextStyle(
- color: disabledTextColor(context, enabled)),
- ))
- ],
- ))),
- ),
- onTap: () {
- onChangedBot(!hasBot.value);
- },
- ).marginOnly(left: _kCheckBoxLeftMargin + 30);
- final trust = Row(
- children: [
- Flexible(
- child: Tooltip(
- waitDuration: Duration(milliseconds: 300),
- message: translate("enable-trusted-devices-tip"),
- child: _OptionCheckBox(context, "Enable trusted devices",
- kOptionEnableTrustedDevices,
- enabled: !locked, update: (v) {
- setState(() {});
- }),
- ),
- ),
- if (mainGetBoolOptionSync(kOptionEnableTrustedDevices))
- ElevatedButton(
- onPressed: locked
- ? null
- : () {
- manageTrustedDeviceDialog();
- },
- child: Text(translate('Manage trusted devices')))
- ],
- ).marginOnly(left: 30);
- return Column(
- children: [tfa, bot, trust],
- );
- }
- return tmpWrapper();
- }
- Widget changeId() {
- return ChangeNotifierProvider.value(
- value: gFFI.serverModel,
- child: Consumer<ServerModel>(builder: ((context, model, child) {
- return _Button('Change ID', changeIdDialog,
- enabled: !locked && model.connectStatus > 0);
- })));
- }
- Widget permissions(context) {
- bool enabled = !locked;
- // Simple temp wrapper for PR check
- tmpWrapper() {
- String accessMode = bind.mainGetOptionSync(key: kOptionAccessMode);
- _AccessMode mode;
- if (accessMode == 'full') {
- mode = _AccessMode.full;
- } else if (accessMode == 'view') {
- mode = _AccessMode.view;
- } else {
- mode = _AccessMode.custom;
- }
- String initialKey;
- bool? fakeValue;
- switch (mode) {
- case _AccessMode.custom:
- initialKey = '';
- fakeValue = null;
- break;
- case _AccessMode.full:
- initialKey = 'full';
- fakeValue = true;
- break;
- case _AccessMode.view:
- initialKey = 'view';
- fakeValue = false;
- break;
- }
- return _Card(title: 'Permissions', children: [
- ComboBox(
- keys: [
- defaultOptionAccessMode,
- 'full',
- 'view',
- ],
- values: [
- translate('Custom'),
- translate('Full Access'),
- translate('Screen Share'),
- ],
- enabled: enabled && !isOptionFixed(kOptionAccessMode),
- initialKey: initialKey,
- onChanged: (mode) async {
- await bind.mainSetOption(key: kOptionAccessMode, value: mode);
- setState(() {});
- }).marginOnly(left: _kContentHMargin),
- Column(
- children: [
- _OptionCheckBox(
- context, 'Enable keyboard/mouse', kOptionEnableKeyboard,
- enabled: enabled, fakeValue: fakeValue),
- _OptionCheckBox(context, 'Enable clipboard', kOptionEnableClipboard,
- enabled: enabled, fakeValue: fakeValue),
- _OptionCheckBox(
- context, 'Enable file transfer', kOptionEnableFileTransfer,
- enabled: enabled, fakeValue: fakeValue),
- _OptionCheckBox(context, 'Enable audio', kOptionEnableAudio,
- enabled: enabled, fakeValue: fakeValue),
- _OptionCheckBox(
- context, 'Enable TCP tunneling', kOptionEnableTunnel,
- enabled: enabled, fakeValue: fakeValue),
- _OptionCheckBox(
- context, 'Enable remote restart', kOptionEnableRemoteRestart,
- enabled: enabled, fakeValue: fakeValue),
- _OptionCheckBox(
- context, 'Enable recording session', kOptionEnableRecordSession,
- enabled: enabled, fakeValue: fakeValue),
- if (isWindows)
- _OptionCheckBox(context, 'Enable blocking user input',
- kOptionEnableBlockInput,
- enabled: enabled, fakeValue: fakeValue),
- _OptionCheckBox(context, 'Enable remote configuration modification',
- kOptionAllowRemoteConfigModification,
- enabled: enabled, fakeValue: fakeValue),
- ],
- ),
- ]);
- }
- return tmpWrapper();
- }
- Widget password(BuildContext context) {
- return ChangeNotifierProvider.value(
- value: gFFI.serverModel,
- child: Consumer<ServerModel>(builder: ((context, model, child) {
- List<String> passwordKeys = [
- kUseTemporaryPassword,
- kUsePermanentPassword,
- kUseBothPasswords,
- ];
- List<String> passwordValues = [
- translate('Use one-time password'),
- translate('Use permanent password'),
- translate('Use both passwords'),
- ];
- bool tmpEnabled = model.verificationMethod != kUsePermanentPassword;
- bool permEnabled = model.verificationMethod != kUseTemporaryPassword;
- String currentValue =
- passwordValues[passwordKeys.indexOf(model.verificationMethod)];
- List<Widget> radios = passwordValues
- .map((value) => _Radio<String>(
- context,
- value: value,
- groupValue: currentValue,
- label: value,
- onChanged: locked
- ? null
- : ((value) async {
- callback() async {
- await model.setVerificationMethod(
- passwordKeys[passwordValues.indexOf(value)]);
- await model.updatePasswordModel();
- }
- if (value ==
- passwordValues[passwordKeys
- .indexOf(kUsePermanentPassword)] &&
- (await bind.mainGetPermanentPassword())
- .isEmpty) {
- setPasswordDialog(notEmptyCallback: callback);
- } else {
- await callback();
- }
- }),
- ))
- .toList();
- var onChanged = tmpEnabled && !locked
- ? (value) {
- if (value != null) {
- () async {
- await model.setTemporaryPasswordLength(value.toString());
- await model.updatePasswordModel();
- }();
- }
- }
- : null;
- List<Widget> lengthRadios = ['6', '8', '10']
- .map((value) => GestureDetector(
- child: Row(
- children: [
- Radio(
- value: value,
- groupValue: model.temporaryPasswordLength,
- onChanged: onChanged),
- Text(
- value,
- style: TextStyle(
- color: disabledTextColor(
- context, onChanged != null)),
- ),
- ],
- ).paddingOnly(right: 10),
- onTap: () => onChanged?.call(value),
- ))
- .toList();
- final modeKeys = <String>[
- 'password',
- 'click',
- defaultOptionApproveMode
- ];
- 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 = defaultOptionApproveMode;
- }
- final usePassword = model.approveMode != 'click';
- final isApproveModeFixed = isOptionFixed(kOptionApproveMode);
- return _Card(title: 'Password', children: [
- ComboBox(
- enabled: !locked && !isApproveModeFixed,
- keys: modeKeys,
- values: modeValues,
- initialKey: modeInitialKey,
- onChanged: (key) => model.setApproveMode(key),
- ).marginOnly(left: _kContentHMargin),
- if (usePassword) radios[0],
- if (usePassword)
- _SubLabeledWidget(
- context,
- 'One-time password length',
- Row(
- children: [
- ...lengthRadios,
- ],
- ),
- enabled: tmpEnabled && !locked),
- if (usePassword) radios[1],
- if (usePassword)
- _SubButton('Set permanent password', setPasswordDialog,
- permEnabled && !locked),
- // if (usePassword)
- // hide_cm(!locked).marginOnly(left: _kContentHSubMargin - 6),
- if (usePassword) radios[2],
- ]);
- })));
- }
- Widget more(BuildContext context) {
- bool enabled = !locked;
- return _Card(title: 'Security', children: [
- shareRdp(context, enabled),
- _OptionCheckBox(context, 'Deny LAN discovery', 'enable-lan-discovery',
- reverse: true, enabled: enabled),
- ...directIp(context),
- whitelist(),
- ...autoDisconnect(context),
- if (bind.mainIsInstalled())
- _OptionCheckBox(context, 'allow-only-conn-window-open-tip',
- 'allow-only-conn-window-open',
- reverse: false, enabled: enabled),
- if (bind.mainIsInstalled()) unlockPin()
- ]);
- }
- shareRdp(BuildContext context, bool enabled) {
- onChanged(bool b) async {
- await bind.mainSetShareRdp(enable: b);
- setState(() {});
- }
- bool value = bind.mainIsShareRdp();
- return Offstage(
- offstage: !(isWindows && bind.mainIsInstalled()),
- 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:
- TextStyle(color: disabledTextColor(context, enabled))),
- )
- ],
- ).marginOnly(left: _kCheckBoxLeftMargin),
- onTap: enabled ? () => onChanged(!value) : null),
- );
- }
- List<Widget> directIp(BuildContext context) {
- TextEditingController controller = TextEditingController();
- update(bool v) => setState(() {});
- RxBool applyEnabled = false.obs;
- return [
- _OptionCheckBox(context, 'Enable direct IP access', kOptionDirectServer,
- update: update, enabled: !locked),
- () {
- // Simple temp wrapper for PR check
- tmpWrapper() {
- bool enabled = option2bool(kOptionDirectServer,
- bind.mainGetOptionSync(key: kOptionDirectServer));
- if (!enabled) applyEnabled.value = false;
- controller.text =
- bind.mainGetOptionSync(key: kOptionDirectAccessPort);
- final isOptFixed = isOptionFixed(kOptionDirectAccessPort);
- return Offstage(
- offstage: !enabled,
- child: _SubLabeledWidget(
- context,
- 'Port',
- Row(children: [
- SizedBox(
- width: 95,
- child: TextField(
- controller: controller,
- enabled: enabled && !locked && !isOptFixed,
- onChanged: (_) => applyEnabled.value = true,
- inputFormatters: [
- FilteringTextInputFormatter.allow(RegExp(
- r'^([0-9]|[1-9]\d|[1-9]\d{2}|[1-9]\d{3}|[1-5]\d{4}|6[0-4]\d{3}|65[0-4]\d{2}|655[0-2]\d|6553[0-5])$')),
- ],
- decoration: const InputDecoration(
- hintText: '21118',
- contentPadding:
- EdgeInsets.symmetric(vertical: 12, horizontal: 12),
- ),
- ).marginOnly(right: 15),
- ),
- Obx(() => ElevatedButton(
- onPressed: applyEnabled.value &&
- enabled &&
- !locked &&
- !isOptFixed
- ? () async {
- applyEnabled.value = false;
- await bind.mainSetOption(
- key: kOptionDirectAccessPort,
- value: controller.text);
- }
- : null,
- child: Text(
- translate('Apply'),
- ),
- ))
- ]),
- enabled: enabled && !locked && !isOptFixed,
- ),
- );
- }
- return tmpWrapper();
- }(),
- ];
- }
- Widget whitelist() {
- bool enabled = !locked;
- // Simple temp wrapper for PR check
- tmpWrapper() {
- RxBool hasWhitelist = whitelistNotEmpty().obs;
- update() async {
- hasWhitelist.value = whitelistNotEmpty();
- }
- onChanged(bool? checked) async {
- changeWhiteList(callback: update);
- }
- final isOptFixed = isOptionFixed(kOptionWhitelist);
- return GestureDetector(
- child: Tooltip(
- message: translate('whitelist_tip'),
- child: Obx(() => Row(
- children: [
- Checkbox(
- value: hasWhitelist.value,
- onChanged: enabled && !isOptFixed ? onChanged : null)
- .marginOnly(right: 5),
- Offstage(
- offstage: !hasWhitelist.value,
- child: MouseRegion(
- child: const Icon(Icons.warning_amber_rounded,
- color: Color.fromARGB(255, 255, 204, 0))
- .marginOnly(right: 5),
- cursor: SystemMouseCursors.click,
- ),
- ),
- Expanded(
- child: Text(
- translate('Use IP Whitelisting'),
- style:
- TextStyle(color: disabledTextColor(context, enabled)),
- ))
- ],
- )),
- ),
- onTap: enabled
- ? () {
- onChanged(!hasWhitelist.value);
- }
- : null,
- ).marginOnly(left: _kCheckBoxLeftMargin);
- }
- return tmpWrapper();
- }
- 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(
- color: disabledTextColor(
- context, enabled && enableHideCm)),
- ),
- ),
- ],
- ),
- ));
- }));
- }
- List<Widget> autoDisconnect(BuildContext context) {
- TextEditingController controller = TextEditingController();
- update(bool v) => setState(() {});
- RxBool applyEnabled = false.obs;
- return [
- _OptionCheckBox(
- context, 'auto_disconnect_option_tip', kOptionAllowAutoDisconnect,
- update: update, enabled: !locked),
- () {
- bool enabled = option2bool(kOptionAllowAutoDisconnect,
- bind.mainGetOptionSync(key: kOptionAllowAutoDisconnect));
- if (!enabled) applyEnabled.value = false;
- controller.text =
- bind.mainGetOptionSync(key: kOptionAutoDisconnectTimeout);
- final isOptFixed = isOptionFixed(kOptionAutoDisconnectTimeout);
- return Offstage(
- offstage: !enabled,
- child: _SubLabeledWidget(
- context,
- 'Timeout in minutes',
- Row(children: [
- SizedBox(
- width: 95,
- child: TextField(
- controller: controller,
- enabled: enabled && !locked && !isOptFixed,
- 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 && !isOptFixed
- ? () async {
- applyEnabled.value = false;
- await bind.mainSetOption(
- key: kOptionAutoDisconnectTimeout,
- value: controller.text);
- }
- : null,
- child: Text(
- translate('Apply'),
- ),
- ))
- ]),
- enabled: enabled && !locked && !isOptFixed,
- ),
- );
- }(),
- ];
- }
- Widget unlockPin() {
- bool enabled = !locked;
- RxString unlockPin = bind.mainGetUnlockPin().obs;
- update() async {
- unlockPin.value = bind.mainGetUnlockPin();
- }
- onChanged(bool? checked) async {
- changeUnlockPinDialog(unlockPin.value, update);
- }
- final isOptFixed = isOptionFixed(kOptionWhitelist);
- return GestureDetector(
- child: Obx(() => Row(
- children: [
- Checkbox(
- value: unlockPin.isNotEmpty,
- onChanged: enabled && !isOptFixed ? onChanged : null)
- .marginOnly(right: 5),
- Expanded(
- child: Text(
- translate('Unlock with PIN'),
- style: TextStyle(color: disabledTextColor(context, enabled)),
- ))
- ],
- )),
- onTap: enabled
- ? () {
- onChanged(!unlockPin.isNotEmpty);
- }
- : null,
- ).marginOnly(left: _kCheckBoxLeftMargin);
- }
- }
- class _Network extends StatefulWidget {
- const _Network({Key? key}) : super(key: key);
- @override
- State<_Network> createState() => _NetworkState();
- }
- class _NetworkState extends State<_Network> with AutomaticKeepAliveClientMixin {
- @override
- bool get wantKeepAlive => true;
- bool locked = !isWeb && bind.mainIsInstalled();
- @override
- Widget build(BuildContext context) {
- super.build(context);
- bool enabled = !locked;
- final scrollController = ScrollController();
- final hideServer =
- bind.mainGetBuildinOption(key: kOptionHideServerSetting) == 'Y';
- // TODO: support web proxy
- final hideProxy =
- isWeb || bind.mainGetBuildinOption(key: kOptionHideProxySetting) == 'Y';
- return DesktopScrollWrapper(
- scrollController: scrollController,
- child: ListView(
- controller: scrollController,
- physics: DraggableNeverScrollableScrollPhysics(),
- children: [
- _lock(locked, 'Unlock Network Settings', () {
- locked = false;
- setState(() => {});
- }),
- AbsorbPointer(
- absorbing: locked,
- child: Column(children: [
- if (!hideServer) server(enabled),
- if (!hideProxy)
- _Card(title: 'Proxy', children: [
- _Button('Socks5/Http(s) Proxy', changeSocks5Proxy,
- enabled: enabled),
- ]),
- ]),
- ),
- ]).marginOnly(bottom: _kListViewBottomMargin));
- }
- server(bool enabled) {
- // Simple temp wrapper for PR check
- tmpWrapper() {
- // Setting page is not modal, oldOptions should only be used when getting options, never when setting.
- Map<String, dynamic> oldOptions = jsonDecode(bind.mainGetOptionsSync());
- old(String key) {
- return (oldOptions[key] ?? '').trim();
- }
- RxString idErrMsg = ''.obs;
- RxString relayErrMsg = ''.obs;
- RxString apiErrMsg = ''.obs;
- var idController =
- TextEditingController(text: old('custom-rendezvous-server'));
- var relayController = TextEditingController(text: old('relay-server'));
- var apiController = TextEditingController(text: old('api-server'));
- var keyController = TextEditingController(text: old('key'));
- final controllers = [
- idController,
- relayController,
- apiController,
- keyController,
- ];
- final errMsgs = [
- idErrMsg,
- relayErrMsg,
- apiErrMsg,
- ];
- submit() async {
- bool result = await setServerConfig(
- null,
- errMsgs,
- ServerConfig(
- idServer: idController.text,
- relayServer: relayController.text,
- apiServer: apiController.text,
- key: keyController.text));
- if (result) {
- setState(() {});
- showToast(translate('Successful'));
- } else {
- showToast(translate('Failed'));
- }
- }
- bool secure = !enabled;
- return _Card(
- title: 'ID/Relay Server',
- title_suffix: ServerConfigImportExportWidgets(controllers, errMsgs),
- children: [
- Column(
- children: [
- Obx(() => _LabeledTextField(context, 'ID Server', idController,
- idErrMsg.value, enabled, secure)),
- if (!isWeb)
- Obx(() => _LabeledTextField(context, 'Relay Server',
- relayController, relayErrMsg.value, enabled, secure)),
- Obx(() => _LabeledTextField(context, 'API Server',
- apiController, apiErrMsg.value, enabled, secure)),
- _LabeledTextField(
- context, 'Key', keyController, '', enabled, secure),
- Row(
- mainAxisAlignment: MainAxisAlignment.end,
- children: [_Button('Apply', submit, enabled: enabled)],
- ).marginOnly(top: 10),
- ],
- )
- ]);
- }
- return tmpWrapper();
- }
- }
- 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,
- physics: DraggableNeverScrollableScrollPhysics(),
- children: [
- viewStyle(context),
- scrollStyle(context),
- imageQuality(context),
- codec(context),
- if (!isWeb) privacyModeImpl(context),
- other(context),
- ]).marginOnly(bottom: _kListViewBottomMargin));
- }
- Widget viewStyle(BuildContext context) {
- final isOptFixed = isOptionFixed(kOptionViewStyle);
- onChanged(String value) async {
- await bind.mainSetUserDefaultOption(key: kOptionViewStyle, value: value);
- setState(() {});
- }
- final groupValue = bind.mainGetUserDefaultOption(key: kOptionViewStyle);
- return _Card(title: 'Default View Style', children: [
- _Radio(context,
- value: kRemoteViewStyleOriginal,
- groupValue: groupValue,
- label: 'Scale original',
- onChanged: isOptFixed ? null : onChanged),
- _Radio(context,
- value: kRemoteViewStyleAdaptive,
- groupValue: groupValue,
- label: 'Scale adaptive',
- onChanged: isOptFixed ? null : onChanged),
- ]);
- }
- Widget scrollStyle(BuildContext context) {
- final isOptFixed = isOptionFixed(kOptionScrollStyle);
- onChanged(String value) async {
- await bind.mainSetUserDefaultOption(
- key: kOptionScrollStyle, value: value);
- setState(() {});
- }
- final groupValue = bind.mainGetUserDefaultOption(key: kOptionScrollStyle);
- return _Card(title: 'Default Scroll Style', children: [
- _Radio(context,
- value: kRemoteScrollStyleAuto,
- groupValue: groupValue,
- label: 'ScrollAuto',
- onChanged: isOptFixed ? null : onChanged),
- _Radio(context,
- value: kRemoteScrollStyleBar,
- groupValue: groupValue,
- label: 'Scrollbar',
- onChanged: isOptFixed ? null : onChanged),
- ]);
- }
- Widget imageQuality(BuildContext context) {
- onChanged(String value) async {
- await bind.mainSetUserDefaultOption(
- key: kOptionImageQuality, value: value);
- setState(() {});
- }
- final isOptFixed = isOptionFixed(kOptionImageQuality);
- final groupValue = bind.mainGetUserDefaultOption(key: kOptionImageQuality);
- return _Card(title: 'Default Image Quality', children: [
- _Radio(context,
- value: kRemoteImageQualityBest,
- groupValue: groupValue,
- label: 'Good image quality',
- onChanged: isOptFixed ? null : onChanged),
- _Radio(context,
- value: kRemoteImageQualityBalanced,
- groupValue: groupValue,
- label: 'Balanced',
- onChanged: isOptFixed ? null : onChanged),
- _Radio(context,
- value: kRemoteImageQualityLow,
- groupValue: groupValue,
- label: 'Optimize reaction time',
- onChanged: isOptFixed ? null : onChanged),
- _Radio(context,
- value: kRemoteImageQualityCustom,
- groupValue: groupValue,
- label: 'Custom',
- onChanged: isOptFixed ? null : onChanged),
- Offstage(
- offstage: groupValue != kRemoteImageQualityCustom,
- child: customImageQualitySetting(),
- )
- ]);
- }
- Widget codec(BuildContext context) {
- onChanged(String value) async {
- await bind.mainSetUserDefaultOption(
- key: kOptionCodecPreference, value: value);
- setState(() {});
- }
- final groupValue =
- bind.mainGetUserDefaultOption(key: kOptionCodecPreference);
- var hwRadios = [];
- final isOptFixed = isOptionFixed(kOptionCodecPreference);
- 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: isOptFixed ? null : onChanged));
- }
- if (h265) {
- hwRadios.add(_Radio(context,
- value: 'h265',
- groupValue: groupValue,
- label: 'H265',
- onChanged: isOptFixed ? null : onChanged));
- }
- } catch (e) {
- debugPrint("failed to parse supported hwdecodings, err=$e");
- }
- return _Card(title: 'Default Codec', children: [
- _Radio(context,
- value: 'auto',
- groupValue: groupValue,
- label: 'Auto',
- onChanged: isOptFixed ? null : onChanged),
- _Radio(context,
- value: 'vp8',
- groupValue: groupValue,
- label: 'VP8',
- onChanged: isOptFixed ? null : onChanged),
- _Radio(context,
- value: 'vp9',
- groupValue: groupValue,
- label: 'VP9',
- onChanged: isOptFixed ? null : onChanged),
- _Radio(context,
- value: 'av1',
- groupValue: groupValue,
- label: 'AV1',
- onChanged: isOptFixed ? null : onChanged),
- ...hwRadios,
- ]);
- }
- 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(),
- );
- }
- Widget otherRow(String label, String key) {
- final value = bind.mainGetUserDefaultOption(key: key) == 'Y';
- final isOptFixed = isOptionFixed(key);
- onChanged(bool b) async {
- await bind.mainSetUserDefaultOption(
- key: key,
- value: b
- ? 'Y'
- : (key == kOptionEnableFileCopyPaste ? 'N' : defaultOptionNo));
- setState(() {});
- }
- return GestureDetector(
- child: Row(
- children: [
- Checkbox(
- value: value,
- onChanged: isOptFixed ? null : (_) => onChanged(!value))
- .marginOnly(right: 5),
- Expanded(
- child: Text(translate(label)),
- )
- ],
- ).marginOnly(left: _kCheckBoxLeftMargin),
- onTap: isOptFixed ? null : () => onChanged(!value));
- }
- Widget other(BuildContext context) {
- final children =
- otherDefaultSettings().map((e) => otherRow(e.$1, e.$2)).toList();
- return _Card(title: 'Other Default Options', children: children);
- }
- }
- class _Account extends StatefulWidget {
- const _Account({Key? key}) : super(key: key);
- @override
- State<_Account> createState() => _AccountState();
- }
- class _AccountState extends State<_Account> {
- @override
- Widget build(BuildContext context) {
- final scrollController = ScrollController();
- return DesktopScrollWrapper(
- scrollController: scrollController,
- child: ListView(
- physics: DraggableNeverScrollableScrollPhysics(),
- controller: scrollController,
- children: [
- _Card(title: 'Account', children: [accountAction(), useInfo()]),
- ],
- ).marginOnly(bottom: _kListViewBottomMargin));
- }
- Widget accountAction() {
- return Obx(() => _Button(
- gFFI.userModel.userName.value.isEmpty ? 'Login' : 'Logout',
- () => {
- gFFI.userModel.userName.value.isEmpty
- ? loginDialog()
- : logOutConfirmDialog()
- }));
- }
- 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),
- // text('Group', gFFI.groupModel.groupName.value),
- ],
- ),
- )).marginOnly(left: 18, top: 16);
- }
- }
- 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);
- @override
- State<_Checkbox> createState() => _CheckboxState();
- }
- class _CheckboxState extends State<_Checkbox> {
- var value = false;
- @override
- initState() {
- super.initState();
- value = widget.getValue();
- }
- @override
- Widget build(BuildContext context) {
- onChanged(bool b) async {
- await widget.setValue(b);
- setState(() {
- value = widget.getValue();
- });
- }
- return GestureDetector(
- child: Row(
- children: [
- Checkbox(
- value: value,
- onChanged: (_) => onChanged(!value),
- ).marginOnly(right: 5),
- Expanded(
- child: Text(translate(widget.label)),
- )
- ],
- ).marginOnly(left: _kCheckBoxLeftMargin),
- onTap: () => onChanged(!value),
- );
- }
- }
- 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) {
- bind.pluginListReload();
- final scrollController = ScrollController();
- return DesktopScrollWrapper(
- scrollController: scrollController,
- child: ChangeNotifierProvider.value(
- value: pluginManager,
- child: Consumer<PluginManager>(builder: (context, model, child) {
- return ListView(
- physics: DraggableNeverScrollableScrollPhysics(),
- controller: scrollController,
- children: model.plugins.map((entry) => pluginCard(entry)).toList(),
- ).marginOnly(bottom: _kListViewBottomMargin);
- }),
- ),
- );
- }
- Widget pluginCard(PluginInfo plugin) {
- return ChangeNotifierProvider.value(
- value: plugin,
- child: Consumer<PluginInfo>(
- builder: (context, model, child) => DesktopSettingsCard(plugin: model),
- ),
- );
- }
- Widget accountAction() {
- return Obx(() => _Button(
- gFFI.userModel.userName.value.isEmpty ? 'Login' : 'Logout',
- () => {
- gFFI.userModel.userName.value.isEmpty
- ? loginDialog()
- : logOutConfirmDialog()
- }));
- }
- }
- 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) {
- return futureBuilder(future: () async {
- final license = await bind.mainGetLicense();
- final version = await bind.mainGetVersion();
- final buildDate = await bind.mainGetBuildDate();
- final fingerprint = await bind.mainGetFingerprint();
- return {
- 'license': license,
- 'version': version,
- 'buildDate': buildDate,
- 'fingerprint': fingerprint
- };
- }(), hasData: (data) {
- final license = data['license'].toString();
- final version = data['version'].toString();
- final buildDate = data['buildDate'].toString();
- final fingerprint = data['fingerprint'].toString();
- const linkStyle = TextStyle(decoration: TextDecoration.underline);
- final scrollController = ScrollController();
- return DesktopScrollWrapper(
- scrollController: scrollController,
- child: SingleChildScrollView(
- controller: scrollController,
- physics: DraggableNeverScrollableScrollPhysics(),
- child: _Card(title: translate('About RustDesk'), children: [
- Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- const SizedBox(
- height: 8.0,
- ),
- SelectionArea(
- child: Text('${translate('Version')}: $version')
- .marginSymmetric(vertical: 4.0)),
- SelectionArea(
- child: Text('${translate('Build Date')}: $buildDate')
- .marginSymmetric(vertical: 4.0)),
- if (!isWeb)
- SelectionArea(
- child: Text('${translate('Fingerprint')}: $fingerprint')
- .marginSymmetric(vertical: 4.0)),
- InkWell(
- onTap: () {
- launchUrlString('https://rustdesk.com/privacy.html');
- },
- child: Text(
- translate('Privacy Statement'),
- style: linkStyle,
- ).marginSymmetric(vertical: 4.0)),
- InkWell(
- onTap: () {
- launchUrlString('https://rustdesk.com');
- },
- child: Text(
- translate('Website'),
- style: linkStyle,
- ).marginSymmetric(vertical: 4.0)),
- Container(
- decoration: const BoxDecoration(color: Color(0xFF2c8cff)),
- padding:
- const EdgeInsets.symmetric(vertical: 24, horizontal: 8),
- child: SelectionArea(
- child: Row(
- children: [
- Expanded(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(
- 'Copyright © ${DateTime.now().toString().substring(0, 4)} Purslane Ltd.\n$license',
- style: const TextStyle(color: Colors.white),
- ),
- Text(
- translate('Slogan_tip'),
- style: TextStyle(
- fontWeight: FontWeight.w800,
- color: Colors.white),
- )
- ],
- ),
- ),
- ],
- )),
- ).marginSymmetric(vertical: 4.0)
- ],
- ).marginOnly(left: _kContentHMargin)
- ]),
- ));
- });
- }
- }
- //#endregion
- //#region components
- // ignore: non_constant_identifier_names
- Widget _Card(
- {required String title,
- required List<Widget> children,
- List<Widget>? title_suffix}) {
- return Row(
- children: [
- 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),
- ...children
- .map((e) => e.marginOnly(top: 4, right: _kContentHMargin)),
- ],
- ).marginOnly(bottom: 10),
- ).marginOnly(left: _kCardLeftMargin, top: 15),
- ),
- ),
- ],
- );
- }
- // ignore: non_constant_identifier_names
- Widget _OptionCheckBox(
- BuildContext context,
- String label,
- String key, {
- Function(bool)? update,
- bool reverse = false,
- bool enabled = true,
- Icon? checkedIcon,
- bool? fakeValue,
- bool isServer = true,
- bool Function()? optGetter,
- Future<void> Function(String, bool)? optSetter,
- }) {
- getOpt() => optGetter != null
- ? optGetter()
- : (isServer
- ? mainGetBoolOptionSync(key)
- : mainGetLocalBoolOptionSync(key));
- bool value = getOpt();
- final isOptFixed = isOptionFixed(key);
- if (reverse) value = !value;
- var ref = value.obs;
- onChanged(option) async {
- if (option != null) {
- if (reverse) option = !option;
- final setter =
- optSetter ?? (isServer ? mainSetBoolOption : mainSetLocalBoolOption);
- await setter(key, option);
- final readOption = getOpt();
- if (reverse) {
- ref.value = !readOption;
- } else {
- ref.value = readOption;
- }
- update?.call(readOption);
- }
- }
- if (fakeValue != null) {
- ref.value = fakeValue;
- enabled = false;
- }
- return GestureDetector(
- child: Obx(
- () => Row(
- children: [
- Checkbox(
- value: ref.value,
- onChanged: enabled && !isOptFixed ? onChanged : null)
- .marginOnly(right: 5),
- Offstage(
- offstage: !ref.value || checkedIcon == null,
- child: checkedIcon?.marginOnly(right: 5),
- ),
- Expanded(
- child: Text(
- translate(label),
- style: TextStyle(color: disabledTextColor(context, enabled)),
- ))
- ],
- ),
- ).marginOnly(left: _kCheckBoxLeftMargin),
- onTap: enabled && !isOptFixed
- ? () {
- onChanged(!ref.value);
- }
- : null,
- );
- }
- // ignore: non_constant_identifier_names
- Widget _Radio<T>(BuildContext context,
- {required T value,
- required T groupValue,
- required String label,
- required Function(T value)? onChanged,
- bool autoNewLine = true}) {
- final onChange2 = onChanged != null
- ? (T? value) {
- if (value != null) {
- onChanged(value);
- }
- }
- : null;
- return GestureDetector(
- child: Row(
- children: [
- Radio<T>(value: value, groupValue: groupValue, onChanged: onChange2),
- Expanded(
- child: Text(translate(label),
- overflow: autoNewLine ? null : TextOverflow.ellipsis,
- style: TextStyle(
- fontSize: _kContentFontSize,
- color: disabledTextColor(context, onChange2 != null)))
- .marginOnly(left: 5),
- ),
- ],
- ).marginOnly(left: _kRadioLeftMargin),
- onTap: () => onChange2?.call(value),
- );
- }
- class WaylandCard extends StatefulWidget {
- const WaylandCard({Key? key}) : super(key: key);
- @override
- State<WaylandCard> createState() => _WaylandCardState();
- }
- class _WaylandCardState extends State<WaylandCard> {
- final restoreTokenKey = 'wayland-restore-token';
- @override
- Widget build(BuildContext context) {
- return futureBuilder(
- future: bind.mainHandleWaylandScreencastRestoreToken(
- key: restoreTokenKey, value: "get"),
- hasData: (restoreToken) {
- final children = [
- if (restoreToken.isNotEmpty)
- _buildClearScreenSelection(context, restoreToken),
- ];
- return Offstage(
- offstage: children.isEmpty,
- child: _Card(title: 'Wayland', children: children),
- );
- },
- );
- }
- Widget _buildClearScreenSelection(BuildContext context, String restoreToken) {
- onConfirm() async {
- final msg = await bind.mainHandleWaylandScreencastRestoreToken(
- key: restoreTokenKey, value: "clear");
- gFFI.dialogManager.dismissAll();
- if (msg.isNotEmpty) {
- msgBox(gFFI.sessionId, 'custom-nocancel', 'Error', msg, '',
- gFFI.dialogManager);
- } else {
- setState(() {});
- }
- }
- showConfirmMsgBox() => msgBoxCommon(
- gFFI.dialogManager,
- 'Confirmation',
- Text(
- translate('confirm_clear_Wayland_screen_selection_tip'),
- ),
- [
- dialogButton('OK', onPressed: onConfirm),
- dialogButton('Cancel',
- onPressed: () => gFFI.dialogManager.dismissAll())
- ]);
- return _Button(
- 'Clear Wayland screen selection',
- showConfirmMsgBox,
- tip: 'clear_Wayland_screen_selection_tip',
- style: ButtonStyle(
- backgroundColor: MaterialStateProperty.all<Color>(
- Theme.of(context).colorScheme.error.withOpacity(0.75)),
- ),
- );
- }
- }
- // ignore: non_constant_identifier_names
- Widget _Button(String label, Function() onPressed,
- {bool enabled = true, String? tip, ButtonStyle? style}) {
- var button = ElevatedButton(
- onPressed: enabled ? onPressed : null,
- child: Text(
- translate(label),
- ).marginSymmetric(horizontal: 15),
- style: style,
- );
- StatefulWidget child;
- if (tip == null) {
- child = button;
- } else {
- child = Tooltip(message: translate(tip), child: button);
- }
- return Row(children: [
- child,
- ]).marginOnly(left: _kContentHMargin);
- }
- // ignore: non_constant_identifier_names
- Widget _SubButton(String label, Function() onPressed, [bool enabled = true]) {
- return Row(
- children: [
- ElevatedButton(
- onPressed: enabled ? onPressed : null,
- child: Text(
- translate(label),
- ).marginSymmetric(horizontal: 15),
- ),
- ],
- ).marginOnly(left: _kContentHSubMargin);
- }
- // ignore: non_constant_identifier_names
- Widget _SubLabeledWidget(BuildContext context, String label, Widget child,
- {bool enabled = true}) {
- return Row(
- children: [
- Text(
- '${translate(label)}: ',
- style: TextStyle(color: disabledTextColor(context, enabled)),
- ),
- SizedBox(
- width: 10,
- ),
- child,
- ],
- ).marginOnly(left: _kContentHSubMargin);
- }
- Widget _lock(
- bool locked,
- String label,
- Function() onUnlock,
- ) {
- return Offstage(
- offstage: !locked,
- child: Row(
- children: [
- 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 {
- final unlockPin = bind.mainGetUnlockPin();
- if (unlockPin.isEmpty) {
- bool checked = await callMainCheckSuperUserPermission();
- if (checked) {
- onUnlock();
- }
- } else {
- checkUnlockPinDialog(unlockPin, onUnlock);
- }
- },
- ).marginSymmetric(horizontal: 2, vertical: 4),
- ).marginOnly(left: _kCardLeftMargin),
- ).marginOnly(top: 10),
- ),
- ],
- ));
- }
- _LabeledTextField(
- BuildContext context,
- String label,
- TextEditingController controller,
- String errorText,
- bool enabled,
- bool secure) {
- return Row(
- children: [
- ConstrainedBox(
- constraints: const BoxConstraints(minWidth: 140),
- child: Text(
- '${translate(label)}:',
- textAlign: TextAlign.right,
- style: TextStyle(
- fontSize: 16, color: disabledTextColor(context, enabled)),
- ).marginOnly(right: 10)),
- Expanded(
- child: TextField(
- controller: controller,
- enabled: enabled,
- obscureText: secure,
- decoration: InputDecoration(
- errorText: errorText.isNotEmpty ? errorText : null),
- style: TextStyle(
- color: disabledTextColor(context, enabled),
- )),
- ),
- ],
- ).marginOnly(bottom: 8);
- }
- 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),
- ),
- );
- }
- }
- //#endregion
- //#region dialogs
- void changeSocks5Proxy() async {
- var socks = await bind.mainGetSocks();
- String proxy = '';
- String proxyMsg = '';
- String username = '';
- String password = '';
- if (socks.length == 3) {
- proxy = socks[0];
- username = socks[1];
- password = socks[2];
- }
- var proxyController = TextEditingController(text: proxy);
- var userController = TextEditingController(text: username);
- var pwdController = TextEditingController(text: password);
- RxBool obscure = true.obs;
- // proxy settings
- // The following option is a not real key, it is just used for custom client advanced settings.
- const String optionProxyUrl = "proxy-url";
- final isOptFixed = isOptionFixed(optionProxyUrl);
- var isInProgress = false;
- gFFI.dialogManager.show((setState, close, context) {
- submit() async {
- setState(() {
- proxyMsg = '';
- isInProgress = true;
- });
- cancel() {
- setState(() {
- isInProgress = false;
- });
- }
- proxy = proxyController.text.trim();
- username = userController.text.trim();
- password = pwdController.text.trim();
- if (proxy.isNotEmpty) {
- String domainPort = proxy;
- if (domainPort.contains('://')) {
- domainPort = domainPort.split('://')[1];
- }
- proxyMsg = translate(await bind.mainTestIfValidServer(
- server: domainPort, testWithProxy: false));
- if (proxyMsg.isEmpty) {
- // ignore
- } else {
- cancel();
- return;
- }
- }
- await bind.mainSetSocks(
- proxy: proxy, username: username, password: password);
- close();
- }
- return CustomAlertDialog(
- title: Text(translate('Socks5/Http(s) Proxy')),
- content: ConstrainedBox(
- constraints: const BoxConstraints(minWidth: 500),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Row(
- children: [
- if (!isMobile)
- ConstrainedBox(
- constraints: const BoxConstraints(minWidth: 140),
- child: Align(
- alignment: Alignment.centerRight,
- child: Row(
- children: [
- Text(
- translate('Server'),
- ).marginOnly(right: 4),
- Tooltip(
- waitDuration: Duration(milliseconds: 0),
- message: translate("default_proxy_tip"),
- child: Icon(
- Icons.help_outline_outlined,
- size: 16,
- color: Theme.of(context)
- .textTheme
- .titleLarge
- ?.color
- ?.withOpacity(0.5),
- ),
- ),
- ],
- )).marginOnly(right: 10),
- ),
- Expanded(
- child: TextField(
- decoration: InputDecoration(
- errorText: proxyMsg.isNotEmpty ? proxyMsg : null,
- labelText: isMobile ? translate('Server') : null,
- helperText:
- isMobile ? translate("default_proxy_tip") : null,
- helperMaxLines: isMobile ? 3 : null,
- ),
- controller: proxyController,
- autofocus: true,
- enabled: !isOptFixed,
- ),
- ),
- ],
- ).marginOnly(bottom: 8),
- Row(
- children: [
- if (!isMobile)
- ConstrainedBox(
- constraints: const BoxConstraints(minWidth: 140),
- child: Text(
- '${translate("Username")}:',
- textAlign: TextAlign.right,
- ).marginOnly(right: 10)),
- Expanded(
- child: TextField(
- controller: userController,
- decoration: InputDecoration(
- labelText: isMobile ? translate('Username') : null,
- ),
- enabled: !isOptFixed,
- ),
- ),
- ],
- ).marginOnly(bottom: 8),
- Row(
- children: [
- if (!isMobile)
- ConstrainedBox(
- constraints: const BoxConstraints(minWidth: 140),
- child: Text(
- '${translate("Password")}:',
- textAlign: TextAlign.right,
- ).marginOnly(right: 10)),
- Expanded(
- child: Obx(() => TextField(
- obscureText: obscure.value,
- decoration: InputDecoration(
- labelText: isMobile ? translate('Password') : null,
- suffixIcon: IconButton(
- onPressed: () => obscure.value = !obscure.value,
- icon: Icon(obscure.value
- ? Icons.visibility_off
- : Icons.visibility))),
- controller: pwdController,
- enabled: !isOptFixed,
- maxLength: bind.mainMaxEncryptLen(),
- )),
- ),
- ],
- ),
- // NOT use Offstage to wrap LinearProgressIndicator
- if (isInProgress)
- const LinearProgressIndicator().marginOnly(top: 8),
- ],
- ),
- ),
- actions: [
- dialogButton('Cancel', onPressed: close, isOutline: true),
- if (!isOptFixed) dialogButton('OK', onPressed: submit),
- ],
- onSubmit: submit,
- onCancel: close,
- );
- });
- }
- //#endregion
|