1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560 |
- 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/mobile/widgets/dialog.dart';
- import 'package:flutter_hbb/models/platform_model.dart';
- import 'package:flutter_hbb/models/server_model.dart';
- import 'package:flutter_hbb/models/state_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 '../../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,
- WidgetsBindingObserver {
- late PageController controller;
- late Rx<SettingsTabKey> selectedTab;
- @override
- bool get wantKeepAlive => true;
- final RxBool _block = false.obs;
- final RxBool _canBeBlocked = false.obs;
- Timer? _videoConnTimer;
- _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 didChangeAppLifecycleState(AppLifecycleState state) {
- super.didChangeAppLifecycleState(state);
- if (state == AppLifecycleState.resumed) {
- shouldBeBlocked(_block, canBeBlocked);
- }
- }
- @override
- void initState() {
- super.initState();
- WidgetsBinding.instance.addObserver(this);
- _videoConnTimer =
- periodic_immediate(Duration(milliseconds: 1000), () async {
- if (!mounted) {
- return;
- }
- _canBeBlocked.value = await canBeBlocked();
- });
- }
- @override
- void dispose() {
- super.dispose();
- Get.delete<PageController>(tag: _kSettingPageControllerTag);
- Get.delete<RxInt>(tag: _kSettingPageTabKeyTag);
- WidgetsBinding.instance.removeObserver(this);
- _videoConnTimer?.cancel();
- }
- 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;
- }
- Widget _buildBlock({required List<Widget> children}) {
- // check both mouseMoveTime and videoConnCount
- return Obx(() {
- final videoConnBlock =
- _canBeBlocked.value && stateGlobal.videoConnCount > 0;
- return Stack(children: [
- buildRemoteBlock(
- block: _block,
- mask: false,
- use: canBeBlocked,
- child: preventMouseKeyBuilder(
- child: Row(children: children),
- block: videoConnBlock,
- ),
- ),
- if (videoConnBlock)
- Container(
- color: Colors.black.withOpacity(0.5),
- )
- ]);
- });
- }
- @override
- Widget build(BuildContext context) {
- super.build(context);
- return Scaffold(
- backgroundColor: Theme.of(context).colorScheme.background,
- body: _buildBlock(
- 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: 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 ListView(
- 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 ListView(
- 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;
- 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 SingleChildScrollView(
- controller: scrollController,
- child: Column(
- children: [
- _lock(locked, 'Unlock Security Settings', () {
- locked = false;
- setState(() => {});
- }),
- preventMouseKeyBuilder(
- block: 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),
- ),
- ).workaroundFreezeLinuxMint().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),
- ),
- ).workaroundFreezeLinuxMint().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();
- final scrollController = ScrollController();
- @override
- Widget build(BuildContext context) {
- super.build(context);
- return ListView(controller: scrollController, children: [
- _lock(locked, 'Unlock Network Settings', () {
- locked = false;
- setState(() => {});
- }),
- preventMouseKeyBuilder(
- block: locked,
- child: Column(children: [
- network(context),
- ]),
- ),
- ]).marginOnly(bottom: _kListViewBottomMargin);
- }
- Widget network(BuildContext context) {
- final hideServer =
- bind.mainGetBuildinOption(key: kOptionHideServerSetting) == 'Y';
- final hideProxy =
- isWeb || bind.mainGetBuildinOption(key: kOptionHideProxySetting) == 'Y';
- if (hideServer && hideProxy) {
- return Offstage();
- }
- return _Card(
- title: 'Network',
- children: [
- Container(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- if (!hideServer)
- ListTile(
- leading: Icon(Icons.dns_outlined, color: _accentColor),
- title: Text(
- translate('ID/Relay Server'),
- style: TextStyle(fontSize: _kContentFontSize),
- ),
- enabled: !locked,
- onTap: () => showServerSettings(gFFI.dialogManager),
- shape: RoundedRectangleBorder(
- borderRadius: BorderRadius.circular(10),
- ),
- contentPadding: EdgeInsets.symmetric(horizontal: 16),
- minLeadingWidth: 0,
- horizontalTitleGap: 10,
- ),
- if (!hideServer && !hideProxy)
- Divider(height: 1, indent: 16, endIndent: 16),
- if (!hideProxy)
- ListTile(
- leading:
- Icon(Icons.network_ping_outlined, color: _accentColor),
- title: Text(
- translate('Socks5/Http(s) Proxy'),
- style: TextStyle(fontSize: _kContentFontSize),
- ),
- enabled: !locked,
- onTap: changeSocks5Proxy,
- shape: RoundedRectangleBorder(
- borderRadius: BorderRadius.circular(10),
- ),
- contentPadding: EdgeInsets.symmetric(horizontal: 16),
- minLeadingWidth: 0,
- horizontalTitleGap: 10,
- ),
- ],
- ),
- ),
- ],
- );
- }
- }
- 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 ListView(controller: scrollController, 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 ListView(
- 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 ChangeNotifierProvider.value(
- value: pluginManager,
- child: Consumer<PluginManager>(builder: (context, model, child) {
- return ListView(
- 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 SingleChildScrollView(
- controller: scrollController,
- 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 Table(
- columnWidths: const {
- 0: FixedColumnWidth(150),
- 1: FlexColumnWidth(),
- },
- defaultVerticalAlignment: TableCellVerticalAlignment.middle,
- children: [
- TableRow(
- children: [
- Padding(
- padding: const EdgeInsets.only(right: 10),
- child: Text(
- '${translate(label)}:',
- textAlign: TextAlign.right,
- style: TextStyle(
- fontSize: 16,
- color: disabledTextColor(context, enabled),
- ),
- ),
- ),
- TextField(
- controller: controller,
- enabled: enabled,
- obscureText: secure,
- autocorrect: false,
- decoration: InputDecoration(
- errorText: errorText.isNotEmpty ? errorText : null,
- ),
- style: TextStyle(
- color: disabledTextColor(context, enabled),
- ),
- ).workaroundFreezeLinuxMint(),
- ],
- ),
- ],
- ).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,
- ).workaroundFreezeLinuxMint(),
- ),
- ],
- ).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,
- ).workaroundFreezeLinuxMint(),
- ),
- ],
- ).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(),
- ).workaroundFreezeLinuxMint()),
- ),
- ],
- ),
- // 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
|