content: 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: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 _allowWebSocket = 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 _hideWebSocket = false;
    var _enableTrustedDevices = false;
    var _enableUdpPunch = false;
    var _enableIpv6Punch = 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));
      _allowWebSocket = mainGetBoolOptionSync(kOptionAllowWebSocket);
      _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';
      _hideWebSocket =
          bind.mainGetBuildinOption(key: kOptionHideWebSocketSetting) == 'Y' ||
              isWeb;
      _enableTrustedDevices = mainGetBoolOptionSync(kOptionEnableTrustedDevices);
      _enableUdpPunch = mainGetLocalBoolOptionSync(kOptionEnableUdpPunch);
      _enableIpv6Punch = mainGetLocalBoolOptionSync(kOptionEnableIpv6Punch);
    }
  
    @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 incomingOnly = 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')),
          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')),
            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();
                  }),
            if (!disabledSettings && !_hideNetwork && !_hideWebSocket)
              SettingsTile.switchTile(
                title: Text(translate('Use WebSocket')),
                initialValue: _allowWebSocket,
                onToggle: isOptionFixed(kOptionAllowWebSocket)
                    ? null
                    : (v) async {
                        await mainSetBoolOption(kOptionAllowWebSocket, v);
                        final newValue =
                            await mainGetBoolOption(kOptionAllowWebSocket);
                        setState(() {
                          _allowWebSocket = newValue;
                        });
                      },
              ),
            if (!incomingOnly)
              SettingsTile.switchTile(
                title: Text(translate('Enable UDP hole punching')),
                initialValue: _enableUdpPunch,
                onToggle: (v) async {
                  await mainSetLocalBoolOption(kOptionEnableUdpPunch, v);
                  final newValue =
                      mainGetLocalBoolOptionSync(kOptionEnableUdpPunch);
                  setState(() {
                    _enableUdpPunch = newValue;
                  });
                },
              ),
            if (!incomingOnly)
              SettingsTile.switchTile(
                title: Text(translate('Enable IPv6 P2P connection')),
                initialValue: _enableIpv6Punch,
                onToggle: (v) async {
                  await mainSetLocalBoolOption(kOptionEnableIpv6Punch, v);
                  final newValue =
                      mainGetLocalBoolOptionSync(kOptionEnableIpv6Punch);
                  setState(() {
                    _enableIpv6Punch = newValue;
                  });
                },
              ),
            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 (!incomingOnly)
                  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))),
      ),
    );
  }
  