Merge pull request #1112 from Heap-Hop/android_password
Android refactor password
This commit is contained in:
commit
cf88ca2bce
@ -313,3 +313,15 @@ class PermissionManager {
|
|||||||
_current = "";
|
_current = "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RadioListTile<T> getRadio<T>(
|
||||||
|
String name, T toValue, T curValue, void Function(T?) onChange) {
|
||||||
|
return RadioListTile<T>(
|
||||||
|
controlAffinity: ListTileControlAffinity.trailing,
|
||||||
|
title: Text(translate(name)),
|
||||||
|
value: toValue,
|
||||||
|
groupValue: curValue,
|
||||||
|
onChanged: onChange,
|
||||||
|
dense: true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@ -9,6 +9,10 @@ import 'model.dart';
|
|||||||
const loginDialogTag = "LOGIN";
|
const loginDialogTag = "LOGIN";
|
||||||
final _emptyIdShow = translate("Generating ...");
|
final _emptyIdShow = translate("Generating ...");
|
||||||
|
|
||||||
|
const kUseTemporaryPassword = "use-temporary-password";
|
||||||
|
const kUsePermanentPassword = "use-permanent-password";
|
||||||
|
const kUseBothPasswords = "use-both-passwords";
|
||||||
|
|
||||||
class ServerModel with ChangeNotifier {
|
class ServerModel with ChangeNotifier {
|
||||||
bool _isStart = false; // Android MainService status
|
bool _isStart = false; // Android MainService status
|
||||||
bool _mediaOk = false;
|
bool _mediaOk = false;
|
||||||
@ -16,6 +20,7 @@ class ServerModel with ChangeNotifier {
|
|||||||
bool _audioOk = false;
|
bool _audioOk = false;
|
||||||
bool _fileOk = false;
|
bool _fileOk = false;
|
||||||
int _connectStatus = 0; // Rendezvous Server status
|
int _connectStatus = 0; // Rendezvous Server status
|
||||||
|
String _verificationMethod = "";
|
||||||
|
|
||||||
final _serverId = TextEditingController(text: _emptyIdShow);
|
final _serverId = TextEditingController(text: _emptyIdShow);
|
||||||
final _serverPasswd = TextEditingController(text: "");
|
final _serverPasswd = TextEditingController(text: "");
|
||||||
@ -34,6 +39,8 @@ class ServerModel with ChangeNotifier {
|
|||||||
|
|
||||||
int get connectStatus => _connectStatus;
|
int get connectStatus => _connectStatus;
|
||||||
|
|
||||||
|
String get verificationMethod => _verificationMethod;
|
||||||
|
|
||||||
TextEditingController get serverId => _serverId;
|
TextEditingController get serverId => _serverId;
|
||||||
|
|
||||||
TextEditingController get serverPasswd => _serverPasswd;
|
TextEditingController get serverPasswd => _serverPasswd;
|
||||||
@ -96,9 +103,29 @@ class ServerModel with ChangeNotifier {
|
|||||||
debugPrint("clients not match!");
|
debugPrint("clients not match!");
|
||||||
updateClientState(res);
|
updateClientState(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updatePasswordModel();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updatePasswordModel() {
|
||||||
|
var update = false;
|
||||||
|
final temporaryPassword = FFI.getByName("temporary_password");
|
||||||
|
final verificationMethod = FFI.getByName("option", "verification-method");
|
||||||
|
if (_serverPasswd.text != temporaryPassword) {
|
||||||
|
_serverPasswd.text = temporaryPassword;
|
||||||
|
update = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_verificationMethod != verificationMethod) {
|
||||||
|
_verificationMethod = verificationMethod;
|
||||||
|
update = true;
|
||||||
|
}
|
||||||
|
if (update) {
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
toggleAudio() async {
|
toggleAudio() async {
|
||||||
if (!_audioOk && !await PermissionManager.check("audio")) {
|
if (!_audioOk && !await PermissionManager.check("audio")) {
|
||||||
final res = await PermissionManager.request("audio");
|
final res = await PermissionManager.request("audio");
|
||||||
@ -195,7 +222,7 @@ class ServerModel with ChangeNotifier {
|
|||||||
FFI.ffiModel.updateEventListener("");
|
FFI.ffiModel.updateEventListener("");
|
||||||
await FFI.invokeMethod("init_service");
|
await FFI.invokeMethod("init_service");
|
||||||
FFI.setByName("start_service");
|
FFI.setByName("start_service");
|
||||||
getIDPasswd();
|
_fetchID();
|
||||||
updateClientState();
|
updateClientState();
|
||||||
Wakelock.enable();
|
Wakelock.enable();
|
||||||
}
|
}
|
||||||
@ -213,54 +240,33 @@ class ServerModel with ChangeNotifier {
|
|||||||
await FFI.invokeMethod("init_input");
|
await FFI.invokeMethod("init_input");
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> updatePassword(String pw) async {
|
Future<bool> setPermanentPassword(String newPW) async {
|
||||||
final oldPasswd = _serverPasswd.text;
|
FFI.setByName("permanent_password", newPW);
|
||||||
FFI.setByName("update_password", pw);
|
|
||||||
await Future.delayed(Duration(milliseconds: 500));
|
await Future.delayed(Duration(milliseconds: 500));
|
||||||
await getIDPasswd(force: true);
|
final pw = FFI.getByName("permanent_password", newPW);
|
||||||
|
if (newPW == pw) {
|
||||||
// check result
|
|
||||||
if (pw == "") {
|
|
||||||
if (_serverPasswd.text.isNotEmpty && _serverPasswd.text != oldPasswd) {
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (_serverPasswd.text == pw) {
|
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
getIDPasswd({bool force = false}) async {
|
_fetchID() async {
|
||||||
if (!force && _serverId.text != _emptyIdShow && _serverPasswd.text != "") {
|
final old = _serverId.text;
|
||||||
return;
|
|
||||||
}
|
|
||||||
var count = 0;
|
var count = 0;
|
||||||
const maxCount = 10;
|
const maxCount = 10;
|
||||||
while (count < maxCount) {
|
while (count < maxCount) {
|
||||||
await Future.delayed(Duration(seconds: 1));
|
await Future.delayed(Duration(seconds: 1));
|
||||||
final id = FFI.getByName("server_id");
|
final id = FFI.getByName("server_id");
|
||||||
final passwd = FFI.getByName("server_password");
|
|
||||||
if (id.isEmpty) {
|
if (id.isEmpty) {
|
||||||
continue;
|
continue;
|
||||||
} else {
|
} else {
|
||||||
_serverId.text = id;
|
_serverId.text = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (passwd.isEmpty) {
|
debugPrint("fetch id again at $count:id:${_serverId.text}");
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
_serverPasswd.text = passwd;
|
|
||||||
}
|
|
||||||
|
|
||||||
debugPrint(
|
|
||||||
"fetch id & passwd again at $count:id:${_serverId.text},passwd:${_serverPasswd.text}");
|
|
||||||
count++;
|
count++;
|
||||||
if (_serverId.text != _emptyIdShow && _serverPasswd.text.isNotEmpty) {
|
if (_serverId.text != old) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -961,18 +961,6 @@ CheckboxListTile getToggle(
|
|||||||
title: Text(translate(name)));
|
title: Text(translate(name)));
|
||||||
}
|
}
|
||||||
|
|
||||||
RadioListTile<String> getRadio(String name, String toValue, String curValue,
|
|
||||||
void Function(String?) onChange) {
|
|
||||||
return RadioListTile<String>(
|
|
||||||
controlAffinity: ListTileControlAffinity.trailing,
|
|
||||||
title: Text(translate(name)),
|
|
||||||
value: toValue,
|
|
||||||
groupValue: curValue,
|
|
||||||
onChanged: onChange,
|
|
||||||
dense: true,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void showOptions() {
|
void showOptions() {
|
||||||
String quality = FFI.getByName('image_quality');
|
String quality = FFI.getByName('image_quality');
|
||||||
if (quality == '') quality = 'balanced';
|
if (quality == '') quality = 'balanced';
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hbb/models/model.dart';
|
import 'package:flutter_hbb/models/model.dart';
|
||||||
import 'package:flutter_hbb/widgets/dialog.dart';
|
import 'package:flutter_hbb/widgets/dialog.dart';
|
||||||
@ -24,36 +26,84 @@ class ServerPage extends StatelessWidget implements PageShape {
|
|||||||
return [
|
return [
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
child: Text(translate("Change ID")),
|
child: Text(translate("Change ID")),
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 16.0),
|
||||||
value: "changeID",
|
value: "changeID",
|
||||||
enabled: false,
|
enabled: false,
|
||||||
),
|
),
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
child: Text(translate("Set your own password")),
|
child: Text(translate("Set permanent password")),
|
||||||
value: "changePW",
|
padding: EdgeInsets.symmetric(horizontal: 16.0),
|
||||||
enabled: FFI.serverModel.isStart,
|
value: "setPermanentPassword",
|
||||||
|
enabled:
|
||||||
|
FFI.serverModel.verificationMethod != kUseTemporaryPassword,
|
||||||
),
|
),
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
child: Text(translate("Refresh random password")),
|
child: Text(translate("Set temporary password length")),
|
||||||
value: "refreshPW",
|
padding: EdgeInsets.symmetric(horizontal: 16.0),
|
||||||
enabled: FFI.serverModel.isStart,
|
value: "setTemporaryPasswordLength",
|
||||||
)
|
enabled:
|
||||||
|
FFI.serverModel.verificationMethod != kUsePermanentPassword,
|
||||||
|
),
|
||||||
|
const PopupMenuDivider(),
|
||||||
|
PopupMenuItem(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 0.0),
|
||||||
|
value: kUseTemporaryPassword,
|
||||||
|
child: Container(
|
||||||
|
child: ListTile(
|
||||||
|
title: Text(translate("Use temporary password")),
|
||||||
|
trailing: Icon(
|
||||||
|
Icons.check,
|
||||||
|
color: FFI.serverModel.verificationMethod ==
|
||||||
|
kUseTemporaryPassword
|
||||||
|
? null
|
||||||
|
: Color(0xFFFFFFFF),
|
||||||
|
))),
|
||||||
|
),
|
||||||
|
PopupMenuItem(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 0.0),
|
||||||
|
value: kUsePermanentPassword,
|
||||||
|
child: ListTile(
|
||||||
|
title: Text(translate("Use permanent password")),
|
||||||
|
trailing: Icon(
|
||||||
|
Icons.check,
|
||||||
|
color: FFI.serverModel.verificationMethod ==
|
||||||
|
kUsePermanentPassword
|
||||||
|
? null
|
||||||
|
: Color(0xFFFFFFFF),
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
PopupMenuItem(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 0.0),
|
||||||
|
value: kUseBothPasswords,
|
||||||
|
child: ListTile(
|
||||||
|
title: Text(translate("Use both passwords")),
|
||||||
|
trailing: Icon(
|
||||||
|
Icons.check,
|
||||||
|
color: FFI.serverModel.verificationMethod !=
|
||||||
|
kUseTemporaryPassword &&
|
||||||
|
FFI.serverModel.verificationMethod !=
|
||||||
|
kUsePermanentPassword
|
||||||
|
? null
|
||||||
|
: Color(0xFFFFFFFF),
|
||||||
|
)),
|
||||||
|
),
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
onSelected: (value) {
|
onSelected: (value) {
|
||||||
if (value == "changeID") {
|
if (value == "changeID") {
|
||||||
// TODO
|
// TODO
|
||||||
} else if (value == "changePW") {
|
} else if (value == "setPermanentPassword") {
|
||||||
updatePasswordDialog();
|
setPermanentPasswordDialog();
|
||||||
} else if (value == "refreshPW") {
|
} else if (value == "setTemporaryPasswordLength") {
|
||||||
() async {
|
setTemporaryPasswordLengthDialog();
|
||||||
showLoading(translate("Waiting"));
|
} else if (value == kUsePermanentPassword ||
|
||||||
if (await FFI.serverModel.updatePassword("")) {
|
value == kUseTemporaryPassword ||
|
||||||
showSuccess();
|
value == kUseBothPasswords) {
|
||||||
} else {
|
Map<String, String> msg = Map()
|
||||||
showError();
|
..["name"] = "verification-method"
|
||||||
}
|
..["value"] = value;
|
||||||
debugPrint("end updatePassword");
|
FFI.setByName('option', jsonEncode(msg));
|
||||||
}();
|
FFI.serverModel.updatePasswordModel();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
];
|
];
|
||||||
@ -90,17 +140,13 @@ void checkService() async {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ServerInfo extends StatefulWidget {
|
class ServerInfo extends StatelessWidget {
|
||||||
@override
|
|
||||||
_ServerInfoState createState() => _ServerInfoState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _ServerInfoState extends State<ServerInfo> {
|
|
||||||
final model = FFI.serverModel;
|
final model = FFI.serverModel;
|
||||||
var _passwdShow = false;
|
final emptyController = TextEditingController(text: "-");
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final isPermanent = model.verificationMethod == kUsePermanentPassword;
|
||||||
return model.isStart
|
return model.isStart
|
||||||
? PaddingCard(
|
? PaddingCard(
|
||||||
child: Column(
|
child: Column(
|
||||||
@ -123,23 +169,22 @@ class _ServerInfoState extends State<ServerInfo> {
|
|||||||
),
|
),
|
||||||
TextFormField(
|
TextFormField(
|
||||||
readOnly: true,
|
readOnly: true,
|
||||||
obscureText: !_passwdShow,
|
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 25.0,
|
fontSize: 25.0,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
color: MyTheme.accent),
|
color: MyTheme.accent),
|
||||||
controller: model.serverPasswd,
|
controller: isPermanent ? emptyController : model.serverPasswd,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
icon: const Icon(Icons.lock),
|
icon: const Icon(Icons.lock),
|
||||||
labelText: translate("Password"),
|
labelText: translate("Password"),
|
||||||
labelStyle: TextStyle(
|
labelStyle: TextStyle(
|
||||||
fontWeight: FontWeight.bold, color: MyTheme.accent50),
|
fontWeight: FontWeight.bold, color: MyTheme.accent50),
|
||||||
suffix: IconButton(
|
suffix: isPermanent
|
||||||
icon: Icon(Icons.visibility),
|
? null
|
||||||
|
: IconButton(
|
||||||
|
icon: const Icon(Icons.refresh),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
setState(() {
|
FFI.setByName("temporary_password");
|
||||||
_passwdShow = !_passwdShow;
|
|
||||||
});
|
|
||||||
})),
|
})),
|
||||||
onSaved: (String? value) {},
|
onSaved: (String? value) {},
|
||||||
),
|
),
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
@ -20,9 +21,10 @@ void showError({Duration duration = SEC1}) {
|
|||||||
showToast(translate("Error"), duration: SEC1);
|
showToast(translate("Error"), duration: SEC1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void updatePasswordDialog() {
|
void setPermanentPasswordDialog() {
|
||||||
final p0 = TextEditingController();
|
final pw = FFI.getByName("permanent_password");
|
||||||
final p1 = TextEditingController();
|
final p0 = TextEditingController(text: pw);
|
||||||
|
final p1 = TextEditingController(text: pw);
|
||||||
var validateLength = false;
|
var validateLength = false;
|
||||||
var validateSame = false;
|
var validateSame = false;
|
||||||
DialogManager.show((setState, close) {
|
DialogManager.show((setState, close) {
|
||||||
@ -86,7 +88,7 @@ void updatePasswordDialog() {
|
|||||||
? () async {
|
? () async {
|
||||||
close();
|
close();
|
||||||
showLoading(translate("Waiting"));
|
showLoading(translate("Waiting"));
|
||||||
if (await FFI.serverModel.updatePassword(p0.text)) {
|
if (await FFI.serverModel.setPermanentPassword(p0.text)) {
|
||||||
showSuccess();
|
showSuccess();
|
||||||
} else {
|
} else {
|
||||||
showError();
|
showError();
|
||||||
@ -100,6 +102,41 @@ void updatePasswordDialog() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setTemporaryPasswordLengthDialog() {
|
||||||
|
List<String> lengths = ['6', '8', '10'];
|
||||||
|
String length = FFI.getByName('option', 'temporary-password-length');
|
||||||
|
var index = lengths.indexOf(length);
|
||||||
|
if (index < 0) index = 0;
|
||||||
|
length = lengths[index];
|
||||||
|
DialogManager.show((setState, close) {
|
||||||
|
final setLength = (newValue) {
|
||||||
|
final oldValue = length;
|
||||||
|
if (oldValue == newValue) return;
|
||||||
|
setState(() {
|
||||||
|
length = newValue;
|
||||||
|
});
|
||||||
|
Map<String, String> msg = Map()
|
||||||
|
..["name"] = "temporary-password-length"
|
||||||
|
..["value"] = newValue;
|
||||||
|
FFI.setByName("option", jsonEncode(msg));
|
||||||
|
FFI.setByName("temporary_password");
|
||||||
|
Future.delayed(Duration(milliseconds: 200), () {
|
||||||
|
close();
|
||||||
|
showSuccess();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
return CustomAlertDialog(
|
||||||
|
title: Text(translate("Set temporary password length")),
|
||||||
|
content: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children:
|
||||||
|
lengths.map((e) => getRadio(e, e, length, setLength)).toList()),
|
||||||
|
actions: [],
|
||||||
|
contentPadding: 14,
|
||||||
|
);
|
||||||
|
}, backDismiss: true, clickMaskDismiss: true);
|
||||||
|
}
|
||||||
|
|
||||||
void enterPasswordDialog(String id) {
|
void enterPasswordDialog(String id) {
|
||||||
final controller = TextEditingController();
|
final controller = TextEditingController();
|
||||||
var remember = FFI.getByName('remember', id) == 'true';
|
var remember = FFI.getByName('remember', id) == 'true';
|
||||||
|
@ -4,6 +4,7 @@ use crate::mobile::{self, Session};
|
|||||||
use crate::common::{make_fd_to_json};
|
use crate::common::{make_fd_to_json};
|
||||||
use flutter_rust_bridge::{StreamSink, ZeroCopyBuffer};
|
use flutter_rust_bridge::{StreamSink, ZeroCopyBuffer};
|
||||||
use hbb_common::{ResultType, init_uuid};
|
use hbb_common::{ResultType, init_uuid};
|
||||||
|
use hbb_common::password_security::password;
|
||||||
use hbb_common::{
|
use hbb_common::{
|
||||||
config::{self, Config, LocalConfig, PeerConfig, ONLINE},
|
config::{self, Config, LocalConfig, PeerConfig, ONLINE},
|
||||||
fs, log,
|
fs, log,
|
||||||
@ -115,22 +116,6 @@ unsafe extern "C" fn get_by_name(name: *const c_char, arg: *const c_char) -> *co
|
|||||||
res = Session::get_option(arg);
|
res = Session::get_option(arg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"server_id" => {
|
|
||||||
res = Config::get_id();
|
|
||||||
}
|
|
||||||
"server_password" => {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
"connect_statue" => {
|
|
||||||
res = ONLINE
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.values()
|
|
||||||
.max()
|
|
||||||
.unwrap_or(&0)
|
|
||||||
.clone()
|
|
||||||
.to_string();
|
|
||||||
}
|
|
||||||
// File Action
|
// File Action
|
||||||
"get_home_dir" => {
|
"get_home_dir" => {
|
||||||
res = fs::get_home_as_string();
|
res = fs::get_home_as_string();
|
||||||
@ -151,6 +136,25 @@ unsafe extern "C" fn get_by_name(name: *const c_char, arg: *const c_char) -> *co
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Server Side
|
// Server Side
|
||||||
|
"server_id" => {
|
||||||
|
res = Config::get_id();
|
||||||
|
}
|
||||||
|
"permanent_password" => {
|
||||||
|
res = Config::get_permanent_password();
|
||||||
|
}
|
||||||
|
"temporary_password" => {
|
||||||
|
res = password::temporary_password();
|
||||||
|
}
|
||||||
|
"connect_statue" => {
|
||||||
|
res = ONLINE
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.values()
|
||||||
|
.max()
|
||||||
|
.unwrap_or(&0)
|
||||||
|
.clone()
|
||||||
|
.to_string();
|
||||||
|
}
|
||||||
#[cfg(target_os = "android")]
|
#[cfg(target_os = "android")]
|
||||||
"clients_state" => {
|
"clients_state" => {
|
||||||
res = get_clients_state();
|
res = get_clients_state();
|
||||||
@ -458,8 +462,11 @@ unsafe extern "C" fn set_by_name(name: *const c_char, value: *const c_char) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Server Side
|
// Server Side
|
||||||
"update_password" => {
|
"permanent_password" => {
|
||||||
todo!()
|
Config::set_permanent_password(value)
|
||||||
|
}
|
||||||
|
"temporary_password" => {
|
||||||
|
password::update_temporary_password();
|
||||||
}
|
}
|
||||||
#[cfg(target_os = "android")]
|
#[cfg(target_os = "android")]
|
||||||
"chat_server_mode" => {
|
"chat_server_mode" => {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user