123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538 |
- import 'dart:async';
- import 'dart:convert';
- import 'package:bot_toast/bot_toast.dart';
- import 'package:flutter/material.dart';
- import 'package:flutter/services.dart';
- import 'package:flutter_hbb/common/shared_state.dart';
- import 'package:flutter_hbb/common/widgets/setting_widgets.dart';
- import 'package:flutter_hbb/consts.dart';
- import 'package:flutter_hbb/models/peer_model.dart';
- import 'package:flutter_hbb/models/peer_tab_model.dart';
- import 'package:flutter_hbb/models/state_model.dart';
- import 'package:get/get.dart';
- import 'package:qr_flutter/qr_flutter.dart';
- import '../../common.dart';
- import '../../models/model.dart';
- import '../../models/platform_model.dart';
- import 'address_book.dart';
- void clientClose(SessionID sessionId, OverlayDialogManager dialogManager) {
- msgBox(sessionId, 'info', 'Close', 'Are you sure to close the connection?',
- '', dialogManager);
- }
- abstract class ValidationRule {
- String get name;
- bool validate(String value);
- }
- class LengthRangeValidationRule extends ValidationRule {
- final int _min;
- final int _max;
- LengthRangeValidationRule(this._min, this._max);
- @override
- String get name => translate('length %min% to %max%')
- .replaceAll('%min%', _min.toString())
- .replaceAll('%max%', _max.toString());
- @override
- bool validate(String value) {
- return value.length >= _min && value.length <= _max;
- }
- }
- class RegexValidationRule extends ValidationRule {
- final String _name;
- final RegExp _regex;
- RegexValidationRule(this._name, this._regex);
- @override
- String get name => translate(_name);
- @override
- bool validate(String value) {
- return value.isNotEmpty ? value.contains(_regex) : false;
- }
- }
- void changeIdDialog() {
- var newId = "";
- var msg = "";
- var isInProgress = false;
- TextEditingController controller = TextEditingController();
- final RxString rxId = controller.text.trim().obs;
- final rules = [
- RegexValidationRule('starts with a letter', RegExp(r'^[a-zA-Z]')),
- LengthRangeValidationRule(6, 16),
- RegexValidationRule('allowed characters', RegExp(r'^[\w-]*$'))
- ];
- gFFI.dialogManager.show((setState, close, context) {
- submit() async {
- debugPrint("onSubmit");
- newId = controller.text.trim();
- final Iterable violations = rules.where((r) => !r.validate(newId));
- if (violations.isNotEmpty) {
- setState(() {
- msg = (isDesktop || isWebDesktop)
- ? '${translate('Prompt')}: ${violations.map((r) => r.name).join(', ')}'
- : violations.map((r) => r.name).join(', ');
- });
- return;
- }
- setState(() {
- msg = "";
- isInProgress = true;
- bind.mainChangeId(newId: newId);
- });
- var status = await bind.mainGetAsyncStatus();
- while (status == " ") {
- await Future.delayed(const Duration(milliseconds: 100));
- status = await bind.mainGetAsyncStatus();
- }
- if (status.isEmpty) {
- // ok
- close();
- return;
- }
- setState(() {
- isInProgress = false;
- msg = (isDesktop || isWebDesktop)
- ? '${translate('Prompt')}: ${translate(status)}'
- : translate(status);
- });
- }
- return CustomAlertDialog(
- title: Text(translate("Change ID")),
- content: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(translate("id_change_tip")),
- const SizedBox(
- height: 12.0,
- ),
- TextField(
- decoration: InputDecoration(
- labelText: translate('Your new ID'),
- errorText: msg.isEmpty ? null : translate(msg),
- suffixText: '${rxId.value.length}/16',
- suffixStyle: const TextStyle(fontSize: 12, color: Colors.grey)),
- inputFormatters: [
- LengthLimitingTextInputFormatter(16),
- // FilteringTextInputFormatter(RegExp(r"[a-zA-z][a-zA-z0-9\_]*"), allow: true)
- ],
- controller: controller,
- autofocus: true,
- onChanged: (value) {
- setState(() {
- rxId.value = value.trim();
- msg = '';
- });
- },
- ).workaroundFreezeLinuxMint(),
- const SizedBox(
- height: 8.0,
- ),
- (isDesktop || isWebDesktop)
- ? Obx(() => Wrap(
- runSpacing: 8,
- spacing: 4,
- children: rules.map((e) {
- var checked = e.validate(rxId.value);
- return Chip(
- label: Text(
- e.name,
- style: TextStyle(
- color: checked
- ? const Color(0xFF0A9471)
- : Color.fromARGB(255, 198, 86, 157)),
- ),
- backgroundColor: checked
- ? const Color(0xFFD0F7ED)
- : Color.fromARGB(255, 247, 205, 232));
- }).toList(),
- )).marginOnly(bottom: 8)
- : SizedBox.shrink(),
- // NOT use Offstage to wrap LinearProgressIndicator
- if (isInProgress) const LinearProgressIndicator(),
- ],
- ),
- actions: [
- dialogButton("Cancel", onPressed: close, isOutline: true),
- dialogButton("OK", onPressed: submit),
- ],
- onSubmit: submit,
- onCancel: close,
- );
- });
- }
- void changeWhiteList({Function()? callback}) async {
- final curWhiteList = await bind.mainGetOption(key: kOptionWhitelist);
- var newWhiteListField = curWhiteList == defaultOptionWhitelist
- ? ''
- : curWhiteList.split(',').join('\n');
- var controller = TextEditingController(text: newWhiteListField);
- var msg = "";
- var isInProgress = false;
- final isOptFixed = isOptionFixed(kOptionWhitelist);
- gFFI.dialogManager.show((setState, close, context) {
- return CustomAlertDialog(
- title: Text(translate("IP Whitelisting")),
- content: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(translate("whitelist_sep")),
- const SizedBox(
- height: 8.0,
- ),
- Row(
- children: [
- Expanded(
- child: TextField(
- maxLines: null,
- decoration: InputDecoration(
- errorText: msg.isEmpty ? null : translate(msg),
- ),
- controller: controller,
- enabled: !isOptFixed,
- autofocus: true)
- .workaroundFreezeLinuxMint(),
- ),
- ],
- ),
- const SizedBox(
- height: 4.0,
- ),
- // NOT use Offstage to wrap LinearProgressIndicator
- if (isInProgress) const LinearProgressIndicator(),
- ],
- ),
- actions: [
- dialogButton("Cancel", onPressed: close, isOutline: true),
- if (!isOptFixed)
- dialogButton("Clear", onPressed: () async {
- await bind.mainSetOption(
- key: kOptionWhitelist, value: defaultOptionWhitelist);
- callback?.call();
- close();
- }, isOutline: true),
- if (!isOptFixed)
- dialogButton(
- "OK",
- onPressed: () async {
- setState(() {
- msg = "";
- isInProgress = true;
- });
- newWhiteListField = controller.text.trim();
- var newWhiteList = "";
- if (newWhiteListField.isEmpty) {
- // pass
- } else {
- final ips =
- newWhiteListField.trim().split(RegExp(r"[\s,;\n]+"));
- // test ip
- final ipMatch = RegExp(
- r"^(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]?|0)\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]?|0)\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]?|0)\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]?|0)(\/([1-9]|[1-2][0-9]|3[0-2])){0,1}$");
- final ipv6Match = RegExp(
- r"^(((?:[0-9A-Fa-f]{1,4}))*((?::[0-9A-Fa-f]{1,4}))*::((?:[0-9A-Fa-f]{1,4}))*((?::[0-9A-Fa-f]{1,4}))*|((?:[0-9A-Fa-f]{1,4}))((?::[0-9A-Fa-f]{1,4})){7})(\/([1-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8])){0,1}$");
- for (final ip in ips) {
- if (!ipMatch.hasMatch(ip) && !ipv6Match.hasMatch(ip)) {
- msg = "${translate("Invalid IP")} $ip";
- setState(() {
- isInProgress = false;
- });
- return;
- }
- }
- newWhiteList = ips.join(',');
- }
- if (newWhiteList.trim().isEmpty) {
- newWhiteList = defaultOptionWhitelist;
- }
- await bind.mainSetOption(
- key: kOptionWhitelist, value: newWhiteList);
- callback?.call();
- close();
- },
- ),
- ],
- onCancel: close,
- );
- });
- }
- Future<String> changeDirectAccessPort(
- String currentIP, String currentPort) async {
- final controller = TextEditingController(text: currentPort);
- await gFFI.dialogManager.show((setState, close, context) {
- return CustomAlertDialog(
- title: Text(translate("Change Local Port")),
- content: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- const SizedBox(height: 8.0),
- Row(
- children: [
- Expanded(
- child: TextField(
- maxLines: null,
- keyboardType: TextInputType.number,
- decoration: InputDecoration(
- hintText: '21118',
- isCollapsed: true,
- prefix: Text('$currentIP : '),
- suffix: IconButton(
- padding: EdgeInsets.zero,
- icon: const Icon(Icons.clear, size: 16),
- onPressed: () => controller.clear())),
- 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])$')),
- ],
- controller: controller,
- autofocus: true)
- .workaroundFreezeLinuxMint(),
- ),
- ],
- ),
- ],
- ),
- actions: [
- dialogButton("Cancel", onPressed: close, isOutline: true),
- dialogButton("OK", onPressed: () async {
- await bind.mainSetOption(
- key: kOptionDirectAccessPort, value: controller.text);
- close();
- }),
- ],
- onCancel: close,
- );
- });
- return controller.text;
- }
- Future<String> changeAutoDisconnectTimeout(String old) async {
- final controller = TextEditingController(text: old);
- await gFFI.dialogManager.show((setState, close, context) {
- return CustomAlertDialog(
- title: Text(translate("Timeout in minutes")),
- content: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- const SizedBox(height: 8.0),
- Row(
- children: [
- Expanded(
- child: TextField(
- maxLines: null,
- keyboardType: TextInputType.number,
- decoration: InputDecoration(
- hintText: '10',
- isCollapsed: true,
- suffix: IconButton(
- padding: EdgeInsets.zero,
- icon: const Icon(Icons.clear, size: 16),
- onPressed: () => controller.clear())),
- 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])$')),
- ],
- controller: controller,
- autofocus: true)
- .workaroundFreezeLinuxMint(),
- ),
- ],
- ),
- ],
- ),
- actions: [
- dialogButton("Cancel", onPressed: close, isOutline: true),
- dialogButton("OK", onPressed: () async {
- await bind.mainSetOption(
- key: kOptionAutoDisconnectTimeout, value: controller.text);
- close();
- }),
- ],
- onCancel: close,
- );
- });
- return controller.text;
- }
- class DialogTextField extends StatelessWidget {
- final String title;
- final String? hintText;
- final bool obscureText;
- final String? errorText;
- final String? helperText;
- final Widget? prefixIcon;
- final Widget? suffixIcon;
- final TextEditingController controller;
- final FocusNode? focusNode;
- final TextInputType? keyboardType;
- final List<TextInputFormatter>? inputFormatters;
- final int? maxLength;
- static const kUsernameTitle = 'Username';
- static const kUsernameIcon = Icon(Icons.account_circle_outlined);
- static const kPasswordTitle = 'Password';
- static const kPasswordIcon = Icon(Icons.lock_outline);
- DialogTextField(
- {Key? key,
- this.focusNode,
- this.obscureText = false,
- this.errorText,
- this.helperText,
- this.prefixIcon,
- this.suffixIcon,
- this.hintText,
- this.keyboardType,
- this.inputFormatters,
- this.maxLength,
- required this.title,
- required this.controller})
- : super(key: key);
- @override
- Widget build(BuildContext context) {
- return Row(
- children: [
- Expanded(
- child: Column(
- children: [
- TextField(
- decoration: InputDecoration(
- labelText: title,
- hintText: hintText,
- prefixIcon: prefixIcon,
- suffixIcon: suffixIcon,
- helperText: helperText,
- helperMaxLines: 8,
- ),
- controller: controller,
- focusNode: focusNode,
- autofocus: true,
- obscureText: obscureText,
- keyboardType: keyboardType,
- inputFormatters: inputFormatters,
- maxLength: maxLength,
- ),
- if (errorText != null)
- Align(
- alignment: Alignment.centerLeft,
- child: SelectableText(
- errorText!,
- style: TextStyle(
- color: Theme.of(context).colorScheme.error,
- fontSize: 12,
- ),
- textAlign: TextAlign.left,
- ).paddingOnly(top: 8, left: 12),
- ),
- ],
- ).workaroundFreezeLinuxMint(),
- ),
- ],
- ).paddingSymmetric(vertical: 4.0);
- }
- }
- abstract class ValidationField extends StatelessWidget {
- ValidationField({Key? key}) : super(key: key);
- String? validate();
- bool get isReady;
- }
- class Dialog2FaField extends ValidationField {
- Dialog2FaField({
- Key? key,
- required this.controller,
- this.autoFocus = true,
- this.reRequestFocus = false,
- this.title,
- this.hintText,
- this.errorText,
- this.readyCallback,
- this.onChanged,
- }) : super(key: key);
- final TextEditingController controller;
- final bool autoFocus;
- final bool reRequestFocus;
- final String? title;
- final String? hintText;
- final String? errorText;
- final VoidCallback? readyCallback;
- final VoidCallback? onChanged;
- final errMsg = translate('2FA code must be 6 digits.');
- @override
- Widget build(BuildContext context) {
- return DialogVerificationCodeField(
- title: title ?? translate('2FA code'),
- controller: controller,
- errorText: errorText,
- autoFocus: autoFocus,
- reRequestFocus: reRequestFocus,
- hintText: hintText,
- readyCallback: readyCallback,
- onChanged: _onChanged,
- keyboardType: TextInputType.number,
- inputFormatters: [
- FilteringTextInputFormatter.allow(RegExp(r'[0-9]')),
- ],
- );
- }
- String get text => controller.text;
- bool get isAllDigits => text.codeUnits.every((e) => e >= 48 && e <= 57);
- @override
- bool get isReady => text.length == 6 && isAllDigits;
- @override
- String? validate() => isReady ? null : errMsg;
- _onChanged(StateSetter setState, SimpleWrapper<String?> errText) {
- onChanged?.call();
- if (text.length > 6) {
- setState(() => errText.value = errMsg);
- return;
- }
- if (!isAllDigits) {
- setState(() => errText.value = errMsg);
- return;
- }
- if (isReady) {
- readyCallback?.call();
- return;
- }
- if (errText.value != null) {
- setState(() => errText.value = null);
- }
- }
- }
- class DialogEmailCodeField extends ValidationField {
- DialogEmailCodeField({
- Key? key,
- required this.controller,
- this.autoFocus = true,
- this.reRequestFocus = false,
- this.hintText,
- this.errorText,
- this.readyCallback,
- this.onChanged,
- }) : super(key: key);
- final TextEditingController controller;
- final bool autoFocus;
- final bool reRequestFocus;
- final String? hintText;
- final String? errorText;
- final VoidCallback? readyCallback;
- final VoidCallback? onChanged;
- final errMsg = translate('Email verification code must be 6 characters.');
- @override
- Widget build(BuildContext context) {
- return DialogVerificationCodeField(
- title: translate('Verification code'),
- controller: controller,
- errorText: errorText,
- autoFocus: autoFocus,
- reRequestFocus: reRequestFocus,
- hintText: hintText,
- readyCallback: readyCallback,
- helperText: translate('verification_tip'),
- onChanged: _onChanged,
- keyboardType: TextInputType.visiblePassword,
- );
- }
- String get text => controller.text;
- @override
- bool get isReady => text.length == 6;
- @override
- String? validate() => isReady ? null : errMsg;
- _onChanged(StateSetter setState, SimpleWrapper<String?> errText) {
- onChanged?.call();
- if (text.length > 6) {
- setState(() => errText.value = errMsg);
- return;
- }
- if (isReady) {
- readyCallback?.call();
- return;
- }
- if (errText.value != null) {
- setState(() => errText.value = null);
- }
- }
- }
- class DialogVerificationCodeField extends StatefulWidget {
- DialogVerificationCodeField({
- Key? key,
- required this.controller,
- required this.title,
- this.autoFocus = true,
- this.reRequestFocus = false,
- this.helperText,
- this.hintText,
- this.errorText,
- this.textLength,
- this.readyCallback,
- this.onChanged,
- this.keyboardType,
- this.inputFormatters,
- }) : super(key: key);
- final TextEditingController controller;
- final bool autoFocus;
- final bool reRequestFocus;
- final String title;
- final String? helperText;
- final String? hintText;
- final String? errorText;
- final int? textLength;
- final VoidCallback? readyCallback;
- final Function(StateSetter setState, SimpleWrapper<String?> errText)?
- onChanged;
- final TextInputType? keyboardType;
- final List<TextInputFormatter>? inputFormatters;
- @override
- State<DialogVerificationCodeField> createState() =>
- _DialogVerificationCodeField();
- }
- class _DialogVerificationCodeField extends State<DialogVerificationCodeField> {
- final _focusNode = FocusNode();
- Timer? _timer;
- Timer? _timerReRequestFocus;
- SimpleWrapper<String?> errorText = SimpleWrapper(null);
- String _preText = '';
- @override
- void initState() {
- super.initState();
- if (widget.autoFocus) {
- _timer =
- Timer(Duration(milliseconds: 50), () => _focusNode.requestFocus());
- if (widget.onChanged != null) {
- widget.controller.addListener(() {
- final text = widget.controller.text.trim();
- if (text == _preText) return;
- widget.onChanged!(setState, errorText);
- _preText = text;
- });
- }
- }
- // software secure keyboard will take the focus since flutter 3.13
- // request focus again when android account password obtain focus
- if (isAndroid && widget.reRequestFocus) {
- _focusNode.addListener(() {
- if (_focusNode.hasFocus) {
- _timerReRequestFocus?.cancel();
- _timerReRequestFocus = Timer(
- Duration(milliseconds: 100), () => _focusNode.requestFocus());
- }
- });
- }
- }
- @override
- void dispose() {
- _timer?.cancel();
- _timerReRequestFocus?.cancel();
- _focusNode.unfocus();
- _focusNode.dispose();
- super.dispose();
- }
- @override
- Widget build(BuildContext context) {
- return DialogTextField(
- title: widget.title,
- controller: widget.controller,
- errorText: widget.errorText ?? errorText.value,
- focusNode: _focusNode,
- helperText: widget.helperText,
- keyboardType: widget.keyboardType,
- inputFormatters: widget.inputFormatters,
- );
- }
- }
- class PasswordWidget extends StatefulWidget {
- PasswordWidget({
- Key? key,
- required this.controller,
- this.autoFocus = true,
- this.reRequestFocus = false,
- this.hintText,
- this.errorText,
- this.title,
- this.maxLength,
- }) : super(key: key);
- final TextEditingController controller;
- final bool autoFocus;
- final bool reRequestFocus;
- final String? hintText;
- final String? errorText;
- final String? title;
- final int? maxLength;
- @override
- State<PasswordWidget> createState() => _PasswordWidgetState();
- }
- class _PasswordWidgetState extends State<PasswordWidget> {
- bool _passwordVisible = false;
- final _focusNode = FocusNode();
- Timer? _timer;
- Timer? _timerReRequestFocus;
- @override
- void initState() {
- super.initState();
- if (widget.autoFocus) {
- _timer =
- Timer(Duration(milliseconds: 50), () => _focusNode.requestFocus());
- }
- // software secure keyboard will take the focus since flutter 3.13
- // request focus again when android account password obtain focus
- if (isAndroid && widget.reRequestFocus) {
- _focusNode.addListener(() {
- if (_focusNode.hasFocus) {
- _timerReRequestFocus?.cancel();
- _timerReRequestFocus = Timer(
- Duration(milliseconds: 100), () => _focusNode.requestFocus());
- }
- });
- }
- }
- @override
- void dispose() {
- _timer?.cancel();
- _timerReRequestFocus?.cancel();
- _focusNode.unfocus();
- _focusNode.dispose();
- super.dispose();
- }
- @override
- Widget build(BuildContext context) {
- return DialogTextField(
- title: translate(widget.title ?? DialogTextField.kPasswordTitle),
- hintText: translate(widget.hintText ?? 'Enter your password'),
- controller: widget.controller,
- prefixIcon: DialogTextField.kPasswordIcon,
- suffixIcon: IconButton(
- icon: Icon(
- // Based on passwordVisible state choose the icon
- _passwordVisible ? Icons.visibility : Icons.visibility_off,
- color: MyTheme.lightTheme.primaryColor),
- onPressed: () {
- // Update the state i.e. toggle the state of passwordVisible variable
- setState(() {
- _passwordVisible = !_passwordVisible;
- });
- },
- ),
- obscureText: !_passwordVisible,
- errorText: widget.errorText,
- focusNode: _focusNode,
- maxLength: widget.maxLength,
- );
- }
- }
- void wrongPasswordDialog(SessionID sessionId,
- OverlayDialogManager dialogManager, type, title, text) {
- dialogManager.dismissAll();
- dialogManager.show((setState, close, context) {
- cancel() {
- close();
- closeConnection();
- }
- submit() {
- enterPasswordDialog(sessionId, dialogManager);
- }
- return CustomAlertDialog(
- title: null,
- content: msgboxContent(type, title, text),
- onSubmit: submit,
- onCancel: cancel,
- actions: [
- dialogButton(
- 'Cancel',
- onPressed: cancel,
- isOutline: true,
- ),
- dialogButton(
- 'Retry',
- onPressed: submit,
- ),
- ]);
- });
- }
- void enterPasswordDialog(
- SessionID sessionId, OverlayDialogManager dialogManager) async {
- await _connectDialog(
- sessionId,
- dialogManager,
- passwordController: TextEditingController(),
- );
- }
- void enterUserLoginDialog(
- SessionID sessionId, OverlayDialogManager dialogManager) async {
- await _connectDialog(
- sessionId,
- dialogManager,
- osUsernameController: TextEditingController(),
- osPasswordController: TextEditingController(),
- );
- }
- void enterUserLoginAndPasswordDialog(
- SessionID sessionId, OverlayDialogManager dialogManager) async {
- await _connectDialog(
- sessionId,
- dialogManager,
- osUsernameController: TextEditingController(),
- osPasswordController: TextEditingController(),
- passwordController: TextEditingController(),
- );
- }
- _connectDialog(
- SessionID sessionId,
- OverlayDialogManager dialogManager, {
- TextEditingController? osUsernameController,
- TextEditingController? osPasswordController,
- TextEditingController? passwordController,
- }) async {
- var rememberPassword = false;
- if (passwordController != null) {
- rememberPassword =
- await bind.sessionGetRemember(sessionId: sessionId) ?? false;
- }
- var rememberAccount = false;
- if (osUsernameController != null) {
- rememberAccount =
- await bind.sessionGetRemember(sessionId: sessionId) ?? false;
- }
- dialogManager.dismissAll();
- dialogManager.show((setState, close, context) {
- cancel() {
- close();
- closeConnection();
- }
- submit() {
- final osUsername = osUsernameController?.text.trim() ?? '';
- final osPassword = osPasswordController?.text.trim() ?? '';
- final password = passwordController?.text.trim() ?? '';
- if (passwordController != null && password.isEmpty) return;
- if (rememberAccount) {
- bind.sessionPeerOption(
- sessionId: sessionId, name: 'os-username', value: osUsername);
- bind.sessionPeerOption(
- sessionId: sessionId, name: 'os-password', value: osPassword);
- }
- gFFI.login(
- osUsername,
- osPassword,
- sessionId,
- password,
- rememberPassword,
- );
- close();
- dialogManager.showLoading(translate('Logging in...'),
- onCancel: closeConnection);
- }
- descWidget(String text) {
- return Column(
- children: [
- Align(
- alignment: Alignment.centerLeft,
- child: Text(
- text,
- maxLines: 3,
- softWrap: true,
- overflow: TextOverflow.ellipsis,
- style: TextStyle(fontSize: 16),
- ),
- ),
- Container(
- height: 8,
- ),
- ],
- );
- }
- rememberWidget(
- String desc,
- bool remember,
- ValueChanged<bool?>? onChanged,
- ) {
- return CheckboxListTile(
- contentPadding: const EdgeInsets.all(0),
- dense: true,
- controlAffinity: ListTileControlAffinity.leading,
- title: Text(desc),
- value: remember,
- onChanged: onChanged,
- );
- }
- osAccountWidget() {
- if (osUsernameController == null || osPasswordController == null) {
- return Offstage();
- }
- return Column(
- children: [
- descWidget(translate('login_linux_tip')),
- DialogTextField(
- title: translate(DialogTextField.kUsernameTitle),
- controller: osUsernameController,
- prefixIcon: DialogTextField.kUsernameIcon,
- errorText: null,
- ),
- PasswordWidget(
- controller: osPasswordController,
- autoFocus: false,
- ),
- rememberWidget(
- translate('remember_account_tip'),
- rememberAccount,
- (v) {
- if (v != null) {
- setState(() => rememberAccount = v);
- }
- },
- ),
- ],
- );
- }
- passwdWidget() {
- if (passwordController == null) {
- return Offstage();
- }
- return Column(
- children: [
- descWidget(translate('verify_rustdesk_password_tip')),
- PasswordWidget(
- controller: passwordController,
- autoFocus: osUsernameController == null,
- ),
- rememberWidget(
- translate('Remember password'),
- rememberPassword,
- (v) {
- if (v != null) {
- setState(() => rememberPassword = v);
- }
- },
- ),
- ],
- );
- }
- return CustomAlertDialog(
- title: Row(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- Icon(Icons.password_rounded, color: MyTheme.accent),
- Text(translate('Password Required')).paddingOnly(left: 10),
- ],
- ),
- content: Column(mainAxisSize: MainAxisSize.min, children: [
- osAccountWidget(),
- osUsernameController == null || passwordController == null
- ? Offstage()
- : Container(height: 12),
- passwdWidget(),
- ]),
- actions: [
- dialogButton(
- 'Cancel',
- icon: Icon(Icons.close_rounded),
- onPressed: cancel,
- isOutline: true,
- ),
- dialogButton(
- 'OK',
- icon: Icon(Icons.done_rounded),
- onPressed: submit,
- ),
- ],
- onSubmit: submit,
- onCancel: cancel,
- );
- });
- }
- void showWaitUacDialog(
- SessionID sessionId, OverlayDialogManager dialogManager, String type) {
- dialogManager.dismissAll();
- dialogManager.show(
- tag: '$sessionId-wait-uac',
- (setState, close, context) => CustomAlertDialog(
- title: null,
- content: msgboxContent(type, 'Wait', 'wait_accept_uac_tip'),
- actions: [
- dialogButton(
- 'OK',
- icon: Icon(Icons.done_rounded),
- onPressed: close,
- ),
- ],
- ));
- }
- // Another username && password dialog?
- void showRequestElevationDialog(
- SessionID sessionId, OverlayDialogManager dialogManager) {
- RxString groupValue = ''.obs;
- RxString errUser = ''.obs;
- RxString errPwd = ''.obs;
- TextEditingController userController = TextEditingController();
- TextEditingController pwdController = TextEditingController();
- void onRadioChanged(String? value) {
- if (value != null) {
- groupValue.value = value;
- }
- }
- // TODO get from theme
- final double fontSizeNote = 13.00;
- Widget OptionRequestPermissions = Obx(
- () => Row(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Radio(
- visualDensity: VisualDensity(horizontal: -4, vertical: -4),
- value: '',
- groupValue: groupValue.value,
- onChanged: onRadioChanged,
- ).marginOnly(right: 10),
- Expanded(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- InkWell(
- hoverColor: Colors.transparent,
- onTap: () => groupValue.value = '',
- child: Text(
- translate('Ask the remote user for authentication'),
- ),
- ).marginOnly(bottom: 10),
- Text(
- translate('Choose this if the remote account is administrator'),
- style: TextStyle(fontSize: fontSizeNote),
- ),
- ],
- ).marginOnly(top: 3),
- ),
- ],
- ),
- );
- Widget OptionCredentials = Obx(
- () => Row(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Radio(
- visualDensity: VisualDensity(horizontal: -4, vertical: -4),
- value: 'logon',
- groupValue: groupValue.value,
- onChanged: onRadioChanged,
- ).marginOnly(right: 10),
- Expanded(
- child: InkWell(
- hoverColor: Colors.transparent,
- onTap: () => onRadioChanged('logon'),
- child: Text(
- translate('Transmit the username and password of administrator'),
- ),
- ).marginOnly(top: 4),
- ),
- ],
- ),
- );
- Widget UacNote = Container(
- padding: EdgeInsets.fromLTRB(10, 8, 8, 8),
- decoration: BoxDecoration(
- color: MyTheme.currentThemeMode() == ThemeMode.dark
- ? Color.fromARGB(135, 87, 87, 90)
- : Colors.grey[100],
- borderRadius: BorderRadius.circular(8),
- border: Border.all(color: Colors.grey),
- ),
- child: Row(
- children: [
- Icon(Icons.info_outline_rounded, size: 20).marginOnly(right: 10),
- Expanded(
- child: Text(
- translate('still_click_uac_tip'),
- style: TextStyle(
- fontSize: fontSizeNote, fontWeight: FontWeight.normal),
- ),
- )
- ],
- ),
- );
- var content = Obx(
- () => Column(
- children: [
- OptionRequestPermissions.marginOnly(bottom: 15),
- OptionCredentials,
- Offstage(
- offstage: 'logon' != groupValue.value,
- child: Column(
- children: [
- UacNote.marginOnly(bottom: 10),
- DialogTextField(
- controller: userController,
- title: translate('Username'),
- hintText: translate('eg: admin'),
- prefixIcon: DialogTextField.kUsernameIcon,
- errorText: errUser.isEmpty ? null : errUser.value,
- ),
- PasswordWidget(
- controller: pwdController,
- autoFocus: false,
- errorText: errPwd.isEmpty ? null : errPwd.value,
- ),
- ],
- ).marginOnly(left: stateGlobal.isPortrait.isFalse ? 35 : 0),
- ).marginOnly(top: 10),
- ],
- ),
- );
- dialogManager.dismissAll();
- dialogManager.show(tag: '$sessionId-request-elevation',
- (setState, close, context) {
- void submit() {
- if (groupValue.value == 'logon') {
- if (userController.text.isEmpty) {
- errUser.value = translate('Empty Username');
- return;
- }
- if (pwdController.text.isEmpty) {
- errPwd.value = translate('Empty Password');
- return;
- }
- bind.sessionElevateWithLogon(
- sessionId: sessionId,
- username: userController.text,
- password: pwdController.text);
- } else {
- bind.sessionElevateDirect(sessionId: sessionId);
- }
- close();
- showWaitUacDialog(sessionId, dialogManager, "wait-uac");
- }
- return CustomAlertDialog(
- title: Text(translate('Request Elevation')),
- content: content,
- actions: [
- dialogButton(
- 'Cancel',
- icon: Icon(Icons.close_rounded),
- onPressed: close,
- isOutline: true,
- ),
- dialogButton(
- 'OK',
- icon: Icon(Icons.done_rounded),
- onPressed: submit,
- )
- ],
- onSubmit: submit,
- onCancel: close,
- );
- });
- }
- void showOnBlockDialog(
- SessionID sessionId,
- String type,
- String title,
- String text,
- OverlayDialogManager dialogManager,
- ) {
- if (dialogManager.existing('$sessionId-wait-uac') ||
- dialogManager.existing('$sessionId-request-elevation')) {
- return;
- }
- dialogManager.show(tag: '$sessionId-$type', (setState, close, context) {
- void submit() {
- close();
- showRequestElevationDialog(sessionId, dialogManager);
- }
- return CustomAlertDialog(
- title: null,
- content: msgboxContent(type, title,
- "${translate(text)}${type.contains('uac') ? '\n' : '\n\n'}${translate('request_elevation_tip')}"),
- actions: [
- dialogButton('Wait', onPressed: close, isOutline: true),
- dialogButton('Request Elevation', onPressed: submit),
- ],
- onSubmit: submit,
- onCancel: close,
- );
- });
- }
- void showElevationError(SessionID sessionId, String type, String title,
- String text, OverlayDialogManager dialogManager) {
- dialogManager.show(tag: '$sessionId-$type', (setState, close, context) {
- void submit() {
- close();
- showRequestElevationDialog(sessionId, dialogManager);
- }
- return CustomAlertDialog(
- title: null,
- content: msgboxContent(type, title, text),
- actions: [
- dialogButton('Cancel', onPressed: () {
- close();
- }, isOutline: true),
- if (text != 'No permission') dialogButton('Retry', onPressed: submit),
- ],
- onSubmit: submit,
- onCancel: close,
- );
- });
- }
- void showWaitAcceptDialog(SessionID sessionId, String type, String title,
- String text, OverlayDialogManager dialogManager) {
- dialogManager.dismissAll();
- dialogManager.show((setState, close, context) {
- onCancel() {
- closeConnection();
- }
- return CustomAlertDialog(
- title: null,
- content: msgboxContent(type, title, text),
- actions: [
- dialogButton('Cancel', onPressed: onCancel, isOutline: true),
- ],
- onCancel: onCancel,
- );
- });
- }
- void showRestartRemoteDevice(PeerInfo pi, String id, SessionID sessionId,
- OverlayDialogManager dialogManager) async {
- final res = await dialogManager
- .show<bool>((setState, close, context) => CustomAlertDialog(
- title: Row(children: [
- Icon(Icons.warning_rounded, color: Colors.redAccent, size: 28),
- Flexible(
- child: Text(translate("Restart remote device"))
- .paddingOnly(left: 10)),
- ]),
- content: Text(
- "${translate('Are you sure you want to restart')} \n${pi.username}@${pi.hostname}($id) ?"),
- actions: [
- dialogButton(
- "Cancel",
- icon: Icon(Icons.close_rounded),
- onPressed: close,
- isOutline: true,
- ),
- dialogButton(
- "OK",
- icon: Icon(Icons.done_rounded),
- onPressed: () => close(true),
- ),
- ],
- onCancel: close,
- onSubmit: () => close(true),
- ));
- if (res == true) bind.sessionRestartRemoteDevice(sessionId: sessionId);
- }
- showSetOSPassword(
- SessionID sessionId,
- bool login,
- OverlayDialogManager dialogManager,
- String? osPassword,
- Function()? closeCallback,
- ) async {
- final controller = TextEditingController();
- osPassword ??=
- await bind.sessionGetOption(sessionId: sessionId, arg: 'os-password') ??
- '';
- var autoLogin =
- await bind.sessionGetOption(sessionId: sessionId, arg: 'auto-login') !=
- '';
- controller.text = osPassword;
- dialogManager.show((setState, close, context) {
- closeWithCallback([dynamic]) {
- close();
- if (closeCallback != null) closeCallback();
- }
- submit() {
- var text = controller.text.trim();
- bind.sessionPeerOption(
- sessionId: sessionId, name: 'os-password', value: text);
- bind.sessionPeerOption(
- sessionId: sessionId,
- name: 'auto-login',
- value: autoLogin ? 'Y' : '');
- if (text != '' && login) {
- bind.sessionInputOsPassword(sessionId: sessionId, value: text);
- }
- closeWithCallback();
- }
- return CustomAlertDialog(
- title: Row(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- Icon(Icons.password_rounded, color: MyTheme.accent),
- Text(translate('OS Password')).paddingOnly(left: 10),
- ],
- ),
- content: Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- PasswordWidget(controller: controller),
- CheckboxListTile(
- contentPadding: const EdgeInsets.all(0),
- dense: true,
- controlAffinity: ListTileControlAffinity.leading,
- title: Text(
- translate('Auto Login'),
- ),
- value: autoLogin,
- onChanged: (v) {
- if (v == null) return;
- setState(() => autoLogin = v);
- },
- ),
- ],
- ),
- actions: [
- dialogButton(
- "Cancel",
- icon: Icon(Icons.close_rounded),
- onPressed: closeWithCallback,
- isOutline: true,
- ),
- dialogButton(
- "OK",
- icon: Icon(Icons.done_rounded),
- onPressed: submit,
- ),
- ],
- onSubmit: submit,
- onCancel: closeWithCallback,
- );
- });
- }
- showSetOSAccount(
- SessionID sessionId,
- OverlayDialogManager dialogManager,
- ) async {
- final usernameController = TextEditingController();
- final passwdController = TextEditingController();
- var username =
- await bind.sessionGetOption(sessionId: sessionId, arg: 'os-username') ??
- '';
- var password =
- await bind.sessionGetOption(sessionId: sessionId, arg: 'os-password') ??
- '';
- usernameController.text = username;
- passwdController.text = password;
- dialogManager.show((setState, close, context) {
- submit() {
- final username = usernameController.text.trim();
- final password = usernameController.text.trim();
- bind.sessionPeerOption(
- sessionId: sessionId, name: 'os-username', value: username);
- bind.sessionPeerOption(
- sessionId: sessionId, name: 'os-password', value: password);
- close();
- }
- descWidget(String text) {
- return Column(
- children: [
- Align(
- alignment: Alignment.centerLeft,
- child: Text(
- text,
- maxLines: 3,
- softWrap: true,
- overflow: TextOverflow.ellipsis,
- style: TextStyle(fontSize: 16),
- ),
- ),
- Container(
- height: 8,
- ),
- ],
- );
- }
- return CustomAlertDialog(
- title: Row(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- Icon(Icons.password_rounded, color: MyTheme.accent),
- Text(translate('OS Account')).paddingOnly(left: 10),
- ],
- ),
- content: Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- descWidget(translate("os_account_desk_tip")),
- DialogTextField(
- title: translate(DialogTextField.kUsernameTitle),
- controller: usernameController,
- prefixIcon: DialogTextField.kUsernameIcon,
- errorText: null,
- ),
- PasswordWidget(controller: passwdController),
- ],
- ),
- actions: [
- dialogButton(
- "Cancel",
- icon: Icon(Icons.close_rounded),
- onPressed: close,
- isOutline: true,
- ),
- dialogButton(
- "OK",
- icon: Icon(Icons.done_rounded),
- onPressed: submit,
- ),
- ],
- onSubmit: submit,
- onCancel: close,
- );
- });
- }
- showAuditDialog(FFI ffi) async {
- final controller = TextEditingController(text: ffi.auditNote);
- ffi.dialogManager.show((setState, close, context) {
- submit() {
- var text = controller.text;
- bind.sessionSendNote(sessionId: ffi.sessionId, note: text);
- ffi.auditNote = text;
- close();
- }
- late final focusNode = FocusNode(
- onKey: (FocusNode node, RawKeyEvent evt) {
- if (evt.logicalKey.keyLabel == 'Enter') {
- if (evt is RawKeyDownEvent) {
- int pos = controller.selection.base.offset;
- controller.text =
- '${controller.text.substring(0, pos)}\n${controller.text.substring(pos)}';
- controller.selection =
- TextSelection.fromPosition(TextPosition(offset: pos + 1));
- }
- return KeyEventResult.handled;
- }
- if (evt.logicalKey.keyLabel == 'Esc') {
- if (evt is RawKeyDownEvent) {
- close();
- }
- return KeyEventResult.handled;
- } else {
- return KeyEventResult.ignored;
- }
- },
- );
- return CustomAlertDialog(
- title: Text(translate('Note')),
- content: SizedBox(
- width: 250,
- height: 120,
- child: TextField(
- autofocus: true,
- keyboardType: TextInputType.multiline,
- textInputAction: TextInputAction.newline,
- decoration: const InputDecoration.collapsed(
- hintText: 'input note here',
- ),
- maxLines: null,
- maxLength: 256,
- controller: controller,
- focusNode: focusNode,
- ).workaroundFreezeLinuxMint()),
- actions: [
- dialogButton('Cancel', onPressed: close, isOutline: true),
- dialogButton('OK', onPressed: submit)
- ],
- onSubmit: submit,
- onCancel: close,
- );
- });
- }
- void showConfirmSwitchSidesDialog(
- SessionID sessionId, String id, OverlayDialogManager dialogManager) async {
- dialogManager.show((setState, close, context) {
- submit() async {
- await bind.sessionSwitchSides(sessionId: sessionId);
- closeConnection(id: id);
- }
- return CustomAlertDialog(
- content: msgboxContent('info', 'Switch Sides',
- 'Please confirm if you want to share your desktop?'),
- actions: [
- dialogButton('Cancel', onPressed: close, isOutline: true),
- dialogButton('OK', onPressed: submit),
- ],
- onSubmit: submit,
- onCancel: close,
- );
- });
- }
- customImageQualityDialog(SessionID sessionId, String id, FFI ffi) async {
- double initQuality = kDefaultQuality;
- double initFps = kDefaultFps;
- bool qualitySet = false;
- bool fpsSet = false;
- bool? direct;
- try {
- direct =
- ConnectionTypeState.find(id).direct.value == ConnectionType.strDirect;
- } catch (_) {}
- bool hideFps = (await bind.mainIsUsingPublicServer() && direct != true) ||
- versionCmp(ffi.ffiModel.pi.version, '1.2.0') < 0;
- bool hideMoreQuality =
- (await bind.mainIsUsingPublicServer() && direct != true) ||
- versionCmp(ffi.ffiModel.pi.version, '1.2.2') < 0;
- setCustomValues({double? quality, double? fps}) async {
- debugPrint("setCustomValues quality:$quality, fps:$fps");
- if (quality != null) {
- qualitySet = true;
- await bind.sessionSetCustomImageQuality(
- sessionId: sessionId, value: quality.toInt());
- }
- if (fps != null) {
- fpsSet = true;
- await bind.sessionSetCustomFps(sessionId: sessionId, fps: fps.toInt());
- }
- if (!qualitySet) {
- qualitySet = true;
- await bind.sessionSetCustomImageQuality(
- sessionId: sessionId, value: initQuality.toInt());
- }
- if (!hideFps && !fpsSet) {
- fpsSet = true;
- await bind.sessionSetCustomFps(
- sessionId: sessionId, fps: initFps.toInt());
- }
- }
- final btnClose = dialogButton('Close', onPressed: () async {
- await setCustomValues();
- ffi.dialogManager.dismissAll();
- });
- // quality
- final quality = await bind.sessionGetCustomImageQuality(sessionId: sessionId);
- initQuality = quality != null && quality.isNotEmpty
- ? quality[0].toDouble()
- : kDefaultQuality;
- if (initQuality < kMinQuality ||
- initQuality > (!hideMoreQuality ? kMaxMoreQuality : kMaxQuality)) {
- initQuality = kDefaultQuality;
- }
- // fps
- final fpsOption =
- await bind.sessionGetOption(sessionId: sessionId, arg: 'custom-fps');
- initFps = fpsOption == null
- ? kDefaultFps
- : double.tryParse(fpsOption) ?? kDefaultFps;
- if (initFps < kMinFps || initFps > kMaxFps) {
- initFps = kDefaultFps;
- }
- final content = customImageQualityWidget(
- initQuality: initQuality,
- initFps: initFps,
- setQuality: (v) => setCustomValues(quality: v),
- setFps: (v) => setCustomValues(fps: v),
- showFps: !hideFps,
- showMoreQuality: !hideMoreQuality);
- msgBoxCommon(ffi.dialogManager, 'Custom Image Quality', content, [btnClose]);
- }
- trackpadSpeedDialog(SessionID sessionId, FFI ffi) async {
- int initSpeed = ffi.inputModel.trackpadSpeed;
- final curSpeed = SimpleWrapper(initSpeed);
- final btnClose = dialogButton('Close', onPressed: () async {
- if (curSpeed.value <= kMaxTrackpadSpeed &&
- curSpeed.value >= kMinTrackpadSpeed &&
- curSpeed.value != initSpeed) {
- await bind.sessionSetTrackpadSpeed(
- sessionId: sessionId, value: curSpeed.value);
- await ffi.inputModel.updateTrackpadSpeed();
- }
- ffi.dialogManager.dismissAll();
- });
- msgBoxCommon(
- ffi.dialogManager,
- 'Trackpad speed',
- TrackpadSpeedWidget(
- value: curSpeed,
- ),
- [btnClose]);
- }
- void deleteConfirmDialog(Function onSubmit, String title) async {
- gFFI.dialogManager.show(
- (setState, close, context) {
- submit() async {
- await onSubmit();
- close();
- }
- return CustomAlertDialog(
- title: Row(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- Icon(
- Icons.delete_rounded,
- color: Colors.red,
- ),
- Expanded(
- child: Text(title, overflow: TextOverflow.ellipsis).paddingOnly(
- left: 10,
- ),
- ),
- ],
- ),
- content: SizedBox.shrink(),
- actions: [
- dialogButton(
- "Cancel",
- icon: Icon(Icons.close_rounded),
- onPressed: close,
- isOutline: true,
- ),
- dialogButton(
- "OK",
- icon: Icon(Icons.done_rounded),
- onPressed: submit,
- ),
- ],
- onSubmit: submit,
- onCancel: close,
- );
- },
- );
- }
- void editAbTagDialog(
- List<dynamic> currentTags, Function(List<dynamic>) onSubmit) {
- var isInProgress = false;
- final tags = List.of(gFFI.abModel.currentAbTags);
- var selectedTag = currentTags.obs;
- gFFI.dialogManager.show((setState, close, context) {
- submit() async {
- setState(() {
- isInProgress = true;
- });
- await onSubmit(selectedTag);
- close();
- }
- return CustomAlertDialog(
- title: Text(translate("Edit Tag")),
- content: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Container(
- padding: const EdgeInsets.symmetric(vertical: 8.0),
- child: Wrap(
- children: tags
- .map((e) => AddressBookTag(
- name: e,
- tags: selectedTag,
- onTap: () {
- if (selectedTag.contains(e)) {
- selectedTag.remove(e);
- } else {
- selectedTag.add(e);
- }
- },
- showActionMenu: false))
- .toList(growable: false),
- ),
- ),
- // NOT use Offstage to wrap LinearProgressIndicator
- if (isInProgress) const LinearProgressIndicator(),
- ],
- ),
- actions: [
- dialogButton("Cancel", onPressed: close, isOutline: true),
- dialogButton("OK", onPressed: submit),
- ],
- onSubmit: submit,
- onCancel: close,
- );
- });
- }
- void renameDialog(
- {required String oldName,
- FormFieldValidator<String>? validator,
- required ValueChanged<String> onSubmit,
- Function? onCancel}) async {
- RxBool isInProgress = false.obs;
- var controller = TextEditingController(text: oldName);
- final formKey = GlobalKey<FormState>();
- gFFI.dialogManager.show((setState, close, context) {
- submit() async {
- String text = controller.text.trim();
- if (validator != null && formKey.currentState?.validate() == false) {
- return;
- }
- isInProgress.value = true;
- onSubmit(text);
- close();
- isInProgress.value = false;
- }
- cancel() {
- onCancel?.call();
- close();
- }
- return CustomAlertDialog(
- title: Row(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- Icon(Icons.edit_rounded, color: MyTheme.accent),
- Text(translate('Rename')).paddingOnly(left: 10),
- ],
- ),
- content: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Container(
- child: Form(
- key: formKey,
- child: TextFormField(
- controller: controller,
- autofocus: true,
- decoration: InputDecoration(labelText: translate('Name')),
- validator: validator,
- ).workaroundFreezeLinuxMint(),
- ),
- ),
- // NOT use Offstage to wrap LinearProgressIndicator
- Obx(() =>
- isInProgress.value ? const LinearProgressIndicator() : Offstage())
- ],
- ),
- actions: [
- dialogButton(
- "Cancel",
- icon: Icon(Icons.close_rounded),
- onPressed: cancel,
- isOutline: true,
- ),
- dialogButton(
- "OK",
- icon: Icon(Icons.done_rounded),
- onPressed: submit,
- ),
- ],
- onSubmit: submit,
- onCancel: cancel,
- );
- });
- }
- void changeBot({Function()? callback}) async {
- if (bind.mainHasValidBotSync()) {
- await bind.mainSetOption(key: "bot", value: "");
- callback?.call();
- return;
- }
- String errorText = '';
- bool loading = false;
- final controller = TextEditingController();
- gFFI.dialogManager.show((setState, close, context) {
- onVerify() async {
- final token = controller.text.trim();
- if (token == "") return;
- loading = true;
- errorText = '';
- setState(() {});
- final error = await bind.mainVerifyBot(token: token);
- if (error == "") {
- callback?.call();
- close();
- } else {
- errorText = translate(error);
- loading = false;
- setState(() {});
- }
- }
- final codeField = TextField(
- autofocus: true,
- controller: controller,
- decoration: InputDecoration(
- hintText: translate('Token'),
- ),
- ).workaroundFreezeLinuxMint();
- return CustomAlertDialog(
- title: Text(translate("Telegram bot")),
- content: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- SelectableText(translate("enable-bot-desc"),
- style: TextStyle(fontSize: 12))
- .marginOnly(bottom: 12),
- Row(children: [Expanded(child: codeField)]),
- if (errorText != '')
- Text(errorText, style: TextStyle(color: Colors.red))
- .marginOnly(top: 12),
- ],
- ),
- actions: [
- dialogButton("Cancel", onPressed: close, isOutline: true),
- loading
- ? CircularProgressIndicator()
- : dialogButton("OK", onPressed: onVerify),
- ],
- onCancel: close,
- );
- });
- }
- void change2fa({Function()? callback}) async {
- if (bind.mainHasValid2FaSync()) {
- await bind.mainSetOption(key: "2fa", value: "");
- await bind.mainClearTrustedDevices();
- callback?.call();
- return;
- }
- var new2fa = (await bind.mainGenerate2Fa());
- final secretRegex = RegExp(r'secret=([^&]+)');
- final secret = secretRegex.firstMatch(new2fa)?.group(1);
- String? errorText;
- final controller = TextEditingController();
- gFFI.dialogManager.show((setState, close, context) {
- onVerify() async {
- if (await bind.mainVerify2Fa(code: controller.text.trim())) {
- callback?.call();
- close();
- } else {
- errorText = translate('wrong-2fa-code');
- }
- }
- final codeField = Dialog2FaField(
- controller: controller,
- errorText: errorText,
- onChanged: () => setState(() => errorText = null),
- title: translate('Verification code'),
- readyCallback: () {
- onVerify();
- setState(() {});
- },
- );
- getOnSubmit() => codeField.isReady ? onVerify : null;
- return CustomAlertDialog(
- title: Text(translate("enable-2fa-title")),
- content: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- SelectableText(translate("enable-2fa-desc"),
- style: TextStyle(fontSize: 12))
- .marginOnly(bottom: 12),
- SizedBox(
- width: 160,
- height: 160,
- child: QrImageView(
- backgroundColor: Colors.white,
- data: new2fa,
- version: QrVersions.auto,
- size: 160,
- gapless: false,
- )).marginOnly(bottom: 6),
- SelectableText(secret ?? '', style: TextStyle(fontSize: 12))
- .marginOnly(bottom: 12),
- Row(children: [Expanded(child: codeField)]),
- ],
- ),
- actions: [
- dialogButton("Cancel", onPressed: close, isOutline: true),
- dialogButton("OK", onPressed: getOnSubmit()),
- ],
- onCancel: close,
- );
- });
- }
- void enter2FaDialog(
- SessionID sessionId, OverlayDialogManager dialogManager) async {
- final controller = TextEditingController();
- final RxBool submitReady = false.obs;
- final RxBool trustThisDevice = false.obs;
- dialogManager.dismissAll();
- dialogManager.show((setState, close, context) {
- cancel() {
- close();
- closeConnection();
- }
- submit() {
- gFFI.send2FA(sessionId, controller.text.trim(), trustThisDevice.value);
- close();
- dialogManager.showLoading(translate('Logging in...'),
- onCancel: closeConnection);
- }
- late Dialog2FaField codeField;
- codeField = Dialog2FaField(
- controller: controller,
- title: translate('Verification code'),
- onChanged: () => submitReady.value = codeField.isReady,
- );
- final trustField = Obx(() => CheckboxListTile(
- contentPadding: const EdgeInsets.all(0),
- dense: true,
- controlAffinity: ListTileControlAffinity.leading,
- title: Text(translate("Trust this device")),
- value: trustThisDevice.value,
- onChanged: (value) {
- if (value == null) return;
- trustThisDevice.value = value;
- },
- ));
- return CustomAlertDialog(
- title: Text(translate('enter-2fa-title')),
- content: Column(
- children: [
- codeField,
- if (bind.sessionGetEnableTrustedDevices(sessionId: sessionId))
- trustField,
- ],
- ),
- actions: [
- dialogButton('Cancel',
- onPressed: cancel,
- isOutline: true,
- style: TextStyle(
- color: Theme.of(context).textTheme.bodyMedium?.color)),
- Obx(() => dialogButton(
- 'OK',
- onPressed: submitReady.isTrue ? submit : null,
- )),
- ],
- onSubmit: submit,
- onCancel: cancel);
- });
- }
- // This dialog should not be dismissed, otherwise it will be black screen, have not reproduced this.
- void showWindowsSessionsDialog(
- String type,
- String title,
- String text,
- OverlayDialogManager dialogManager,
- SessionID sessionId,
- String peerId,
- String sessions) {
- List<dynamic> sessionsList = [];
- try {
- sessionsList = json.decode(sessions);
- } catch (e) {
- print(e);
- }
- List<String> sids = [];
- List<String> names = [];
- for (var session in sessionsList) {
- sids.add(session['sid']);
- names.add(session['name']);
- }
- String selectedUserValue = sids.first;
- dialogManager.dismissAll();
- dialogManager.show((setState, close, context) {
- submit() {
- bind.sessionSendSelectedSessionId(
- sessionId: sessionId, sid: selectedUserValue);
- close();
- }
- return CustomAlertDialog(
- title: null,
- content: msgboxContent(type, title, text),
- actions: [
- ComboBox(
- keys: sids,
- values: names,
- initialKey: selectedUserValue,
- onChanged: (value) {
- selectedUserValue = value;
- }),
- dialogButton('Connect', onPressed: submit, isOutline: false),
- ],
- );
- });
- }
- void addPeersToAbDialog(
- List<Peer> peers,
- ) async {
- Future<bool> addTo(String abname) async {
- final mapList = peers.map((e) {
- var json = e.toJson();
- // remove password when add to another address book to avoid re-share
- json.remove('password');
- json.remove('hash');
- return json;
- }).toList();
- final errMsg = await gFFI.abModel.addPeersTo(mapList, abname);
- if (errMsg == null) {
- showToast(translate('Successful'));
- return true;
- } else {
- BotToast.showText(text: errMsg, contentColor: Colors.red);
- return false;
- }
- }
- // if only one address book and it is personal, add to it directly
- if (gFFI.abModel.addressbooks.length == 1 &&
- gFFI.abModel.current.isPersonal()) {
- await addTo(gFFI.abModel.currentName.value);
- return;
- }
- RxBool isInProgress = false.obs;
- final names = gFFI.abModel.addressBooksCanWrite();
- RxString currentName = gFFI.abModel.currentName.value.obs;
- TextEditingController controller = TextEditingController();
- if (gFFI.peerTabModel.currentTab == PeerTabIndex.ab.index) {
- names.remove(currentName.value);
- }
- if (names.isEmpty) {
- debugPrint('no address book to add peers to, should not happen');
- return;
- }
- if (!names.contains(currentName.value)) {
- currentName.value = names[0];
- }
- gFFI.dialogManager.show((setState, close, context) {
- submit() async {
- if (controller.text != gFFI.abModel.translatedName(currentName.value)) {
- BotToast.showText(
- text: 'illegal address book name: ${controller.text}',
- contentColor: Colors.red);
- return;
- }
- isInProgress.value = true;
- if (await addTo(currentName.value)) {
- close();
- }
- isInProgress.value = false;
- }
- cancel() {
- close();
- }
- return CustomAlertDialog(
- title: Row(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- Icon(IconFont.addressBook, color: MyTheme.accent),
- Text(translate('Add to address book')).paddingOnly(left: 10),
- ],
- ),
- content: Obx(() => Column(
- crossAxisAlignment: CrossAxisAlignment.center,
- children: [
- // https://github.com/flutter/flutter/issues/145081
- DropdownMenu(
- initialSelection: currentName.value,
- onSelected: (value) {
- if (value != null) {
- currentName.value = value;
- }
- },
- dropdownMenuEntries: names
- .map((e) => DropdownMenuEntry(
- value: e, label: gFFI.abModel.translatedName(e)))
- .toList(),
- inputDecorationTheme: InputDecorationTheme(
- isDense: true, border: UnderlineInputBorder()),
- enableFilter: true,
- controller: controller,
- ),
- // NOT use Offstage to wrap LinearProgressIndicator
- isInProgress.value ? const LinearProgressIndicator() : Offstage()
- ],
- )),
- actions: [
- dialogButton(
- "Cancel",
- icon: Icon(Icons.close_rounded),
- onPressed: cancel,
- isOutline: true,
- ),
- dialogButton(
- "OK",
- icon: Icon(Icons.done_rounded),
- onPressed: submit,
- ),
- ],
- onSubmit: submit,
- onCancel: cancel,
- );
- });
- }
- void setSharedAbPasswordDialog(String abName, Peer peer) {
- TextEditingController controller = TextEditingController(text: '');
- RxBool isInProgress = false.obs;
- RxBool isInputEmpty = true.obs;
- bool passwordVisible = false;
- controller.addListener(() {
- isInputEmpty.value = controller.text.isEmpty;
- });
- gFFI.dialogManager.show((setState, close, context) {
- change(String password) async {
- isInProgress.value = true;
- bool res =
- await gFFI.abModel.changeSharedPassword(abName, peer.id, password);
- isInProgress.value = false;
- if (res) {
- showToast(translate('Successful'));
- }
- close();
- }
- cancel() {
- close();
- }
- return CustomAlertDialog(
- title: Row(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- Icon(Icons.key, color: MyTheme.accent),
- Text(translate(peer.password.isEmpty
- ? 'Set shared password'
- : 'Change Password'))
- .paddingOnly(left: 10),
- ],
- ),
- content: Obx(() => Column(children: [
- TextField(
- controller: controller,
- autofocus: true,
- obscureText: !passwordVisible,
- decoration: InputDecoration(
- suffixIcon: IconButton(
- icon: Icon(
- passwordVisible ? Icons.visibility : Icons.visibility_off,
- color: MyTheme.lightTheme.primaryColor),
- onPressed: () {
- setState(() {
- passwordVisible = !passwordVisible;
- });
- },
- ),
- ),
- ).workaroundFreezeLinuxMint(),
- if (!gFFI.abModel.current.isPersonal())
- Row(children: [
- Icon(Icons.info, color: Colors.amber).marginOnly(right: 4),
- Text(
- translate('share_warning_tip'),
- style: TextStyle(fontSize: 12),
- )
- ]).marginSymmetric(vertical: 10),
- // NOT use Offstage to wrap LinearProgressIndicator
- isInProgress.value ? const LinearProgressIndicator() : Offstage()
- ])),
- actions: [
- dialogButton(
- "Cancel",
- icon: Icon(Icons.close_rounded),
- onPressed: cancel,
- isOutline: true,
- ),
- if (peer.password.isNotEmpty)
- dialogButton(
- "Remove",
- icon: Icon(Icons.delete_outline_rounded),
- onPressed: () => change(''),
- buttonStyle: ButtonStyle(
- backgroundColor: MaterialStatePropertyAll(Colors.red)),
- ),
- Obx(() => dialogButton(
- "OK",
- icon: Icon(Icons.done_rounded),
- onPressed:
- isInputEmpty.value ? null : () => change(controller.text),
- )),
- ],
- onSubmit: isInputEmpty.value ? null : () => change(controller.text),
- onCancel: cancel,
- );
- });
- }
- void CommonConfirmDialog(OverlayDialogManager dialogManager, String content,
- VoidCallback onConfirm) {
- dialogManager.show((setState, close, context) {
- submit() {
- close();
- onConfirm.call();
- }
- return CustomAlertDialog(
- content: Row(
- children: [
- Expanded(
- child: Text(content,
- style: const TextStyle(fontSize: 15),
- textAlign: TextAlign.start),
- ),
- ],
- ).marginOnly(bottom: 12),
- actions: [
- dialogButton(translate("Cancel"), onPressed: close, isOutline: true),
- dialogButton(translate("OK"), onPressed: submit),
- ],
- onSubmit: submit,
- onCancel: close,
- );
- });
- }
- void changeUnlockPinDialog(String oldPin, Function() callback) {
- final pinController = TextEditingController(text: oldPin);
- final confirmController = TextEditingController(text: oldPin);
- String? pinErrorText;
- String? confirmationErrorText;
- final maxLength = bind.mainMaxEncryptLen();
- gFFI.dialogManager.show((setState, close, context) {
- submit() async {
- pinErrorText = null;
- confirmationErrorText = null;
- final pin = pinController.text.trim();
- final confirm = confirmController.text.trim();
- if (pin != confirm) {
- setState(() {
- confirmationErrorText =
- translate('The confirmation is not identical.');
- });
- return;
- }
- final errorMsg = bind.mainSetUnlockPin(pin: pin);
- if (errorMsg != '') {
- setState(() {
- pinErrorText = translate(errorMsg);
- });
- return;
- }
- callback.call();
- close();
- }
- return CustomAlertDialog(
- title: Text(translate("Set PIN")),
- content: Column(
- children: [
- DialogTextField(
- title: 'PIN',
- controller: pinController,
- obscureText: true,
- errorText: pinErrorText,
- maxLength: maxLength,
- ),
- DialogTextField(
- title: translate('Confirmation'),
- controller: confirmController,
- obscureText: true,
- errorText: confirmationErrorText,
- maxLength: maxLength,
- )
- ],
- ).marginOnly(bottom: 12),
- actions: [
- dialogButton(translate("Cancel"), onPressed: close, isOutline: true),
- dialogButton(translate("OK"), onPressed: submit),
- ],
- onSubmit: submit,
- onCancel: close,
- );
- });
- }
- void checkUnlockPinDialog(String correctPin, Function() passCallback) {
- final controller = TextEditingController();
- String? errorText;
- gFFI.dialogManager.show((setState, close, context) {
- submit() async {
- final pin = controller.text.trim();
- if (correctPin != pin) {
- setState(() {
- errorText = translate('Wrong PIN');
- });
- return;
- }
- passCallback.call();
- close();
- }
- return CustomAlertDialog(
- content: Row(
- children: [
- Expanded(
- child: PasswordWidget(
- title: 'PIN',
- controller: controller,
- errorText: errorText,
- hintText: '',
- ))
- ],
- ).marginOnly(bottom: 12),
- actions: [
- dialogButton(translate("Cancel"), onPressed: close, isOutline: true),
- dialogButton(translate("OK"), onPressed: submit),
- ],
- onSubmit: submit,
- onCancel: close,
- );
- });
- }
- void confrimDeleteTrustedDevicesDialog(
- RxList<TrustedDevice> trustedDevices, RxList<Uint8List> selectedDevices) {
- CommonConfirmDialog(gFFI.dialogManager, '${translate('Confirm Delete')}?',
- () async {
- if (selectedDevices.isEmpty) return;
- if (selectedDevices.length == trustedDevices.length) {
- await bind.mainClearTrustedDevices();
- trustedDevices.clear();
- selectedDevices.clear();
- } else {
- final json = jsonEncode(selectedDevices.map((e) => e.toList()).toList());
- await bind.mainRemoveTrustedDevices(json: json);
- trustedDevices.removeWhere((element) {
- return selectedDevices.contains(element.hwid);
- });
- selectedDevices.clear();
- }
- });
- }
- void manageTrustedDeviceDialog() async {
- RxList<TrustedDevice> trustedDevices = (await TrustedDevice.get()).obs;
- RxList<Uint8List> selectedDevices = RxList.empty();
- gFFI.dialogManager.show((setState, close, context) {
- return CustomAlertDialog(
- title: Text(translate("Manage trusted devices")),
- content: trustedDevicesTable(trustedDevices, selectedDevices),
- actions: [
- Obx(() => dialogButton(translate("Delete"),
- onPressed: selectedDevices.isEmpty
- ? null
- : () {
- confrimDeleteTrustedDevicesDialog(
- trustedDevices,
- selectedDevices,
- );
- },
- isOutline: false)
- .marginOnly(top: 12)),
- dialogButton(translate("Close"), onPressed: close, isOutline: true)
- .marginOnly(top: 12),
- ],
- onCancel: close,
- );
- });
- }
- class TrustedDevice {
- late final Uint8List hwid;
- late final int time;
- late final String id;
- late final String name;
- late final String platform;
- TrustedDevice.fromJson(Map<String, dynamic> json) {
- final hwidList = json['hwid'] as List<dynamic>;
- hwid = Uint8List.fromList(hwidList.cast<int>());
- time = json['time'];
- id = json['id'];
- name = json['name'];
- platform = json['platform'];
- }
- String daysRemaining() {
- final expiry = time + 90 * 24 * 60 * 60 * 1000;
- final remaining = expiry - DateTime.now().millisecondsSinceEpoch;
- if (remaining < 0) {
- return '0';
- }
- return (remaining / (24 * 60 * 60 * 1000)).toStringAsFixed(0);
- }
- static Future<List<TrustedDevice>> get() async {
- final List<TrustedDevice> devices = List.empty(growable: true);
- try {
- final devicesJson = await bind.mainGetTrustedDevices();
- if (devicesJson.isNotEmpty) {
- final devicesList = json.decode(devicesJson);
- if (devicesList is List) {
- for (var device in devicesList) {
- devices.add(TrustedDevice.fromJson(device));
- }
- }
- }
- } catch (e) {
- print(e.toString());
- }
- devices.sort((a, b) => b.time.compareTo(a.time));
- return devices;
- }
- }
- Widget trustedDevicesTable(
- RxList<TrustedDevice> devices, RxList<Uint8List> selectedDevices) {
- RxBool selectAll = false.obs;
- setSelectAll() {
- if (selectedDevices.isNotEmpty &&
- selectedDevices.length == devices.length) {
- selectAll.value = true;
- } else {
- selectAll.value = false;
- }
- }
- devices.listen((_) {
- setSelectAll();
- });
- selectedDevices.listen((_) {
- setSelectAll();
- });
- return FittedBox(
- child: Obx(() => DataTable(
- columns: [
- DataColumn(
- label: Checkbox(
- value: selectAll.value,
- onChanged: (value) {
- if (value == true) {
- selectedDevices.clear();
- selectedDevices.addAll(devices.map((e) => e.hwid));
- } else {
- selectedDevices.clear();
- }
- },
- )),
- DataColumn(label: Text(translate('Platform'))),
- DataColumn(label: Text(translate('ID'))),
- DataColumn(label: Text(translate('Username'))),
- DataColumn(label: Text(translate('Days remaining'))),
- ],
- rows: devices.map((device) {
- return DataRow(cells: [
- DataCell(Checkbox(
- value: selectedDevices.contains(device.hwid),
- onChanged: (value) {
- if (value == null) return;
- if (value) {
- selectedDevices.remove(device.hwid);
- selectedDevices.add(device.hwid);
- } else {
- selectedDevices.remove(device.hwid);
- }
- },
- )),
- DataCell(Text(device.platform)),
- DataCell(Text(device.id)),
- DataCell(Text(device.name)),
- DataCell(Text(device.daysRemaining())),
- ]);
- }).toList(),
- )),
- );
- }
|