Merge pull request #2621 from Heap-Hop/master
refactor import/export id server config
This commit is contained in:
commit
aa2837dd39
@ -1510,3 +1510,53 @@ Pointer<win32.OSVERSIONINFOEX> getOSVERSIONINFOEXPointer() {
|
|||||||
bool get kUseCompatibleUiMode =>
|
bool get kUseCompatibleUiMode =>
|
||||||
Platform.isWindows &&
|
Platform.isWindows &&
|
||||||
const [WindowsTarget.w7].contains(windowsBuildNumber.windowsVersion);
|
const [WindowsTarget.w7].contains(windowsBuildNumber.windowsVersion);
|
||||||
|
|
||||||
|
class ServerConfig {
|
||||||
|
late String idServer;
|
||||||
|
late String relayServer;
|
||||||
|
late String apiServer;
|
||||||
|
late String key;
|
||||||
|
|
||||||
|
ServerConfig(
|
||||||
|
{String? idServer, String? relayServer, String? apiServer, String? key}) {
|
||||||
|
this.idServer = idServer?.trim() ?? '';
|
||||||
|
this.relayServer = relayServer?.trim() ?? '';
|
||||||
|
this.apiServer = apiServer?.trim() ?? '';
|
||||||
|
this.key = key?.trim() ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/// decode from shared string (from user shared or rustdesk-server generated)
|
||||||
|
/// also see [encode]
|
||||||
|
/// throw when decoding failure
|
||||||
|
ServerConfig.decode(String msg) {
|
||||||
|
final input = msg.split('').reversed.join('');
|
||||||
|
final bytes = base64Decode(base64.normalize(input));
|
||||||
|
final json = jsonDecode(utf8.decode(bytes));
|
||||||
|
|
||||||
|
idServer = json['host'] ?? '';
|
||||||
|
relayServer = json['relay'] ?? '';
|
||||||
|
apiServer = json['api'] ?? '';
|
||||||
|
key = json['key'] ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/// encode to shared string
|
||||||
|
/// also see [ServerConfig.decode]
|
||||||
|
String encode() {
|
||||||
|
Map<String, String> config = {};
|
||||||
|
config['host'] = idServer.trim();
|
||||||
|
config['relay'] = relayServer.trim();
|
||||||
|
config['api'] = apiServer.trim();
|
||||||
|
config['key'] = key.trim();
|
||||||
|
return base64Encode(Uint8List.fromList(jsonEncode(config).codeUnits))
|
||||||
|
.split('')
|
||||||
|
.reversed
|
||||||
|
.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// from local options
|
||||||
|
ServerConfig.fromOptions(Map<String, dynamic> options)
|
||||||
|
: idServer = options['custom-rendezvous-server'] ?? "",
|
||||||
|
relayServer = options['relay-server'] ?? "",
|
||||||
|
apiServer = options['api-server'] ?? "",
|
||||||
|
key = options['key'] ?? "";
|
||||||
|
}
|
||||||
|
@ -958,23 +958,17 @@ class _NetworkState extends State<_Network> with AutomaticKeepAliveClientMixin {
|
|||||||
|
|
||||||
import() {
|
import() {
|
||||||
Clipboard.getData(Clipboard.kTextPlain).then((value) {
|
Clipboard.getData(Clipboard.kTextPlain).then((value) {
|
||||||
TextEditingController mytext = TextEditingController();
|
final text = value?.text;
|
||||||
String? aNullableString = '';
|
if (text != null && text.isNotEmpty) {
|
||||||
aNullableString = value?.text;
|
|
||||||
mytext.text = aNullableString.toString();
|
|
||||||
if (mytext.text.isNotEmpty) {
|
|
||||||
try {
|
try {
|
||||||
Map<String, dynamic> config = jsonDecode(mytext.text);
|
final sc = ServerConfig.decode(text);
|
||||||
if (config.containsKey('IdServer')) {
|
if (sc.idServer.isNotEmpty) {
|
||||||
String id = config['IdServer'] ?? '';
|
idController.text = sc.idServer;
|
||||||
String relay = config['RelayServer'] ?? '';
|
relayController.text = sc.relayServer;
|
||||||
String api = config['ApiServer'] ?? '';
|
apiController.text = sc.apiServer;
|
||||||
String key = config['Key'] ?? '';
|
keyController.text = sc.key;
|
||||||
idController.text = id;
|
Future<bool> success =
|
||||||
relayController.text = relay;
|
set(sc.idServer, sc.relayServer, sc.apiServer, sc.key);
|
||||||
apiController.text = api;
|
|
||||||
keyController.text = key;
|
|
||||||
Future<bool> success = set(id, relay, api, key);
|
|
||||||
success.then((value) {
|
success.then((value) {
|
||||||
if (value) {
|
if (value) {
|
||||||
showToast(
|
showToast(
|
||||||
@ -996,12 +990,15 @@ class _NetworkState extends State<_Network> with AutomaticKeepAliveClientMixin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export() {
|
export() {
|
||||||
Map<String, String> config = {};
|
final text = ServerConfig(
|
||||||
config['IdServer'] = idController.text.trim();
|
idServer: idController.text,
|
||||||
config['RelayServer'] = relayController.text.trim();
|
relayServer: relayController.text,
|
||||||
config['ApiServer'] = apiController.text.trim();
|
apiServer: apiController.text,
|
||||||
config['Key'] = keyController.text.trim();
|
key: keyController.text)
|
||||||
Clipboard.setData(ClipboardData(text: jsonEncode(config)));
|
.encode();
|
||||||
|
debugPrint("ServerConfig export: $text");
|
||||||
|
|
||||||
|
Clipboard.setData(ClipboardData(text: text));
|
||||||
showToast(translate('Export server configuration successfully'));
|
showToast(translate('Export server configuration successfully'));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1106,8 +1103,10 @@ class _AboutState extends State<_About> {
|
|||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 8.0,
|
height: 8.0,
|
||||||
),
|
),
|
||||||
Text(translate('Version') + ': $version').marginSymmetric(vertical: 4.0),
|
Text(translate('Version') + ': $version')
|
||||||
Text(translate('Build Date') + ': $buildDate').marginSymmetric(vertical: 4.0),
|
.marginSymmetric(vertical: 4.0),
|
||||||
|
Text(translate('Build Date') + ': $buildDate')
|
||||||
|
.marginSymmetric(vertical: 4.0),
|
||||||
InkWell(
|
InkWell(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
launchUrlString('https://rustdesk.com/privacy');
|
launchUrlString('https://rustdesk.com/privacy');
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@ -9,11 +8,11 @@ import 'package:qr_code_scanner/qr_code_scanner.dart';
|
|||||||
import 'package:zxing2/qrcode.dart';
|
import 'package:zxing2/qrcode.dart';
|
||||||
|
|
||||||
import '../../common.dart';
|
import '../../common.dart';
|
||||||
import '../../models/platform_model.dart';
|
import '../widgets/dialog.dart';
|
||||||
|
|
||||||
class ScanPage extends StatefulWidget {
|
class ScanPage extends StatefulWidget {
|
||||||
@override
|
@override
|
||||||
_ScanPageState createState() => _ScanPageState();
|
State<ScanPage> createState() => _ScanPageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ScanPageState extends State<ScanPage> {
|
class _ScanPageState extends State<ScanPage> {
|
||||||
@ -42,9 +41,9 @@ class _ScanPageState extends State<ScanPage> {
|
|||||||
icon: Icon(Icons.image_search),
|
icon: Icon(Icons.image_search),
|
||||||
iconSize: 32.0,
|
iconSize: 32.0,
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
final ImagePicker _picker = ImagePicker();
|
final ImagePicker picker = ImagePicker();
|
||||||
final XFile? file =
|
final XFile? file =
|
||||||
await _picker.pickImage(source: ImageSource.gallery);
|
await picker.pickImage(source: ImageSource.gallery);
|
||||||
if (file != null) {
|
if (file != null) {
|
||||||
var image = img.decodeNamedImage(
|
var image = img.decodeNamedImage(
|
||||||
File(file.path).readAsBytesSync(), file.path)!;
|
File(file.path).readAsBytesSync(), file.path)!;
|
||||||
@ -139,158 +138,12 @@ class _ScanPageState extends State<ScanPage> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
Map<String, dynamic> values = json.decode(data.substring(7));
|
final sc = ServerConfig.decode(data.substring(7));
|
||||||
var host = values['host'] != null ? values['host'] as String : '';
|
|
||||||
var key = values['key'] != null ? values['key'] as String : '';
|
|
||||||
var api = values['api'] != null ? values['api'] as String : '';
|
|
||||||
Timer(Duration(milliseconds: 60), () {
|
Timer(Duration(milliseconds: 60), () {
|
||||||
showServerSettingsWithValue(host, '', key, api, gFFI.dialogManager);
|
showServerSettingsWithValue(sc, gFFI.dialogManager);
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
showToast('Invalid QR code');
|
showToast('Invalid QR code');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void showServerSettingsWithValue(String id, String relay, String key,
|
|
||||||
String api, OverlayDialogManager dialogManager) async {
|
|
||||||
Map<String, dynamic> oldOptions = jsonDecode(await bind.mainGetOptions());
|
|
||||||
String id0 = oldOptions['custom-rendezvous-server'] ?? "";
|
|
||||||
String relay0 = oldOptions['relay-server'] ?? "";
|
|
||||||
String api0 = oldOptions['api-server'] ?? "";
|
|
||||||
String key0 = oldOptions['key'] ?? "";
|
|
||||||
var isInProgress = false;
|
|
||||||
final idController = TextEditingController(text: id);
|
|
||||||
final relayController = TextEditingController(text: relay);
|
|
||||||
final apiController = TextEditingController(text: api);
|
|
||||||
|
|
||||||
String? idServerMsg;
|
|
||||||
String? relayServerMsg;
|
|
||||||
String? apiServerMsg;
|
|
||||||
|
|
||||||
dialogManager.show((setState, close) {
|
|
||||||
Future<bool> validate() async {
|
|
||||||
if (idController.text != id) {
|
|
||||||
final res = await validateAsync(idController.text);
|
|
||||||
setState(() => idServerMsg = res);
|
|
||||||
if (idServerMsg != null) return false;
|
|
||||||
id = idController.text;
|
|
||||||
}
|
|
||||||
if (relayController.text != relay) {
|
|
||||||
relayServerMsg = await validateAsync(relayController.text);
|
|
||||||
if (relayServerMsg != null) return false;
|
|
||||||
relay = relayController.text;
|
|
||||||
}
|
|
||||||
if (apiController.text != relay) {
|
|
||||||
apiServerMsg = await validateAsync(apiController.text);
|
|
||||||
if (apiServerMsg != null) return false;
|
|
||||||
api = apiController.text;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return CustomAlertDialog(
|
|
||||||
title: Text(translate('ID/Relay Server')),
|
|
||||||
content: Form(
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: <Widget>[
|
|
||||||
TextFormField(
|
|
||||||
controller: idController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: translate('ID Server'),
|
|
||||||
errorText: idServerMsg),
|
|
||||||
)
|
|
||||||
] +
|
|
||||||
(isAndroid
|
|
||||||
? [
|
|
||||||
TextFormField(
|
|
||||||
controller: relayController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: translate('Relay Server'),
|
|
||||||
errorText: relayServerMsg),
|
|
||||||
)
|
|
||||||
]
|
|
||||||
: []) +
|
|
||||||
[
|
|
||||||
TextFormField(
|
|
||||||
controller: apiController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: translate('API Server'),
|
|
||||||
),
|
|
||||||
autovalidateMode: AutovalidateMode.onUserInteraction,
|
|
||||||
validator: (v) {
|
|
||||||
if (v != null && v.length > 0) {
|
|
||||||
if (!(v.startsWith('http://') ||
|
|
||||||
v.startsWith("https://"))) {
|
|
||||||
return translate("invalid_http");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return apiServerMsg;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
TextFormField(
|
|
||||||
initialValue: key,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'Key',
|
|
||||||
),
|
|
||||||
onChanged: (String? value) {
|
|
||||||
if (value != null) key = value.trim();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
Offstage(
|
|
||||||
offstage: !isInProgress,
|
|
||||||
child: LinearProgressIndicator())
|
|
||||||
])),
|
|
||||||
actions: [
|
|
||||||
TextButton(
|
|
||||||
style: flatButtonStyle,
|
|
||||||
onPressed: () {
|
|
||||||
close();
|
|
||||||
},
|
|
||||||
child: Text(translate('Cancel')),
|
|
||||||
),
|
|
||||||
TextButton(
|
|
||||||
style: flatButtonStyle,
|
|
||||||
onPressed: () async {
|
|
||||||
setState(() {
|
|
||||||
idServerMsg = null;
|
|
||||||
relayServerMsg = null;
|
|
||||||
apiServerMsg = null;
|
|
||||||
isInProgress = true;
|
|
||||||
});
|
|
||||||
if (await validate()) {
|
|
||||||
if (id != id0) {
|
|
||||||
if (id0.isNotEmpty) {
|
|
||||||
await gFFI.userModel.logOut();
|
|
||||||
}
|
|
||||||
bind.mainSetOption(key: "custom-rendezvous-server", value: id);
|
|
||||||
}
|
|
||||||
if (relay != relay0) {
|
|
||||||
bind.mainSetOption(key: "relay-server", value: relay);
|
|
||||||
}
|
|
||||||
if (key != key0) bind.mainSetOption(key: "key", value: key);
|
|
||||||
if (api != api0) {
|
|
||||||
bind.mainSetOption(key: "api-server", value: api);
|
|
||||||
}
|
|
||||||
close();
|
|
||||||
}
|
|
||||||
setState(() {
|
|
||||||
isInProgress = false;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
child: Text(translate('OK')),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<String?> validateAsync(String value) async {
|
|
||||||
value = value.trim();
|
|
||||||
if (value.isEmpty) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
final res = await bind.mainTestIfValidServer(server: value);
|
|
||||||
return res.isEmpty ? null : res;
|
|
||||||
}
|
|
||||||
|
@ -391,11 +391,7 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
|
|||||||
|
|
||||||
void showServerSettings(OverlayDialogManager dialogManager) async {
|
void showServerSettings(OverlayDialogManager dialogManager) async {
|
||||||
Map<String, dynamic> options = jsonDecode(await bind.mainGetOptions());
|
Map<String, dynamic> options = jsonDecode(await bind.mainGetOptions());
|
||||||
String id = options['custom-rendezvous-server'] ?? "";
|
showServerSettingsWithValue(ServerConfig.fromOptions(options), dialogManager);
|
||||||
String relay = options['relay-server'] ?? "";
|
|
||||||
String api = options['api-server'] ?? "";
|
|
||||||
String key = options['key'] ?? "";
|
|
||||||
showServerSettingsWithValue(id, relay, key, api, dialogManager);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void showLanguageSettings(OverlayDialogManager dialogManager) async {
|
void showLanguageSettings(OverlayDialogManager dialogManager) async {
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import '../../common.dart';
|
import '../../common.dart';
|
||||||
@ -236,6 +237,145 @@ void wrongPasswordDialog(String id, OverlayDialogManager dialogManager) {
|
|||||||
]));
|
]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void showServerSettingsWithValue(
|
||||||
|
ServerConfig serverConfig, OverlayDialogManager dialogManager) async {
|
||||||
|
Map<String, dynamic> oldOptions = jsonDecode(await bind.mainGetOptions());
|
||||||
|
final oldCfg = ServerConfig.fromOptions(oldOptions);
|
||||||
|
|
||||||
|
var isInProgress = false;
|
||||||
|
final idCtrl = TextEditingController(text: serverConfig.idServer);
|
||||||
|
final relayCtrl = TextEditingController(text: serverConfig.relayServer);
|
||||||
|
final apiCtrl = TextEditingController(text: serverConfig.apiServer);
|
||||||
|
final keyCtrl = TextEditingController(text: serverConfig.key);
|
||||||
|
|
||||||
|
String? idServerMsg;
|
||||||
|
String? relayServerMsg;
|
||||||
|
String? apiServerMsg;
|
||||||
|
|
||||||
|
dialogManager.show((setState, close) {
|
||||||
|
Future<bool> validate() async {
|
||||||
|
if (idCtrl.text != oldCfg.idServer) {
|
||||||
|
final res = await validateAsync(idCtrl.text);
|
||||||
|
setState(() => idServerMsg = res);
|
||||||
|
if (idServerMsg != null) return false;
|
||||||
|
}
|
||||||
|
if (relayCtrl.text != oldCfg.relayServer) {
|
||||||
|
relayServerMsg = await validateAsync(relayCtrl.text);
|
||||||
|
if (relayServerMsg != null) return false;
|
||||||
|
}
|
||||||
|
if (apiCtrl.text != oldCfg.apiServer) {
|
||||||
|
apiServerMsg = await validateAsync(apiCtrl.text);
|
||||||
|
if (apiServerMsg != null) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return CustomAlertDialog(
|
||||||
|
title: Text(translate('ID/Relay Server')),
|
||||||
|
content: Form(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: <Widget>[
|
||||||
|
TextFormField(
|
||||||
|
controller: idCtrl,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: translate('ID Server'),
|
||||||
|
errorText: idServerMsg),
|
||||||
|
)
|
||||||
|
] +
|
||||||
|
(isAndroid
|
||||||
|
? [
|
||||||
|
TextFormField(
|
||||||
|
controller: relayCtrl,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: translate('Relay Server'),
|
||||||
|
errorText: relayServerMsg),
|
||||||
|
)
|
||||||
|
]
|
||||||
|
: []) +
|
||||||
|
[
|
||||||
|
TextFormField(
|
||||||
|
controller: apiCtrl,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: translate('API Server'),
|
||||||
|
),
|
||||||
|
autovalidateMode: AutovalidateMode.onUserInteraction,
|
||||||
|
validator: (v) {
|
||||||
|
if (v != null && v.isNotEmpty) {
|
||||||
|
if (!(v.startsWith('http://') ||
|
||||||
|
v.startsWith("https://"))) {
|
||||||
|
return translate("invalid_http");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return apiServerMsg;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
TextFormField(
|
||||||
|
controller: keyCtrl,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'Key',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Offstage(
|
||||||
|
offstage: !isInProgress,
|
||||||
|
child: LinearProgressIndicator())
|
||||||
|
])),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
style: flatButtonStyle,
|
||||||
|
onPressed: () {
|
||||||
|
close();
|
||||||
|
},
|
||||||
|
child: Text(translate('Cancel')),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
style: flatButtonStyle,
|
||||||
|
onPressed: () async {
|
||||||
|
setState(() {
|
||||||
|
idServerMsg = null;
|
||||||
|
relayServerMsg = null;
|
||||||
|
apiServerMsg = null;
|
||||||
|
isInProgress = true;
|
||||||
|
});
|
||||||
|
if (await validate()) {
|
||||||
|
if (idCtrl.text != oldCfg.idServer) {
|
||||||
|
if (oldCfg.idServer.isNotEmpty) {
|
||||||
|
await gFFI.userModel.logOut();
|
||||||
|
}
|
||||||
|
bind.mainSetOption(
|
||||||
|
key: "custom-rendezvous-server", value: idCtrl.text);
|
||||||
|
}
|
||||||
|
if (relayCtrl.text != oldCfg.relayServer) {
|
||||||
|
bind.mainSetOption(key: "relay-server", value: relayCtrl.text);
|
||||||
|
}
|
||||||
|
if (keyCtrl.text != oldCfg.key) {
|
||||||
|
bind.mainSetOption(key: "key", value: keyCtrl.text);
|
||||||
|
}
|
||||||
|
if (apiCtrl.text != oldCfg.apiServer) {
|
||||||
|
bind.mainSetOption(key: "api-server", value: apiCtrl.text);
|
||||||
|
}
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
setState(() {
|
||||||
|
isInProgress = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: Text(translate('OK')),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String?> validateAsync(String value) async {
|
||||||
|
value = value.trim();
|
||||||
|
if (value.isEmpty) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final res = await bind.mainTestIfValidServer(server: value);
|
||||||
|
return res.isEmpty ? null : res;
|
||||||
|
}
|
||||||
|
|
||||||
class PasswordWidget extends StatefulWidget {
|
class PasswordWidget extends StatefulWidget {
|
||||||
PasswordWidget({Key? key, required this.controller, this.autoFocus = true})
|
PasswordWidget({Key? key, required this.controller, this.autoFocus = true})
|
||||||
: super(key: key);
|
: super(key: key);
|
||||||
@ -285,7 +425,7 @@ class _PasswordWidgetState extends State<PasswordWidget> {
|
|||||||
color: Theme.of(context).primaryColorDark,
|
color: Theme.of(context).primaryColorDark,
|
||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
// Update the state i.e. toogle the state of passwordVisible variable
|
// Update the state i.e. toggle the state of passwordVisible variable
|
||||||
setState(() {
|
setState(() {
|
||||||
_passwordVisible = !_passwordVisible;
|
_passwordVisible = !_passwordVisible;
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user