12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190 |
- import 'dart:async';
- import 'dart:convert';
- import 'dart:typed_data';
- import 'package:flutter/material.dart';
- import 'package:flutter_hbb/common/widgets/setting_widgets.dart';
- import 'package:flutter_hbb/desktop/pages/desktop_setting_page.dart';
- import 'package:flutter_hbb/models/state_model.dart';
- import 'package:get/get.dart';
- import 'package:provider/provider.dart';
- import 'package:settings_ui/settings_ui.dart';
- import 'package:url_launcher/url_launcher.dart';
- import 'package:url_launcher/url_launcher_string.dart';
- import '../../common.dart';
- import '../../common/widgets/dialog.dart';
- import '../../common/widgets/login.dart';
- import '../../consts.dart';
- import '../../models/model.dart';
- import '../../models/platform_model.dart';
- import '../widgets/dialog.dart';
- import 'home_page.dart';
- import 'scan_page.dart';
- class SettingsPage extends StatefulWidget implements PageShape {
- @override
- final title = translate("Settings");
- @override
- final icon = Icon(Icons.settings);
- @override
- final appBarActions = bind.isDisableSettings() ? [] : [ScanButton()];
- @override
- State<SettingsPage> createState() => _SettingsState();
- }
- const url = 'https://rustdesk.com/';
- enum KeepScreenOn {
- never,
- duringControlled,
- serviceOn,
- }
- String _keepScreenOnToOption(KeepScreenOn value) {
- switch (value) {
- case KeepScreenOn.never:
- return 'never';
- case KeepScreenOn.duringControlled:
- return 'during-controlled';
- case KeepScreenOn.serviceOn:
- return 'service-on';
- }
- }
- KeepScreenOn optionToKeepScreenOn(String value) {
- switch (value) {
- case 'never':
- return KeepScreenOn.never;
- case 'service-on':
- return KeepScreenOn.serviceOn;
- default:
- return KeepScreenOn.duringControlled;
- }
- }
- class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
- final _hasIgnoreBattery =
- false; //androidVersion >= 26; // remove because not work on every device
- var _ignoreBatteryOpt = false;
- var _enableStartOnBoot = false;
- var _checkUpdateOnStartup = false;
- var _floatingWindowDisabled = false;
- var _keepScreenOn = KeepScreenOn.duringControlled; // relay on floating window
- var _enableAbr = false;
- var _denyLANDiscovery = false;
- var _onlyWhiteList = false;
- var _enableDirectIPAccess = false;
- var _enableRecordSession = false;
- var _enableHardwareCodec = false;
- var _autoRecordIncomingSession = false;
- var _autoRecordOutgoingSession = false;
- var _allowAutoDisconnect = false;
- var _localIP = "";
- var _directAccessPort = "";
- var _fingerprint = "";
- var _buildDate = "";
- var _autoDisconnectTimeout = "";
- var _hideServer = false;
- var _hideProxy = false;
- var _hideNetwork = false;
- var _enableTrustedDevices = false;
- _SettingsState() {
- _enableAbr = option2bool(
- kOptionEnableAbr, bind.mainGetOptionSync(key: kOptionEnableAbr));
- _denyLANDiscovery = !option2bool(kOptionEnableLanDiscovery,
- bind.mainGetOptionSync(key: kOptionEnableLanDiscovery));
- _onlyWhiteList = whitelistNotEmpty();
- _enableDirectIPAccess = option2bool(
- kOptionDirectServer, bind.mainGetOptionSync(key: kOptionDirectServer));
- _enableRecordSession = option2bool(kOptionEnableRecordSession,
- bind.mainGetOptionSync(key: kOptionEnableRecordSession));
- _enableHardwareCodec = option2bool(kOptionEnableHwcodec,
- bind.mainGetOptionSync(key: kOptionEnableHwcodec));
- _autoRecordIncomingSession = option2bool(kOptionAllowAutoRecordIncoming,
- bind.mainGetOptionSync(key: kOptionAllowAutoRecordIncoming));
- _autoRecordOutgoingSession = option2bool(kOptionAllowAutoRecordOutgoing,
- bind.mainGetLocalOption(key: kOptionAllowAutoRecordOutgoing));
- _localIP = bind.mainGetOptionSync(key: 'local-ip-addr');
- _directAccessPort = bind.mainGetOptionSync(key: kOptionDirectAccessPort);
- _allowAutoDisconnect = option2bool(kOptionAllowAutoDisconnect,
- bind.mainGetOptionSync(key: kOptionAllowAutoDisconnect));
- _autoDisconnectTimeout =
- bind.mainGetOptionSync(key: kOptionAutoDisconnectTimeout);
- _hideServer =
- bind.mainGetBuildinOption(key: kOptionHideServerSetting) == 'Y';
- _hideProxy = bind.mainGetBuildinOption(key: kOptionHideProxySetting) == 'Y';
- _hideNetwork =
- bind.mainGetBuildinOption(key: kOptionHideNetworkSetting) == 'Y';
- _enableTrustedDevices = mainGetBoolOptionSync(kOptionEnableTrustedDevices);
- }
- @override
- void initState() {
- super.initState();
- WidgetsBinding.instance.addObserver(this);
- WidgetsBinding.instance.addPostFrameCallback((_) async {
- var update = false;
- if (_hasIgnoreBattery) {
- if (await checkAndUpdateIgnoreBatteryStatus()) {
- update = true;
- }
- }
- if (await checkAndUpdateStartOnBoot()) {
- update = true;
- }
- // start on boot depends on ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS and SYSTEM_ALERT_WINDOW
- var enableStartOnBoot =
- await gFFI.invokeMethod(AndroidChannel.kGetStartOnBootOpt);
- if (enableStartOnBoot) {
- if (!await canStartOnBoot()) {
- enableStartOnBoot = false;
- gFFI.invokeMethod(AndroidChannel.kSetStartOnBootOpt, false);
- }
- }
- if (enableStartOnBoot != _enableStartOnBoot) {
- update = true;
- _enableStartOnBoot = enableStartOnBoot;
- }
- var checkUpdateOnStartup =
- mainGetLocalBoolOptionSync(kOptionEnableCheckUpdate);
- if (checkUpdateOnStartup != _checkUpdateOnStartup) {
- update = true;
- _checkUpdateOnStartup = checkUpdateOnStartup;
- }
- var floatingWindowDisabled =
- bind.mainGetLocalOption(key: kOptionDisableFloatingWindow) == "Y" ||
- !await AndroidPermissionManager.check(kSystemAlertWindow);
- if (floatingWindowDisabled != _floatingWindowDisabled) {
- update = true;
- _floatingWindowDisabled = floatingWindowDisabled;
- }
- final keepScreenOn = _floatingWindowDisabled
- ? KeepScreenOn.never
- : optionToKeepScreenOn(
- bind.mainGetLocalOption(key: kOptionKeepScreenOn));
- if (keepScreenOn != _keepScreenOn) {
- update = true;
- _keepScreenOn = keepScreenOn;
- }
- final fingerprint = await bind.mainGetFingerprint();
- if (_fingerprint != fingerprint) {
- update = true;
- _fingerprint = fingerprint;
- }
- final buildDate = await bind.mainGetBuildDate();
- if (_buildDate != buildDate) {
- update = true;
- _buildDate = buildDate;
- }
- if (update) {
- setState(() {});
- }
- });
- }
- @override
- void dispose() {
- WidgetsBinding.instance.removeObserver(this);
- super.dispose();
- }
- @override
- void didChangeAppLifecycleState(AppLifecycleState state) {
- if (state == AppLifecycleState.resumed) {
- () async {
- final ibs = await checkAndUpdateIgnoreBatteryStatus();
- final sob = await checkAndUpdateStartOnBoot();
- if (ibs || sob) {
- setState(() {});
- }
- }();
- }
- }
- Future<bool> checkAndUpdateIgnoreBatteryStatus() async {
- final res = await AndroidPermissionManager.check(
- kRequestIgnoreBatteryOptimizations);
- if (_ignoreBatteryOpt != res) {
- _ignoreBatteryOpt = res;
- return true;
- } else {
- return false;
- }
- }
- Future<bool> checkAndUpdateStartOnBoot() async {
- if (!await canStartOnBoot() && _enableStartOnBoot) {
- _enableStartOnBoot = false;
- debugPrint(
- "checkAndUpdateStartOnBoot and set _enableStartOnBoot -> false");
- gFFI.invokeMethod(AndroidChannel.kSetStartOnBootOpt, false);
- return true;
- } else {
- return false;
- }
- }
- @override
- Widget build(BuildContext context) {
- Provider.of<FfiModel>(context);
- final outgoingOnly = bind.isOutgoingOnly();
- final incommingOnly = bind.isIncomingOnly();
- final customClientSection = CustomSettingsSection(
- child: Column(
- children: [
- if (bind.isCustomClient())
- Align(
- alignment: Alignment.center,
- child: loadPowered(context),
- ),
- Align(
- alignment: Alignment.center,
- child: loadLogo(),
- )
- ],
- ));
- final List<AbstractSettingsTile> enhancementsTiles = [];
- final enable2fa = bind.mainHasValid2FaSync();
- final List<AbstractSettingsTile> tfaTiles = [
- SettingsTile.switchTile(
- title: Text(translate('enable-2fa-title')),
- initialValue: enable2fa,
- onToggle: (v) async {
- update() async {
- setState(() {});
- }
- if (v == false) {
- CommonConfirmDialog(
- gFFI.dialogManager, translate('cancel-2fa-confirm-tip'), () {
- change2fa(callback: update);
- });
- } else {
- change2fa(callback: update);
- }
- },
- ),
- if (enable2fa)
- SettingsTile.switchTile(
- title: Text(translate('Telegram bot')),
- initialValue: bind.mainHasValidBotSync(),
- onToggle: (v) async {
- update() async {
- setState(() {});
- }
- if (v == false) {
- CommonConfirmDialog(
- gFFI.dialogManager, translate('cancel-bot-confirm-tip'), () {
- changeBot(callback: update);
- });
- } else {
- changeBot(callback: update);
- }
- },
- ),
- if (enable2fa)
- SettingsTile.switchTile(
- title: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(translate('Enable trusted devices')),
- Text('* ${translate('enable-trusted-devices-tip')}',
- style: Theme.of(context).textTheme.bodySmall),
- ],
- ),
- initialValue: _enableTrustedDevices,
- onToggle: isOptionFixed(kOptionEnableTrustedDevices)
- ? null
- : (v) async {
- mainSetBoolOption(kOptionEnableTrustedDevices, v);
- setState(() {
- _enableTrustedDevices = v;
- });
- },
- ),
- if (enable2fa && _enableTrustedDevices)
- SettingsTile(
- title: Text(translate('Manage trusted devices')),
- trailing: Icon(Icons.arrow_forward_ios),
- onPressed: (context) {
- Navigator.push(context, MaterialPageRoute(builder: (context) {
- return _ManageTrustedDevices();
- }));
- })
- ];
- final List<AbstractSettingsTile> shareScreenTiles = [
- SettingsTile.switchTile(
- title: Text(translate('Deny LAN discovery')),
- initialValue: _denyLANDiscovery,
- onToggle: isOptionFixed(kOptionEnableLanDiscovery)
- ? null
- : (v) async {
- await bind.mainSetOption(
- key: kOptionEnableLanDiscovery,
- value: bool2option(kOptionEnableLanDiscovery, !v));
- final newValue = !option2bool(kOptionEnableLanDiscovery,
- await bind.mainGetOption(key: kOptionEnableLanDiscovery));
- setState(() {
- _denyLANDiscovery = newValue;
- });
- },
- ),
- SettingsTile.switchTile(
- title: Row(children: [
- Expanded(child: Text(translate('Use IP Whitelisting'))),
- Offstage(
- offstage: !_onlyWhiteList,
- child: const Icon(Icons.warning_amber_rounded,
- color: Color.fromARGB(255, 255, 204, 0)))
- .marginOnly(left: 5)
- ]),
- initialValue: _onlyWhiteList,
- onToggle: (_) async {
- update() async {
- final onlyWhiteList = whitelistNotEmpty();
- if (onlyWhiteList != _onlyWhiteList) {
- setState(() {
- _onlyWhiteList = onlyWhiteList;
- });
- }
- }
- changeWhiteList(callback: update);
- },
- ),
- SettingsTile.switchTile(
- title: Text('${translate('Adaptive bitrate')} (beta)'),
- initialValue: _enableAbr,
- onToggle: isOptionFixed(kOptionEnableAbr)
- ? null
- : (v) async {
- await mainSetBoolOption(kOptionEnableAbr, v);
- final newValue = await mainGetBoolOption(kOptionEnableAbr);
- setState(() {
- _enableAbr = newValue;
- });
- },
- ),
- SettingsTile.switchTile(
- title: Text(translate('Enable recording session')),
- initialValue: _enableRecordSession,
- onToggle: isOptionFixed(kOptionEnableRecordSession)
- ? null
- : (v) async {
- await mainSetBoolOption(kOptionEnableRecordSession, v);
- final newValue =
- await mainGetBoolOption(kOptionEnableRecordSession);
- setState(() {
- _enableRecordSession = newValue;
- });
- },
- ),
- SettingsTile.switchTile(
- title: Row(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- crossAxisAlignment: CrossAxisAlignment.center,
- children: [
- Expanded(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(translate("Direct IP Access")),
- Offstage(
- offstage: !_enableDirectIPAccess,
- child: Text(
- '${translate("Local Address")}: $_localIP${_directAccessPort.isEmpty ? "" : ":$_directAccessPort"}',
- style: Theme.of(context).textTheme.bodySmall,
- )),
- ])),
- Offstage(
- offstage: !_enableDirectIPAccess,
- child: IconButton(
- padding: EdgeInsets.zero,
- icon: Icon(
- Icons.edit,
- size: 20,
- ),
- onPressed: isOptionFixed(kOptionDirectAccessPort)
- ? null
- : () async {
- final port = await changeDirectAccessPort(
- _localIP, _directAccessPort);
- setState(() {
- _directAccessPort = port;
- });
- }))
- ]),
- initialValue: _enableDirectIPAccess,
- onToggle: isOptionFixed(kOptionDirectServer)
- ? null
- : (_) async {
- _enableDirectIPAccess = !_enableDirectIPAccess;
- String value =
- bool2option(kOptionDirectServer, _enableDirectIPAccess);
- await bind.mainSetOption(
- key: kOptionDirectServer, value: value);
- setState(() {});
- },
- ),
- SettingsTile.switchTile(
- title: Row(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- crossAxisAlignment: CrossAxisAlignment.center,
- children: [
- Expanded(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(translate("auto_disconnect_option_tip")),
- Offstage(
- offstage: !_allowAutoDisconnect,
- child: Text(
- '${_autoDisconnectTimeout.isEmpty ? '10' : _autoDisconnectTimeout} min',
- style: Theme.of(context).textTheme.bodySmall,
- )),
- ])),
- Offstage(
- offstage: !_allowAutoDisconnect,
- child: IconButton(
- padding: EdgeInsets.zero,
- icon: Icon(
- Icons.edit,
- size: 20,
- ),
- onPressed: isOptionFixed(kOptionAutoDisconnectTimeout)
- ? null
- : () async {
- final timeout = await changeAutoDisconnectTimeout(
- _autoDisconnectTimeout);
- setState(() {
- _autoDisconnectTimeout = timeout;
- });
- }))
- ]),
- initialValue: _allowAutoDisconnect,
- onToggle: isOptionFixed(kOptionAllowAutoDisconnect)
- ? null
- : (_) async {
- _allowAutoDisconnect = !_allowAutoDisconnect;
- String value = bool2option(
- kOptionAllowAutoDisconnect, _allowAutoDisconnect);
- await bind.mainSetOption(
- key: kOptionAllowAutoDisconnect, value: value);
- setState(() {});
- },
- )
- ];
- if (_hasIgnoreBattery) {
- enhancementsTiles.insert(
- 0,
- SettingsTile.switchTile(
- initialValue: _ignoreBatteryOpt,
- title: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(translate('Keep RustDesk background service')),
- Text('* ${translate('Ignore Battery Optimizations')}',
- style: Theme.of(context).textTheme.bodySmall),
- ]),
- onToggle: (v) async {
- if (v) {
- await AndroidPermissionManager.request(
- kRequestIgnoreBatteryOptimizations);
- } else {
- final res = await gFFI.dialogManager.show<bool>(
- (setState, close, context) => CustomAlertDialog(
- title: Text(translate("Open System Setting")),
- content: Text(translate(
- "android_open_battery_optimizations_tip")),
- actions: [
- dialogButton("Cancel",
- onPressed: () => close(), isOutline: true),
- dialogButton(
- "Open System Setting",
- onPressed: () => close(true),
- ),
- ],
- ));
- if (res == true) {
- AndroidPermissionManager.startAction(
- kActionApplicationDetailsSettings);
- }
- }
- }));
- }
- enhancementsTiles.add(SettingsTile.switchTile(
- initialValue: _enableStartOnBoot,
- title: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
- Text("${translate('Start on boot')} (beta)"),
- Text(
- '* ${translate('Start the screen sharing service on boot, requires special permissions')}',
- style: Theme.of(context).textTheme.bodySmall),
- ]),
- onToggle: (toValue) async {
- if (toValue) {
- // 1. request kIgnoreBatteryOptimizations
- if (!await AndroidPermissionManager.check(
- kRequestIgnoreBatteryOptimizations)) {
- if (!await AndroidPermissionManager.request(
- kRequestIgnoreBatteryOptimizations)) {
- return;
- }
- }
- // 2. request kSystemAlertWindow
- if (!await AndroidPermissionManager.check(kSystemAlertWindow)) {
- if (!await AndroidPermissionManager.request(kSystemAlertWindow)) {
- return;
- }
- }
- // (Optional) 3. request input permission
- }
- setState(() => _enableStartOnBoot = toValue);
- gFFI.invokeMethod(AndroidChannel.kSetStartOnBootOpt, toValue);
- }));
- if (!bind.isCustomClient()) {
- enhancementsTiles.add(
- SettingsTile.switchTile(
- initialValue: _checkUpdateOnStartup,
- title:
- Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
- Text(translate('Check for software update on startup')),
- ]),
- onToggle: (bool toValue) async {
- await mainSetLocalBoolOption(kOptionEnableCheckUpdate, toValue);
- setState(() => _checkUpdateOnStartup = toValue);
- },
- ),
- );
- }
- onFloatingWindowChanged(bool toValue) async {
- if (toValue) {
- if (!await AndroidPermissionManager.check(kSystemAlertWindow)) {
- if (!await AndroidPermissionManager.request(kSystemAlertWindow)) {
- return;
- }
- }
- }
- final disable = !toValue;
- bind.mainSetLocalOption(
- key: kOptionDisableFloatingWindow,
- value: disable ? 'Y' : defaultOptionNo);
- setState(() => _floatingWindowDisabled = disable);
- gFFI.serverModel.androidUpdatekeepScreenOn();
- }
- enhancementsTiles.add(SettingsTile.switchTile(
- initialValue: !_floatingWindowDisabled,
- title: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
- Text(translate('Floating window')),
- Text('* ${translate('floating_window_tip')}',
- style: Theme.of(context).textTheme.bodySmall),
- ]),
- onToggle: bind.mainIsOptionFixed(key: kOptionDisableFloatingWindow)
- ? null
- : onFloatingWindowChanged));
- enhancementsTiles.add(_getPopupDialogRadioEntry(
- title: 'Keep screen on',
- list: [
- _RadioEntry('Never', _keepScreenOnToOption(KeepScreenOn.never)),
- _RadioEntry('During controlled',
- _keepScreenOnToOption(KeepScreenOn.duringControlled)),
- _RadioEntry('During service is on',
- _keepScreenOnToOption(KeepScreenOn.serviceOn)),
- ],
- getter: () => _keepScreenOnToOption(_floatingWindowDisabled
- ? KeepScreenOn.never
- : optionToKeepScreenOn(
- bind.mainGetLocalOption(key: kOptionKeepScreenOn))),
- asyncSetter: isOptionFixed(kOptionKeepScreenOn) || _floatingWindowDisabled
- ? null
- : (value) async {
- await bind.mainSetLocalOption(
- key: kOptionKeepScreenOn, value: value);
- setState(() => _keepScreenOn = optionToKeepScreenOn(value));
- gFFI.serverModel.androidUpdatekeepScreenOn();
- },
- ));
- final disabledSettings = bind.isDisableSettings();
- final hideSecuritySettings =
- bind.mainGetBuildinOption(key: kOptionHideSecuritySetting) == 'Y';
- final settings = SettingsList(
- sections: [
- customClientSection,
- if (!bind.isDisableAccount())
- SettingsSection(
- title: Text(translate('Account')),
- tiles: [
- SettingsTile(
- title: Obx(() => Text(gFFI.userModel.userName.value.isEmpty
- ? translate('Login')
- : '${translate('Logout')} (${gFFI.userModel.userName.value})')),
- leading: Icon(Icons.person),
- onPressed: (context) {
- if (gFFI.userModel.userName.value.isEmpty) {
- loginDialog();
- } else {
- logOutConfirmDialog();
- }
- },
- ),
- ],
- ),
- SettingsSection(title: Text(translate("Settings")), tiles: [
- if (!disabledSettings && !_hideNetwork && !_hideServer)
- SettingsTile(
- title: Text(translate('ID/Relay Server')),
- leading: Icon(Icons.cloud),
- onPressed: (context) {
- showServerSettings(gFFI.dialogManager);
- }),
- if (!isIOS && !_hideNetwork && !_hideProxy)
- SettingsTile(
- title: Text(translate('Socks5/Http(s) Proxy')),
- leading: Icon(Icons.network_ping),
- onPressed: (context) {
- changeSocks5Proxy();
- }),
- SettingsTile(
- title: Text(translate('Language')),
- leading: Icon(Icons.translate),
- onPressed: (context) {
- showLanguageSettings(gFFI.dialogManager);
- }),
- SettingsTile(
- title: Text(translate(
- Theme.of(context).brightness == Brightness.light
- ? 'Light Theme'
- : 'Dark Theme')),
- leading: Icon(Theme.of(context).brightness == Brightness.light
- ? Icons.dark_mode
- : Icons.light_mode),
- onPressed: (context) {
- showThemeSettings(gFFI.dialogManager);
- },
- )
- ]),
- if (isAndroid)
- SettingsSection(title: Text(translate('Hardware Codec')), tiles: [
- SettingsTile.switchTile(
- title: Text(translate('Enable hardware codec')),
- initialValue: _enableHardwareCodec,
- onToggle: isOptionFixed(kOptionEnableHwcodec)
- ? null
- : (v) async {
- await mainSetBoolOption(kOptionEnableHwcodec, v);
- final newValue =
- await mainGetBoolOption(kOptionEnableHwcodec);
- setState(() {
- _enableHardwareCodec = newValue;
- });
- },
- ),
- ]),
- if (isAndroid)
- SettingsSection(
- title: Text(translate("Recording")),
- tiles: [
- if (!outgoingOnly)
- SettingsTile.switchTile(
- title:
- Text(translate('Automatically record incoming sessions')),
- initialValue: _autoRecordIncomingSession,
- onToggle: isOptionFixed(kOptionAllowAutoRecordIncoming)
- ? null
- : (v) async {
- await bind.mainSetOption(
- key: kOptionAllowAutoRecordIncoming,
- value: bool2option(
- kOptionAllowAutoRecordIncoming, v));
- final newValue = option2bool(
- kOptionAllowAutoRecordIncoming,
- await bind.mainGetOption(
- key: kOptionAllowAutoRecordIncoming));
- setState(() {
- _autoRecordIncomingSession = newValue;
- });
- },
- ),
- if (!incommingOnly)
- SettingsTile.switchTile(
- title:
- Text(translate('Automatically record outgoing sessions')),
- initialValue: _autoRecordOutgoingSession,
- onToggle: isOptionFixed(kOptionAllowAutoRecordOutgoing)
- ? null
- : (v) async {
- await bind.mainSetLocalOption(
- key: kOptionAllowAutoRecordOutgoing,
- value: bool2option(
- kOptionAllowAutoRecordOutgoing, v));
- final newValue = option2bool(
- kOptionAllowAutoRecordOutgoing,
- bind.mainGetLocalOption(
- key: kOptionAllowAutoRecordOutgoing));
- setState(() {
- _autoRecordOutgoingSession = newValue;
- });
- },
- ),
- SettingsTile(
- title: Text(translate("Directory")),
- description: Text(bind.mainVideoSaveDirectory(root: false)),
- ),
- ],
- ),
- if (isAndroid &&
- !disabledSettings &&
- !outgoingOnly &&
- !hideSecuritySettings)
- SettingsSection(title: Text('2FA'), tiles: tfaTiles),
- if (isAndroid &&
- !disabledSettings &&
- !outgoingOnly &&
- !hideSecuritySettings)
- SettingsSection(
- title: Text(translate("Share Screen")),
- tiles: shareScreenTiles,
- ),
- if (!bind.isIncomingOnly()) defaultDisplaySection(),
- if (isAndroid &&
- !disabledSettings &&
- !outgoingOnly &&
- !hideSecuritySettings)
- SettingsSection(
- title: Text(translate("Enhancements")),
- tiles: enhancementsTiles,
- ),
- SettingsSection(
- title: Text(translate("About")),
- tiles: [
- SettingsTile(
- onPressed: (context) async {
- await launchUrl(Uri.parse(url));
- },
- title: Text(translate("Version: ") + version),
- value: Padding(
- padding: EdgeInsets.symmetric(vertical: 8),
- child: Text('rustdesk.com',
- style: TextStyle(
- decoration: TextDecoration.underline,
- )),
- ),
- leading: Icon(Icons.info)),
- SettingsTile(
- title: Text(translate("Build Date")),
- value: Padding(
- padding: EdgeInsets.symmetric(vertical: 8),
- child: Text(_buildDate),
- ),
- leading: Icon(Icons.query_builder)),
- if (isAndroid)
- SettingsTile(
- onPressed: (context) => onCopyFingerprint(_fingerprint),
- title: Text(translate("Fingerprint")),
- value: Padding(
- padding: EdgeInsets.symmetric(vertical: 8),
- child: Text(_fingerprint),
- ),
- leading: Icon(Icons.fingerprint)),
- SettingsTile(
- title: Text(translate("Privacy Statement")),
- onPressed: (context) =>
- launchUrlString('https://rustdesk.com/privacy.html'),
- leading: Icon(Icons.privacy_tip),
- )
- ],
- ),
- ],
- );
- return settings;
- }
- Future<bool> canStartOnBoot() async {
- // start on boot depends on ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS and SYSTEM_ALERT_WINDOW
- if (_hasIgnoreBattery && !_ignoreBatteryOpt) {
- return false;
- }
- if (!await AndroidPermissionManager.check(kSystemAlertWindow)) {
- return false;
- }
- return true;
- }
- defaultDisplaySection() {
- return SettingsSection(
- title: Text(translate("Display Settings")),
- tiles: [
- SettingsTile(
- title: Text(translate('Display Settings')),
- leading: Icon(Icons.desktop_windows_outlined),
- trailing: Icon(Icons.arrow_forward_ios),
- onPressed: (context) {
- Navigator.push(context, MaterialPageRoute(builder: (context) {
- return _DisplayPage();
- }));
- })
- ],
- );
- }
- }
- void showLanguageSettings(OverlayDialogManager dialogManager) async {
- try {
- final langs = json.decode(await bind.mainGetLangs()) as List<dynamic>;
- var lang = bind.mainGetLocalOption(key: kCommConfKeyLang);
- dialogManager.show((setState, close, context) {
- setLang(v) async {
- if (lang != v) {
- setState(() {
- lang = v;
- });
- await bind.mainSetLocalOption(key: kCommConfKeyLang, value: v);
- HomePage.homeKey.currentState?.refreshPages();
- Future.delayed(Duration(milliseconds: 200), close);
- }
- }
- final isOptFixed = isOptionFixed(kCommConfKeyLang);
- return CustomAlertDialog(
- content: Column(
- children: [
- getRadio(Text(translate('Default')), defaultOptionLang, lang,
- isOptFixed ? null : setLang),
- Divider(color: MyTheme.border),
- ] +
- langs.map((e) {
- final key = e[0] as String;
- final name = e[1] as String;
- return getRadio(Text(translate(name)), key, lang,
- isOptFixed ? null : setLang);
- }).toList(),
- ),
- );
- }, backDismiss: true, clickMaskDismiss: true);
- } catch (e) {
- //
- }
- }
- void showThemeSettings(OverlayDialogManager dialogManager) async {
- var themeMode = MyTheme.getThemeModePreference();
- dialogManager.show((setState, close, context) {
- setTheme(v) {
- if (themeMode != v) {
- setState(() {
- themeMode = v;
- });
- MyTheme.changeDarkMode(themeMode);
- Future.delayed(Duration(milliseconds: 200), close);
- }
- }
- final isOptFixed = isOptionFixed(kCommConfKeyTheme);
- return CustomAlertDialog(
- content: Column(children: [
- getRadio(Text(translate('Light')), ThemeMode.light, themeMode,
- isOptFixed ? null : setTheme),
- getRadio(Text(translate('Dark')), ThemeMode.dark, themeMode,
- isOptFixed ? null : setTheme),
- getRadio(Text(translate('Follow System')), ThemeMode.system, themeMode,
- isOptFixed ? null : setTheme)
- ]),
- );
- }, backDismiss: true, clickMaskDismiss: true);
- }
- void showAbout(OverlayDialogManager dialogManager) {
- dialogManager.show((setState, close, context) {
- return CustomAlertDialog(
- title: Text(translate('About RustDesk')),
- content: Wrap(direction: Axis.vertical, spacing: 12, children: [
- Text('Version: $version'),
- InkWell(
- onTap: () async {
- const url = 'https://rustdesk.com/';
- await launchUrl(Uri.parse(url));
- },
- child: Padding(
- padding: EdgeInsets.symmetric(vertical: 8),
- child: Text('rustdesk.com',
- style: TextStyle(
- decoration: TextDecoration.underline,
- )),
- )),
- ]),
- actions: [],
- );
- }, clickMaskDismiss: true, backDismiss: true);
- }
- class ScanButton extends StatelessWidget {
- @override
- Widget build(BuildContext context) {
- return IconButton(
- icon: Icon(Icons.qr_code_scanner),
- onPressed: () {
- Navigator.push(
- context,
- MaterialPageRoute(
- builder: (BuildContext context) => ScanPage(),
- ),
- );
- },
- );
- }
- }
- class _DisplayPage extends StatefulWidget {
- const _DisplayPage();
- @override
- State<_DisplayPage> createState() => __DisplayPageState();
- }
- class __DisplayPageState extends State<_DisplayPage> {
- @override
- Widget build(BuildContext context) {
- final Map codecsJson = jsonDecode(bind.mainSupportedHwdecodings());
- final h264 = codecsJson['h264'] ?? false;
- final h265 = codecsJson['h265'] ?? false;
- var codecList = [
- _RadioEntry('Auto', 'auto'),
- _RadioEntry('VP8', 'vp8'),
- _RadioEntry('VP9', 'vp9'),
- _RadioEntry('AV1', 'av1'),
- if (h264) _RadioEntry('H264', 'h264'),
- if (h265) _RadioEntry('H265', 'h265')
- ];
- RxBool showCustomImageQuality = false.obs;
- return Scaffold(
- appBar: AppBar(
- leading: IconButton(
- onPressed: () => Navigator.pop(context),
- icon: Icon(Icons.arrow_back_ios)),
- title: Text(translate('Display Settings')),
- centerTitle: true,
- ),
- body: SettingsList(sections: [
- SettingsSection(
- tiles: [
- _getPopupDialogRadioEntry(
- title: 'Default View Style',
- list: [
- _RadioEntry('Scale original', kRemoteViewStyleOriginal),
- _RadioEntry('Scale adaptive', kRemoteViewStyleAdaptive)
- ],
- getter: () =>
- bind.mainGetUserDefaultOption(key: kOptionViewStyle),
- asyncSetter: isOptionFixed(kOptionViewStyle)
- ? null
- : (value) async {
- await bind.mainSetUserDefaultOption(
- key: kOptionViewStyle, value: value);
- },
- ),
- _getPopupDialogRadioEntry(
- title: 'Default Image Quality',
- list: [
- _RadioEntry('Good image quality', kRemoteImageQualityBest),
- _RadioEntry('Balanced', kRemoteImageQualityBalanced),
- _RadioEntry('Optimize reaction time', kRemoteImageQualityLow),
- _RadioEntry('Custom', kRemoteImageQualityCustom),
- ],
- getter: () {
- final v =
- bind.mainGetUserDefaultOption(key: kOptionImageQuality);
- showCustomImageQuality.value = v == kRemoteImageQualityCustom;
- return v;
- },
- asyncSetter: isOptionFixed(kOptionImageQuality)
- ? null
- : (value) async {
- await bind.mainSetUserDefaultOption(
- key: kOptionImageQuality, value: value);
- showCustomImageQuality.value =
- value == kRemoteImageQualityCustom;
- },
- tail: customImageQualitySetting(),
- showTail: showCustomImageQuality,
- notCloseValue: kRemoteImageQualityCustom,
- ),
- _getPopupDialogRadioEntry(
- title: 'Default Codec',
- list: codecList,
- getter: () =>
- bind.mainGetUserDefaultOption(key: kOptionCodecPreference),
- asyncSetter: isOptionFixed(kOptionCodecPreference)
- ? null
- : (value) async {
- await bind.mainSetUserDefaultOption(
- key: kOptionCodecPreference, value: value);
- },
- ),
- ],
- ),
- SettingsSection(
- title: Text(translate('Other Default Options')),
- tiles:
- otherDefaultSettings().map((e) => otherRow(e.$1, e.$2)).toList(),
- ),
- ]),
- );
- }
- SettingsTile otherRow(String label, String key) {
- final value = bind.mainGetUserDefaultOption(key: key) == 'Y';
- final isOptFixed = isOptionFixed(key);
- return SettingsTile.switchTile(
- initialValue: value,
- title: Text(translate(label)),
- onToggle: isOptFixed
- ? null
- : (b) async {
- await bind.mainSetUserDefaultOption(
- key: key, value: b ? 'Y' : defaultOptionNo);
- setState(() {});
- },
- );
- }
- }
- class _ManageTrustedDevices extends StatefulWidget {
- const _ManageTrustedDevices();
- @override
- State<_ManageTrustedDevices> createState() => __ManageTrustedDevicesState();
- }
- class __ManageTrustedDevicesState extends State<_ManageTrustedDevices> {
- RxList<TrustedDevice> trustedDevices = RxList.empty(growable: true);
- RxList<Uint8List> selectedDevices = RxList.empty();
- @override
- Widget build(BuildContext context) {
- return Scaffold(
- appBar: AppBar(
- title: Text(translate('Manage trusted devices')),
- centerTitle: true,
- actions: [
- Obx(() => IconButton(
- icon: Icon(Icons.delete, color: Colors.white),
- onPressed: selectedDevices.isEmpty
- ? null
- : () {
- confrimDeleteTrustedDevicesDialog(
- trustedDevices, selectedDevices);
- }))
- ],
- ),
- body: FutureBuilder(
- future: TrustedDevice.get(),
- builder: (context, snapshot) {
- if (snapshot.connectionState == ConnectionState.waiting) {
- return Center(child: CircularProgressIndicator());
- }
- if (snapshot.hasError) {
- return Center(child: Text('Error: ${snapshot.error}'));
- }
- final devices = snapshot.data as List<TrustedDevice>;
- trustedDevices = devices.obs;
- return trustedDevicesTable(trustedDevices, selectedDevices);
- }),
- );
- }
- }
- class _RadioEntry {
- final String label;
- final String value;
- _RadioEntry(this.label, this.value);
- }
- typedef _RadioEntryGetter = String Function();
- typedef _RadioEntrySetter = Future<void> Function(String);
- SettingsTile _getPopupDialogRadioEntry({
- required String title,
- required List<_RadioEntry> list,
- required _RadioEntryGetter getter,
- required _RadioEntrySetter? asyncSetter,
- Widget? tail,
- RxBool? showTail,
- String? notCloseValue,
- }) {
- RxString groupValue = ''.obs;
- RxString valueText = ''.obs;
- init() {
- groupValue.value = getter();
- final e = list.firstWhereOrNull((e) => e.value == groupValue.value);
- if (e != null) {
- valueText.value = e.label;
- }
- }
- init();
- void showDialog() async {
- gFFI.dialogManager.show((setState, close, context) {
- final onChanged = asyncSetter == null
- ? null
- : (String? value) async {
- if (value == null) return;
- await asyncSetter(value);
- init();
- if (value != notCloseValue) {
- close();
- }
- };
- return CustomAlertDialog(
- content: Obx(
- () => Column(children: [
- ...list
- .map((e) => getRadio(Text(translate(e.label)), e.value,
- groupValue.value, onChanged))
- .toList(),
- Offstage(
- offstage:
- !(tail != null && showTail != null && showTail.value == true),
- child: tail,
- ),
- ]),
- ));
- }, backDismiss: true, clickMaskDismiss: true);
- }
- return SettingsTile(
- title: Text(translate(title)),
- onPressed: asyncSetter == null ? null : (context) => showDialog(),
- value: Padding(
- padding: EdgeInsets.symmetric(vertical: 8),
- child: Obx(() => Text(translate(valueText.value))),
- ),
- );
- }
|