2023-03-24 15:21:14 +08:00
|
|
|
import 'dart:async';
|
refactor windows specific session (#7170)
1. Modify the process to have the control side lead the session switching: After the control side sends a `LoginRequest`, the controlled side will add all session information and the current session ID in the `LoginResponse`. Upon receiving the `LoginResponse`, the control side will check if the current session ID matches the ID in the `LoginConfigHandler`. If they match, the control side will send the current session ID. If they don't match, a session selection dialog will pop up, the selected session id will be sent. Upon receiving this message, the controlled side will restart if different or sub service if same .
2. Always show physical console session on the top
3. Show running session and distinguish sessions with the same name
4. Not sub service until correct session id is ensured
5. Fix switch sides not work for multisession session
6. Remove all session string join/split except get_available_sessions in
windows.rs
7. Fix prelogin, when share rdp is enabled and there is a rdp session,
the console is in login screen, get_active_username will be the rdp's
username and prelogin will be false, cm can't be created an that
causes disconnection in a loop
8. Rename all user session to windows session
Known issue:
1. Use current process session id for `run_as_user`, sahil says it can
be wrong but I didn't reproduce.
2. Have not change tray process to current session
3. File transfer doesn't update home directory when session changed
4. When it's in login screen, remote file directory is empty, because cm
have not start up
Signed-off-by: 21pages <pages21@163.com>
2024-02-18 22:08:25 +08:00
|
|
|
import 'dart:convert';
|
2023-03-24 15:21:14 +08:00
|
|
|
|
2024-03-20 15:05:54 +08:00
|
|
|
import 'package:bot_toast/bot_toast.dart';
|
2022-09-16 21:52:08 +08:00
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
import 'package:flutter/services.dart';
|
2024-06-27 16:18:41 +08:00
|
|
|
import 'package:flutter/widgets.dart';
|
2023-04-12 09:41:13 +08:00
|
|
|
import 'package:flutter_hbb/common/shared_state.dart';
|
2023-08-31 20:30:20 +08:00
|
|
|
import 'package:flutter_hbb/common/widgets/setting_widgets.dart';
|
2023-11-13 19:29:16 +08:00
|
|
|
import 'package:flutter_hbb/consts.dart';
|
2024-03-20 15:05:54 +08:00
|
|
|
import 'package:flutter_hbb/models/peer_model.dart';
|
|
|
|
import 'package:flutter_hbb/models/peer_tab_model.dart';
|
2023-02-19 22:47:52 +08:00
|
|
|
import 'package:get/get.dart';
|
2024-01-19 15:35:58 +08:00
|
|
|
import 'package:qr_flutter/qr_flutter.dart';
|
2022-09-16 21:52:08 +08:00
|
|
|
|
|
|
|
import '../../common.dart';
|
2023-03-24 15:21:14 +08:00
|
|
|
import '../../models/model.dart';
|
2022-09-16 21:52:08 +08:00
|
|
|
import '../../models/platform_model.dart';
|
2023-08-03 16:48:14 +08:00
|
|
|
import 'address_book.dart';
|
2022-09-16 21:52:08 +08:00
|
|
|
|
2023-06-06 07:39:44 +08:00
|
|
|
void clientClose(SessionID sessionId, OverlayDialogManager dialogManager) {
|
|
|
|
msgBox(sessionId, 'info', 'Close', 'Are you sure to close the connection?',
|
|
|
|
'', dialogManager);
|
2023-03-24 15:21:14 +08:00
|
|
|
}
|
|
|
|
|
2023-02-19 22:47:52 +08:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-16 21:52:08 +08:00
|
|
|
void changeIdDialog() {
|
|
|
|
var newId = "";
|
|
|
|
var msg = "";
|
|
|
|
var isInProgress = false;
|
|
|
|
TextEditingController controller = TextEditingController();
|
2023-02-19 22:47:52 +08:00
|
|
|
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*$'))
|
|
|
|
];
|
|
|
|
|
2023-05-08 12:34:19 +08:00
|
|
|
gFFI.dialogManager.show((setState, close, context) {
|
2022-09-16 21:52:08 +08:00
|
|
|
submit() async {
|
|
|
|
debugPrint("onSubmit");
|
|
|
|
newId = controller.text.trim();
|
2023-02-19 22:47:52 +08:00
|
|
|
|
|
|
|
final Iterable violations = rules.where((r) => !r.validate(newId));
|
|
|
|
if (violations.isNotEmpty) {
|
|
|
|
setState(() {
|
2024-03-28 11:38:11 +08:00
|
|
|
msg = (isDesktop || isWebDesktop)
|
2023-03-05 06:23:10 +08:00
|
|
|
? '${translate('Prompt')}: ${violations.map((r) => r.name).join(', ')}'
|
|
|
|
: violations.map((r) => r.name).join(', ');
|
2023-02-19 22:47:52 +08:00
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-09-16 21:52:08 +08:00
|
|
|
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;
|
2024-03-28 11:38:11 +08:00
|
|
|
msg = (isDesktop || isWebDesktop)
|
2023-03-05 06:23:10 +08:00
|
|
|
? '${translate('Prompt')}: ${translate(status)}'
|
|
|
|
: translate(status);
|
2022-09-16 21:52:08 +08:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
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(
|
2023-02-19 22:47:52 +08:00
|
|
|
labelText: translate('Your new ID'),
|
|
|
|
errorText: msg.isEmpty ? null : translate(msg),
|
|
|
|
suffixText: '${rxId.value.length}/16',
|
|
|
|
suffixStyle: const TextStyle(fontSize: 12, color: Colors.grey)),
|
2022-09-16 21:52:08 +08:00
|
|
|
inputFormatters: [
|
|
|
|
LengthLimitingTextInputFormatter(16),
|
|
|
|
// FilteringTextInputFormatter(RegExp(r"[a-zA-z][a-zA-z0-9\_]*"), allow: true)
|
|
|
|
],
|
|
|
|
controller: controller,
|
2023-02-12 09:03:13 +08:00
|
|
|
autofocus: true,
|
2023-02-19 22:47:52 +08:00
|
|
|
onChanged: (value) {
|
|
|
|
setState(() {
|
|
|
|
rxId.value = value.trim();
|
|
|
|
msg = '';
|
|
|
|
});
|
|
|
|
},
|
|
|
|
),
|
|
|
|
const SizedBox(
|
|
|
|
height: 8.0,
|
2022-09-16 21:52:08 +08:00
|
|
|
),
|
2024-03-28 11:38:11 +08:00
|
|
|
(isDesktop || isWebDesktop)
|
2023-03-05 06:23:10 +08:00
|
|
|
? 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(),
|
2023-08-18 16:13:24 +08:00
|
|
|
// NOT use Offstage to wrap LinearProgressIndicator
|
|
|
|
if (isInProgress) const LinearProgressIndicator(),
|
2022-09-16 21:52:08 +08:00
|
|
|
],
|
|
|
|
),
|
|
|
|
actions: [
|
2023-01-15 19:46:16 +08:00
|
|
|
dialogButton("Cancel", onPressed: close, isOutline: true),
|
|
|
|
dialogButton("OK", onPressed: submit),
|
2022-09-16 21:52:08 +08:00
|
|
|
],
|
|
|
|
onSubmit: submit,
|
|
|
|
onCancel: close,
|
|
|
|
);
|
|
|
|
});
|
|
|
|
}
|
2022-09-26 11:21:40 +08:00
|
|
|
|
|
|
|
void changeWhiteList({Function()? callback}) async {
|
Fix/custom client advanced settings (#8066)
* fix: custom client, advanced settings
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* refact: custom client, default options
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* fix: cargo test
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* refact: remove prefix $ and unify option keys
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* refact: custom client, advanced options
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* debug custom client, advanced settings
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* custom client, advanced settings. Add filter-transfer to display settings
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* custom client, advanced settings
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* fix: custom client, advanced settings, codec
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* fix: custom client, advanced settings, whitelist
Signed-off-by: fufesou <shuanglongchen@yeah.net>
---------
Signed-off-by: fufesou <shuanglongchen@yeah.net>
2024-05-17 14:19:11 +08:00
|
|
|
final curWhiteList = await bind.mainGetOption(key: kOptionWhitelist);
|
|
|
|
var newWhiteListField = curWhiteList == defaultOptionWhitelist
|
|
|
|
? ''
|
|
|
|
: curWhiteList.split(',').join('\n');
|
2022-09-26 11:21:40 +08:00
|
|
|
var controller = TextEditingController(text: newWhiteListField);
|
|
|
|
var msg = "";
|
|
|
|
var isInProgress = false;
|
Fix/custom client advanced settings (#8066)
* fix: custom client, advanced settings
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* refact: custom client, default options
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* fix: cargo test
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* refact: remove prefix $ and unify option keys
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* refact: custom client, advanced options
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* debug custom client, advanced settings
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* custom client, advanced settings. Add filter-transfer to display settings
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* custom client, advanced settings
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* fix: custom client, advanced settings, codec
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* fix: custom client, advanced settings, whitelist
Signed-off-by: fufesou <shuanglongchen@yeah.net>
---------
Signed-off-by: fufesou <shuanglongchen@yeah.net>
2024-05-17 14:19:11 +08:00
|
|
|
final isOptFixed = isOptionFixed(kOptionWhitelist);
|
2023-05-08 12:34:19 +08:00
|
|
|
gFFI.dialogManager.show((setState, close, context) {
|
2022-09-26 11:21:40 +08:00
|
|
|
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,
|
2024-05-18 23:13:54 +08:00
|
|
|
enabled: !isOptFixed,
|
2023-02-12 09:03:13 +08:00
|
|
|
autofocus: true),
|
2022-09-26 11:21:40 +08:00
|
|
|
),
|
|
|
|
],
|
|
|
|
),
|
|
|
|
const SizedBox(
|
|
|
|
height: 4.0,
|
|
|
|
),
|
2023-08-18 16:13:24 +08:00
|
|
|
// NOT use Offstage to wrap LinearProgressIndicator
|
|
|
|
if (isInProgress) const LinearProgressIndicator(),
|
2022-09-26 11:21:40 +08:00
|
|
|
],
|
|
|
|
),
|
|
|
|
actions: [
|
2023-01-15 19:46:16 +08:00
|
|
|
dialogButton("Cancel", onPressed: close, isOutline: true),
|
2024-06-27 16:18:41 +08:00
|
|
|
if (!isOptFixed)
|
|
|
|
dialogButton("Clear", onPressed: () async {
|
Fix/custom client advanced settings (#8066)
* fix: custom client, advanced settings
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* refact: custom client, default options
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* fix: cargo test
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* refact: remove prefix $ and unify option keys
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* refact: custom client, advanced options
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* debug custom client, advanced settings
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* custom client, advanced settings. Add filter-transfer to display settings
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* custom client, advanced settings
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* fix: custom client, advanced settings, codec
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* fix: custom client, advanced settings, whitelist
Signed-off-by: fufesou <shuanglongchen@yeah.net>
---------
Signed-off-by: fufesou <shuanglongchen@yeah.net>
2024-05-17 14:19:11 +08:00
|
|
|
await bind.mainSetOption(
|
2024-06-27 16:18:41 +08:00
|
|
|
key: kOptionWhitelist, value: defaultOptionWhitelist);
|
2023-01-15 19:46:16 +08:00
|
|
|
callback?.call();
|
|
|
|
close();
|
2024-06-27 16:18:41 +08:00
|
|
|
}, 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();
|
|
|
|
},
|
|
|
|
),
|
2022-09-26 11:21:40 +08:00
|
|
|
],
|
|
|
|
onCancel: close,
|
|
|
|
);
|
|
|
|
});
|
|
|
|
}
|
2022-09-29 13:07:20 +08:00
|
|
|
|
|
|
|
Future<String> changeDirectAccessPort(
|
|
|
|
String currentIP, String currentPort) async {
|
|
|
|
final controller = TextEditingController(text: currentPort);
|
2023-05-08 12:34:19 +08:00
|
|
|
await gFFI.dialogManager.show((setState, close, context) {
|
2022-09-29 13:07:20 +08:00
|
|
|
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,
|
2023-02-12 09:03:13 +08:00
|
|
|
autofocus: true),
|
2022-09-29 13:07:20 +08:00
|
|
|
),
|
|
|
|
],
|
|
|
|
),
|
|
|
|
],
|
|
|
|
),
|
|
|
|
actions: [
|
2023-01-15 19:46:16 +08:00
|
|
|
dialogButton("Cancel", onPressed: close, isOutline: true),
|
|
|
|
dialogButton("OK", onPressed: () async {
|
|
|
|
await bind.mainSetOption(
|
Fix/custom client advanced settings (#8066)
* fix: custom client, advanced settings
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* refact: custom client, default options
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* fix: cargo test
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* refact: remove prefix $ and unify option keys
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* refact: custom client, advanced options
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* debug custom client, advanced settings
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* custom client, advanced settings. Add filter-transfer to display settings
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* custom client, advanced settings
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* fix: custom client, advanced settings, codec
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* fix: custom client, advanced settings, whitelist
Signed-off-by: fufesou <shuanglongchen@yeah.net>
---------
Signed-off-by: fufesou <shuanglongchen@yeah.net>
2024-05-17 14:19:11 +08:00
|
|
|
key: kOptionDirectAccessPort, value: controller.text);
|
2023-01-15 19:46:16 +08:00
|
|
|
close();
|
|
|
|
}),
|
2022-09-29 13:07:20 +08:00
|
|
|
],
|
|
|
|
onCancel: close,
|
2023-09-13 13:45:40 +08:00
|
|
|
);
|
|
|
|
});
|
|
|
|
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),
|
|
|
|
),
|
|
|
|
],
|
|
|
|
),
|
|
|
|
],
|
|
|
|
),
|
|
|
|
actions: [
|
|
|
|
dialogButton("Cancel", onPressed: close, isOutline: true),
|
|
|
|
dialogButton("OK", onPressed: () async {
|
|
|
|
await bind.mainSetOption(
|
Fix/custom client advanced settings (#8066)
* fix: custom client, advanced settings
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* refact: custom client, default options
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* fix: cargo test
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* refact: remove prefix $ and unify option keys
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* refact: custom client, advanced options
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* debug custom client, advanced settings
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* custom client, advanced settings. Add filter-transfer to display settings
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* custom client, advanced settings
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* fix: custom client, advanced settings, codec
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* fix: custom client, advanced settings, whitelist
Signed-off-by: fufesou <shuanglongchen@yeah.net>
---------
Signed-off-by: fufesou <shuanglongchen@yeah.net>
2024-05-17 14:19:11 +08:00
|
|
|
key: kOptionAutoDisconnectTimeout, value: controller.text);
|
2023-09-13 13:45:40 +08:00
|
|
|
close();
|
|
|
|
}),
|
|
|
|
],
|
|
|
|
onCancel: close,
|
2022-09-29 13:07:20 +08:00
|
|
|
);
|
|
|
|
});
|
|
|
|
return controller.text;
|
|
|
|
}
|
2023-03-24 15:21:14 +08:00
|
|
|
|
|
|
|
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;
|
2024-01-21 15:22:05 +08:00
|
|
|
final TextInputType? keyboardType;
|
Refact/verification code input check (#6924)
* Refact, login verification code, input check
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Refact, settings, enable 2fa, dialog input
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Refact. Connect, 2fa code, input check
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* refact, 2Fa text field, input formatter, only digits
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* refact, error message
Signed-off-by: fufesou <shuanglongchen@yeah.net>
---------
Signed-off-by: fufesou <shuanglongchen@yeah.net>
2024-01-20 15:36:32 +08:00
|
|
|
final List<TextInputFormatter>? inputFormatters;
|
2023-03-24 15:21:14 +08:00
|
|
|
|
|
|
|
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,
|
2024-01-21 15:22:05 +08:00
|
|
|
this.keyboardType,
|
Refact/verification code input check (#6924)
* Refact, login verification code, input check
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Refact, settings, enable 2fa, dialog input
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Refact. Connect, 2fa code, input check
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* refact, 2Fa text field, input formatter, only digits
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* refact, error message
Signed-off-by: fufesou <shuanglongchen@yeah.net>
---------
Signed-off-by: fufesou <shuanglongchen@yeah.net>
2024-01-20 15:36:32 +08:00
|
|
|
this.inputFormatters,
|
2023-03-24 15:21:14 +08:00
|
|
|
required this.title,
|
|
|
|
required this.controller})
|
|
|
|
: super(key: key);
|
|
|
|
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
return Row(
|
|
|
|
children: [
|
|
|
|
Expanded(
|
|
|
|
child: TextField(
|
|
|
|
decoration: InputDecoration(
|
|
|
|
labelText: title,
|
|
|
|
hintText: hintText,
|
|
|
|
prefixIcon: prefixIcon,
|
|
|
|
suffixIcon: suffixIcon,
|
|
|
|
helperText: helperText,
|
|
|
|
helperMaxLines: 8,
|
|
|
|
errorText: errorText,
|
2023-06-04 14:29:00 +08:00
|
|
|
errorMaxLines: 8,
|
2023-03-24 15:21:14 +08:00
|
|
|
),
|
|
|
|
controller: controller,
|
|
|
|
focusNode: focusNode,
|
|
|
|
autofocus: true,
|
|
|
|
obscureText: obscureText,
|
2024-01-21 15:22:05 +08:00
|
|
|
keyboardType: keyboardType,
|
Refact/verification code input check (#6924)
* Refact, login verification code, input check
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Refact, settings, enable 2fa, dialog input
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Refact. Connect, 2fa code, input check
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* refact, 2Fa text field, input formatter, only digits
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* refact, error message
Signed-off-by: fufesou <shuanglongchen@yeah.net>
---------
Signed-off-by: fufesou <shuanglongchen@yeah.net>
2024-01-20 15:36:32 +08:00
|
|
|
inputFormatters: inputFormatters,
|
2023-03-24 15:21:14 +08:00
|
|
|
),
|
|
|
|
),
|
|
|
|
],
|
|
|
|
).paddingSymmetric(vertical: 4.0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Refact/verification code input check (#6924)
* Refact, login verification code, input check
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Refact, settings, enable 2fa, dialog input
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Refact. Connect, 2fa code, input check
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* refact, 2Fa text field, input formatter, only digits
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* refact, error message
Signed-off-by: fufesou <shuanglongchen@yeah.net>
---------
Signed-off-by: fufesou <shuanglongchen@yeah.net>
2024-01-20 15:36:32 +08:00
|
|
|
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,
|
2024-01-21 15:22:05 +08:00
|
|
|
keyboardType: TextInputType.number,
|
Refact/verification code input check (#6924)
* Refact, login verification code, input check
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Refact, settings, enable 2fa, dialog input
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Refact. Connect, 2fa code, input check
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* refact, 2Fa text field, input formatter, only digits
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* refact, error message
Signed-off-by: fufesou <shuanglongchen@yeah.net>
---------
Signed-off-by: fufesou <shuanglongchen@yeah.net>
2024-01-20 15:36:32 +08:00
|
|
|
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,
|
2024-01-21 15:22:05 +08:00
|
|
|
keyboardType: TextInputType.visiblePassword,
|
Refact/verification code input check (#6924)
* Refact, login verification code, input check
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Refact, settings, enable 2fa, dialog input
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Refact. Connect, 2fa code, input check
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* refact, 2Fa text field, input formatter, only digits
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* refact, error message
Signed-off-by: fufesou <shuanglongchen@yeah.net>
---------
Signed-off-by: fufesou <shuanglongchen@yeah.net>
2024-01-20 15:36:32 +08:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
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,
|
2024-01-21 15:22:05 +08:00
|
|
|
this.keyboardType,
|
Refact/verification code input check (#6924)
* Refact, login verification code, input check
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Refact, settings, enable 2fa, dialog input
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Refact. Connect, 2fa code, input check
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* refact, 2Fa text field, input formatter, only digits
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* refact, error message
Signed-off-by: fufesou <shuanglongchen@yeah.net>
---------
Signed-off-by: fufesou <shuanglongchen@yeah.net>
2024-01-20 15:36:32 +08:00
|
|
|
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;
|
2024-01-21 15:22:05 +08:00
|
|
|
final TextInputType? keyboardType;
|
Refact/verification code input check (#6924)
* Refact, login verification code, input check
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Refact, settings, enable 2fa, dialog input
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Refact. Connect, 2fa code, input check
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* refact, 2Fa text field, input formatter, only digits
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* refact, error message
Signed-off-by: fufesou <shuanglongchen@yeah.net>
---------
Signed-off-by: fufesou <shuanglongchen@yeah.net>
2024-01-20 15:36:32 +08:00
|
|
|
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);
|
2024-01-20 16:03:07 +08:00
|
|
|
String _preText = '';
|
Refact/verification code input check (#6924)
* Refact, login verification code, input check
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Refact, settings, enable 2fa, dialog input
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Refact. Connect, 2fa code, input check
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* refact, 2Fa text field, input formatter, only digits
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* refact, error message
Signed-off-by: fufesou <shuanglongchen@yeah.net>
---------
Signed-off-by: fufesou <shuanglongchen@yeah.net>
2024-01-20 15:36:32 +08:00
|
|
|
|
|
|
|
@override
|
|
|
|
void initState() {
|
|
|
|
super.initState();
|
|
|
|
if (widget.autoFocus) {
|
|
|
|
_timer =
|
|
|
|
Timer(Duration(milliseconds: 50), () => _focusNode.requestFocus());
|
|
|
|
|
|
|
|
if (widget.onChanged != null) {
|
|
|
|
widget.controller.addListener(() {
|
2024-01-20 16:03:07 +08:00
|
|
|
final text = widget.controller.text.trim();
|
|
|
|
if (text == _preText) return;
|
Refact/verification code input check (#6924)
* Refact, login verification code, input check
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Refact, settings, enable 2fa, dialog input
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Refact. Connect, 2fa code, input check
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* refact, 2Fa text field, input formatter, only digits
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* refact, error message
Signed-off-by: fufesou <shuanglongchen@yeah.net>
---------
Signed-off-by: fufesou <shuanglongchen@yeah.net>
2024-01-20 15:36:32 +08:00
|
|
|
widget.onChanged!(setState, errorText);
|
2024-01-20 16:03:07 +08:00
|
|
|
_preText = text;
|
Refact/verification code input check (#6924)
* Refact, login verification code, input check
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Refact, settings, enable 2fa, dialog input
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Refact. Connect, 2fa code, input check
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* refact, 2Fa text field, input formatter, only digits
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* refact, error message
Signed-off-by: fufesou <shuanglongchen@yeah.net>
---------
Signed-off-by: fufesou <shuanglongchen@yeah.net>
2024-01-20 15:36:32 +08:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// software secure keyboard will take the focus since flutter 3.13
|
|
|
|
// request focus again when android account password obtain focus
|
2024-03-24 11:23:06 +08:00
|
|
|
if (isAndroid && widget.reRequestFocus) {
|
Refact/verification code input check (#6924)
* Refact, login verification code, input check
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Refact, settings, enable 2fa, dialog input
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Refact. Connect, 2fa code, input check
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* refact, 2Fa text field, input formatter, only digits
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* refact, error message
Signed-off-by: fufesou <shuanglongchen@yeah.net>
---------
Signed-off-by: fufesou <shuanglongchen@yeah.net>
2024-01-20 15:36:32 +08:00
|
|
|
_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,
|
2024-01-21 15:22:05 +08:00
|
|
|
keyboardType: widget.keyboardType,
|
Refact/verification code input check (#6924)
* Refact, login verification code, input check
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Refact, settings, enable 2fa, dialog input
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Refact. Connect, 2fa code, input check
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* refact, 2Fa text field, input formatter, only digits
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* refact, error message
Signed-off-by: fufesou <shuanglongchen@yeah.net>
---------
Signed-off-by: fufesou <shuanglongchen@yeah.net>
2024-01-20 15:36:32 +08:00
|
|
|
inputFormatters: widget.inputFormatters,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-24 15:21:14 +08:00
|
|
|
class PasswordWidget extends StatefulWidget {
|
|
|
|
PasswordWidget({
|
|
|
|
Key? key,
|
|
|
|
required this.controller,
|
|
|
|
this.autoFocus = true,
|
2023-12-24 16:16:47 +08:00
|
|
|
this.reRequestFocus = false,
|
2023-03-24 15:21:14 +08:00
|
|
|
this.hintText,
|
|
|
|
this.errorText,
|
|
|
|
}) : super(key: key);
|
|
|
|
|
|
|
|
final TextEditingController controller;
|
|
|
|
final bool autoFocus;
|
2023-12-24 16:16:47 +08:00
|
|
|
final bool reRequestFocus;
|
2023-03-24 15:21:14 +08:00
|
|
|
final String? hintText;
|
|
|
|
final String? errorText;
|
|
|
|
|
|
|
|
@override
|
|
|
|
State<PasswordWidget> createState() => _PasswordWidgetState();
|
|
|
|
}
|
|
|
|
|
|
|
|
class _PasswordWidgetState extends State<PasswordWidget> {
|
|
|
|
bool _passwordVisible = false;
|
|
|
|
final _focusNode = FocusNode();
|
|
|
|
Timer? _timer;
|
2023-12-24 16:16:47 +08:00
|
|
|
Timer? _timerReRequestFocus;
|
2023-03-24 15:21:14 +08:00
|
|
|
|
|
|
|
@override
|
|
|
|
void initState() {
|
|
|
|
super.initState();
|
|
|
|
if (widget.autoFocus) {
|
|
|
|
_timer =
|
|
|
|
Timer(Duration(milliseconds: 50), () => _focusNode.requestFocus());
|
|
|
|
}
|
2023-12-24 16:16:47 +08:00
|
|
|
// software secure keyboard will take the focus since flutter 3.13
|
|
|
|
// request focus again when android account password obtain focus
|
2024-03-24 11:23:06 +08:00
|
|
|
if (isAndroid && widget.reRequestFocus) {
|
2023-12-24 16:16:47 +08:00
|
|
|
_focusNode.addListener(() {
|
|
|
|
if (_focusNode.hasFocus) {
|
|
|
|
_timerReRequestFocus?.cancel();
|
|
|
|
_timerReRequestFocus = Timer(
|
|
|
|
Duration(milliseconds: 100), () => _focusNode.requestFocus());
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2023-03-24 15:21:14 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
void dispose() {
|
|
|
|
_timer?.cancel();
|
2023-12-24 16:16:47 +08:00
|
|
|
_timerReRequestFocus?.cancel();
|
2023-03-24 15:21:14 +08:00
|
|
|
_focusNode.unfocus();
|
|
|
|
_focusNode.dispose();
|
|
|
|
super.dispose();
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
return DialogTextField(
|
|
|
|
title: translate(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,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-06 07:39:44 +08:00
|
|
|
void wrongPasswordDialog(SessionID sessionId,
|
|
|
|
OverlayDialogManager dialogManager, type, title, text) {
|
2023-03-24 15:21:14 +08:00
|
|
|
dialogManager.dismissAll();
|
2023-05-08 12:34:19 +08:00
|
|
|
dialogManager.show((setState, close, context) {
|
2023-03-24 15:21:14 +08:00
|
|
|
cancel() {
|
|
|
|
close();
|
|
|
|
closeConnection();
|
|
|
|
}
|
|
|
|
|
|
|
|
submit() {
|
2023-06-06 07:39:44 +08:00
|
|
|
enterPasswordDialog(sessionId, dialogManager);
|
2023-03-24 15:21:14 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return CustomAlertDialog(
|
|
|
|
title: null,
|
|
|
|
content: msgboxContent(type, title, text),
|
|
|
|
onSubmit: submit,
|
|
|
|
onCancel: cancel,
|
|
|
|
actions: [
|
|
|
|
dialogButton(
|
|
|
|
'Cancel',
|
|
|
|
onPressed: cancel,
|
|
|
|
isOutline: true,
|
|
|
|
),
|
|
|
|
dialogButton(
|
|
|
|
'Retry',
|
|
|
|
onPressed: submit,
|
|
|
|
),
|
|
|
|
]);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2023-06-06 07:39:44 +08:00
|
|
|
void enterPasswordDialog(
|
|
|
|
SessionID sessionId, OverlayDialogManager dialogManager) async {
|
2023-03-24 15:21:14 +08:00
|
|
|
await _connectDialog(
|
2023-06-06 07:39:44 +08:00
|
|
|
sessionId,
|
2023-03-24 15:21:14 +08:00
|
|
|
dialogManager,
|
2023-03-29 23:18:27 +08:00
|
|
|
passwordController: TextEditingController(),
|
2023-03-24 15:21:14 +08:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2023-06-06 07:39:44 +08:00
|
|
|
void enterUserLoginDialog(
|
|
|
|
SessionID sessionId, OverlayDialogManager dialogManager) async {
|
2023-03-24 15:21:14 +08:00
|
|
|
await _connectDialog(
|
2023-06-06 07:39:44 +08:00
|
|
|
sessionId,
|
2023-03-24 15:21:14 +08:00
|
|
|
dialogManager,
|
2023-03-29 23:18:27 +08:00
|
|
|
osUsernameController: TextEditingController(),
|
|
|
|
osPasswordController: TextEditingController(),
|
2023-03-24 15:21:14 +08:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
void enterUserLoginAndPasswordDialog(
|
2023-06-06 07:39:44 +08:00
|
|
|
SessionID sessionId, OverlayDialogManager dialogManager) async {
|
2023-03-24 15:21:14 +08:00
|
|
|
await _connectDialog(
|
2023-06-06 07:39:44 +08:00
|
|
|
sessionId,
|
2023-03-24 15:21:14 +08:00
|
|
|
dialogManager,
|
2023-03-29 23:18:27 +08:00
|
|
|
osUsernameController: TextEditingController(),
|
|
|
|
osPasswordController: TextEditingController(),
|
2023-03-24 15:21:14 +08:00
|
|
|
passwordController: TextEditingController(),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
_connectDialog(
|
2023-06-06 07:39:44 +08:00
|
|
|
SessionID sessionId,
|
2023-03-24 15:21:14 +08:00
|
|
|
OverlayDialogManager dialogManager, {
|
2023-03-29 23:18:27 +08:00
|
|
|
TextEditingController? osUsernameController,
|
|
|
|
TextEditingController? osPasswordController,
|
2023-03-24 15:21:14 +08:00
|
|
|
TextEditingController? passwordController,
|
|
|
|
}) async {
|
2023-03-29 23:18:27 +08:00
|
|
|
var rememberPassword = false;
|
|
|
|
if (passwordController != null) {
|
2023-06-06 07:39:44 +08:00
|
|
|
rememberPassword =
|
|
|
|
await bind.sessionGetRemember(sessionId: sessionId) ?? false;
|
2023-03-29 23:18:27 +08:00
|
|
|
}
|
|
|
|
var rememberAccount = false;
|
|
|
|
if (osUsernameController != null) {
|
2023-06-06 07:39:44 +08:00
|
|
|
rememberAccount =
|
|
|
|
await bind.sessionGetRemember(sessionId: sessionId) ?? false;
|
2023-03-29 23:18:27 +08:00
|
|
|
}
|
2023-03-24 15:21:14 +08:00
|
|
|
dialogManager.dismissAll();
|
2023-05-08 12:34:19 +08:00
|
|
|
dialogManager.show((setState, close, context) {
|
2023-03-24 15:21:14 +08:00
|
|
|
cancel() {
|
|
|
|
close();
|
|
|
|
closeConnection();
|
|
|
|
}
|
|
|
|
|
|
|
|
submit() {
|
2023-03-29 23:18:27 +08:00
|
|
|
final osUsername = osUsernameController?.text.trim() ?? '';
|
|
|
|
final osPassword = osPasswordController?.text.trim() ?? '';
|
2023-03-24 15:21:14 +08:00
|
|
|
final password = passwordController?.text.trim() ?? '';
|
2023-03-29 23:18:27 +08:00
|
|
|
if (passwordController != null && password.isEmpty) return;
|
2023-03-30 22:01:43 +08:00
|
|
|
if (rememberAccount) {
|
2023-06-06 07:39:44 +08:00
|
|
|
bind.sessionPeerOption(
|
|
|
|
sessionId: sessionId, name: 'os-username', value: osUsername);
|
|
|
|
bind.sessionPeerOption(
|
|
|
|
sessionId: sessionId, name: 'os-password', value: osPassword);
|
2023-03-30 22:01:43 +08:00
|
|
|
}
|
2023-03-24 15:21:14 +08:00
|
|
|
gFFI.login(
|
2023-03-29 23:18:27 +08:00
|
|
|
osUsername,
|
|
|
|
osPassword,
|
2023-06-06 07:39:44 +08:00
|
|
|
sessionId,
|
2023-03-29 23:18:27 +08:00
|
|
|
password,
|
|
|
|
rememberPassword,
|
2023-03-24 15:21:14 +08:00
|
|
|
);
|
|
|
|
close();
|
|
|
|
dialogManager.showLoading(translate('Logging in...'),
|
|
|
|
onCancel: closeConnection);
|
|
|
|
}
|
|
|
|
|
2023-03-29 23:18:27 +08:00
|
|
|
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: [
|
2023-03-30 11:17:24 +08:00
|
|
|
descWidget(translate('login_linux_tip')),
|
2023-03-29 23:18:27 +08:00
|
|
|
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(
|
2023-03-30 23:26:03 +08:00
|
|
|
translate('Remember password'),
|
2023-03-29 23:18:27 +08:00
|
|
|
rememberPassword,
|
|
|
|
(v) {
|
|
|
|
if (v != null) {
|
|
|
|
setState(() => rememberPassword = v);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
),
|
|
|
|
],
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2023-03-24 15:21:14 +08:00
|
|
|
return CustomAlertDialog(
|
|
|
|
title: Row(
|
|
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
|
|
children: [
|
|
|
|
Icon(Icons.password_rounded, color: MyTheme.accent),
|
2023-03-29 23:18:27 +08:00
|
|
|
Text(translate('Password Required')).paddingOnly(left: 10),
|
2023-03-24 15:21:14 +08:00
|
|
|
],
|
|
|
|
),
|
|
|
|
content: Column(mainAxisSize: MainAxisSize.min, children: [
|
2023-03-29 23:18:27 +08:00
|
|
|
osAccountWidget(),
|
|
|
|
osUsernameController == null || passwordController == null
|
2023-03-24 15:21:14 +08:00
|
|
|
? Offstage()
|
2023-03-30 10:48:18 +08:00
|
|
|
: Container(height: 12),
|
2023-03-29 23:18:27 +08:00
|
|
|
passwdWidget(),
|
2023-03-24 15:21:14 +08:00
|
|
|
]),
|
|
|
|
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(
|
2023-06-06 07:39:44 +08:00
|
|
|
SessionID sessionId, OverlayDialogManager dialogManager, String type) {
|
2023-03-24 15:21:14 +08:00
|
|
|
dialogManager.dismissAll();
|
|
|
|
dialogManager.show(
|
2023-06-06 07:39:44 +08:00
|
|
|
tag: '$sessionId-wait-uac',
|
2023-05-08 12:34:19 +08:00
|
|
|
(setState, close, context) => CustomAlertDialog(
|
2023-03-24 15:21:14 +08:00
|
|
|
title: null,
|
|
|
|
content: msgboxContent(type, 'Wait', 'wait_accept_uac_tip'),
|
2023-09-30 19:47:59 +08:00
|
|
|
actions: [
|
|
|
|
dialogButton(
|
|
|
|
'OK',
|
|
|
|
icon: Icon(Icons.done_rounded),
|
|
|
|
onPressed: close,
|
|
|
|
),
|
|
|
|
],
|
2023-03-24 15:21:14 +08:00
|
|
|
));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Another username && password dialog?
|
2023-06-06 07:39:44 +08:00
|
|
|
void showRequestElevationDialog(
|
|
|
|
SessionID sessionId, OverlayDialogManager dialogManager) {
|
2023-03-24 15:21:14 +08:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-07 19:40:06 +08:00
|
|
|
// 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),
|
2023-03-24 15:21:14 +08:00
|
|
|
),
|
2023-06-07 19:40:06 +08:00
|
|
|
],
|
|
|
|
),
|
|
|
|
);
|
|
|
|
|
|
|
|
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),
|
2023-03-24 15:21:14 +08:00
|
|
|
),
|
2023-06-07 19:40:06 +08:00
|
|
|
],
|
|
|
|
),
|
|
|
|
);
|
|
|
|
|
|
|
|
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(
|
2023-03-24 15:21:14 +08:00
|
|
|
controller: userController,
|
2023-06-07 19:40:06 +08:00
|
|
|
title: translate('Username'),
|
|
|
|
hintText: translate('eg: admin'),
|
|
|
|
prefixIcon: DialogTextField.kUsernameIcon,
|
|
|
|
errorText: errUser.isEmpty ? null : errUser.value,
|
2023-03-24 15:21:14 +08:00
|
|
|
),
|
2023-06-07 19:40:06 +08:00
|
|
|
PasswordWidget(
|
2023-03-24 15:21:14 +08:00
|
|
|
controller: pwdController,
|
2023-06-07 19:40:06 +08:00
|
|
|
autoFocus: false,
|
|
|
|
errorText: errPwd.isEmpty ? null : errPwd.value,
|
2023-03-24 15:21:14 +08:00
|
|
|
),
|
2023-06-07 19:40:06 +08:00
|
|
|
],
|
2024-03-28 11:38:11 +08:00
|
|
|
).marginOnly(left: (isDesktop || isWebDesktop) ? 35 : 0),
|
2023-06-07 19:40:06 +08:00
|
|
|
).marginOnly(top: 10),
|
|
|
|
],
|
|
|
|
),
|
|
|
|
);
|
2023-03-24 15:21:14 +08:00
|
|
|
|
|
|
|
dialogManager.dismissAll();
|
2023-06-06 07:39:44 +08:00
|
|
|
dialogManager.show(tag: '$sessionId-request-elevation',
|
|
|
|
(setState, close, context) {
|
2023-03-24 15:21:14 +08:00
|
|
|
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(
|
2023-06-06 07:39:44 +08:00
|
|
|
sessionId: sessionId,
|
2023-03-24 15:21:14 +08:00
|
|
|
username: userController.text,
|
|
|
|
password: pwdController.text);
|
|
|
|
} else {
|
2023-06-06 07:39:44 +08:00
|
|
|
bind.sessionElevateDirect(sessionId: sessionId);
|
2023-03-24 15:21:14 +08:00
|
|
|
}
|
2023-08-30 20:34:41 +08:00
|
|
|
close();
|
|
|
|
showWaitUacDialog(sessionId, dialogManager, "wait-uac");
|
2023-03-24 15:21:14 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return CustomAlertDialog(
|
|
|
|
title: Text(translate('Request Elevation')),
|
|
|
|
content: content,
|
|
|
|
actions: [
|
2023-06-07 19:40:06 +08:00
|
|
|
dialogButton(
|
|
|
|
'Cancel',
|
|
|
|
icon: Icon(Icons.close_rounded),
|
|
|
|
onPressed: close,
|
|
|
|
isOutline: true,
|
|
|
|
),
|
|
|
|
dialogButton(
|
|
|
|
'OK',
|
|
|
|
icon: Icon(Icons.done_rounded),
|
|
|
|
onPressed: submit,
|
|
|
|
)
|
2023-03-24 15:21:14 +08:00
|
|
|
],
|
|
|
|
onSubmit: submit,
|
|
|
|
onCancel: close,
|
|
|
|
);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
void showOnBlockDialog(
|
2023-06-06 07:39:44 +08:00
|
|
|
SessionID sessionId,
|
2023-03-24 15:21:14 +08:00
|
|
|
String type,
|
|
|
|
String title,
|
|
|
|
String text,
|
|
|
|
OverlayDialogManager dialogManager,
|
|
|
|
) {
|
2023-06-06 07:39:44 +08:00
|
|
|
if (dialogManager.existing('$sessionId-wait-uac') ||
|
|
|
|
dialogManager.existing('$sessionId-request-elevation')) {
|
2023-03-24 15:21:14 +08:00
|
|
|
return;
|
|
|
|
}
|
2023-06-06 07:39:44 +08:00
|
|
|
dialogManager.show(tag: '$sessionId-$type', (setState, close, context) {
|
2023-03-24 15:21:14 +08:00
|
|
|
void submit() {
|
|
|
|
close();
|
2023-06-06 07:39:44 +08:00
|
|
|
showRequestElevationDialog(sessionId, dialogManager);
|
2023-03-24 15:21:14 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
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,
|
|
|
|
);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2023-06-06 07:39:44 +08:00
|
|
|
void showElevationError(SessionID sessionId, String type, String title,
|
|
|
|
String text, OverlayDialogManager dialogManager) {
|
|
|
|
dialogManager.show(tag: '$sessionId-$type', (setState, close, context) {
|
2023-03-24 15:21:14 +08:00
|
|
|
void submit() {
|
|
|
|
close();
|
2023-06-06 07:39:44 +08:00
|
|
|
showRequestElevationDialog(sessionId, dialogManager);
|
2023-03-24 15:21:14 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return CustomAlertDialog(
|
|
|
|
title: null,
|
|
|
|
content: msgboxContent(type, title, text),
|
|
|
|
actions: [
|
|
|
|
dialogButton('Cancel', onPressed: () {
|
|
|
|
close();
|
|
|
|
}, isOutline: true),
|
2023-09-30 19:47:59 +08:00
|
|
|
if (text != 'No permission') dialogButton('Retry', onPressed: submit),
|
2023-03-24 15:21:14 +08:00
|
|
|
],
|
|
|
|
onSubmit: submit,
|
|
|
|
onCancel: close,
|
|
|
|
);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2023-06-06 07:39:44 +08:00
|
|
|
void showWaitAcceptDialog(SessionID sessionId, String type, String title,
|
|
|
|
String text, OverlayDialogManager dialogManager) {
|
2023-03-24 15:21:14 +08:00
|
|
|
dialogManager.dismissAll();
|
2023-05-08 12:34:19 +08:00
|
|
|
dialogManager.show((setState, close, context) {
|
2023-03-24 15:21:14 +08:00
|
|
|
onCancel() {
|
|
|
|
closeConnection();
|
|
|
|
}
|
|
|
|
|
|
|
|
return CustomAlertDialog(
|
|
|
|
title: null,
|
|
|
|
content: msgboxContent(type, title, text),
|
|
|
|
actions: [
|
|
|
|
dialogButton('Cancel', onPressed: onCancel, isOutline: true),
|
|
|
|
],
|
|
|
|
onCancel: onCancel,
|
|
|
|
);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2023-06-06 07:39:44 +08:00
|
|
|
void showRestartRemoteDevice(PeerInfo pi, String id, SessionID sessionId,
|
|
|
|
OverlayDialogManager dialogManager) async {
|
2023-06-07 19:40:06 +08:00
|
|
|
final res = await dialogManager
|
|
|
|
.show<bool>((setState, close, context) => CustomAlertDialog(
|
2023-03-24 15:21:14 +08:00
|
|
|
title: Row(children: [
|
|
|
|
Icon(Icons.warning_rounded, color: Colors.redAccent, size: 28),
|
2023-04-12 09:41:13 +08:00
|
|
|
Flexible(
|
2023-11-06 20:12:01 +08:00
|
|
|
child: Text(translate("Restart remote device"))
|
2023-04-12 09:41:13 +08:00
|
|
|
.paddingOnly(left: 10)),
|
2023-03-24 15:21:14 +08:00
|
|
|
]),
|
|
|
|
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),
|
|
|
|
));
|
2023-06-06 07:39:44 +08:00
|
|
|
if (res == true) bind.sessionRestartRemoteDevice(sessionId: sessionId);
|
2023-03-24 15:21:14 +08:00
|
|
|
}
|
2023-03-30 11:47:32 +08:00
|
|
|
|
|
|
|
showSetOSPassword(
|
2023-06-06 07:39:44 +08:00
|
|
|
SessionID sessionId,
|
2023-03-30 11:47:32 +08:00
|
|
|
bool login,
|
|
|
|
OverlayDialogManager dialogManager,
|
2023-07-04 19:43:39 +08:00
|
|
|
String? osPassword,
|
2023-07-04 20:26:43 +08:00
|
|
|
Function()? closeCallback,
|
2023-03-30 11:47:32 +08:00
|
|
|
) async {
|
|
|
|
final controller = TextEditingController();
|
2023-07-27 18:39:19 +08:00
|
|
|
osPassword ??=
|
|
|
|
await bind.sessionGetOption(sessionId: sessionId, arg: 'os-password') ??
|
|
|
|
'';
|
2023-06-06 07:39:44 +08:00
|
|
|
var autoLogin =
|
|
|
|
await bind.sessionGetOption(sessionId: sessionId, arg: 'auto-login') !=
|
|
|
|
'';
|
2023-07-04 19:43:39 +08:00
|
|
|
controller.text = osPassword;
|
2023-05-08 12:34:19 +08:00
|
|
|
dialogManager.show((setState, close, context) {
|
2023-07-04 20:26:43 +08:00
|
|
|
closeWithCallback([dynamic]) {
|
|
|
|
close();
|
|
|
|
if (closeCallback != null) closeCallback();
|
|
|
|
}
|
2023-07-27 18:39:19 +08:00
|
|
|
|
2023-03-30 11:47:32 +08:00
|
|
|
submit() {
|
|
|
|
var text = controller.text.trim();
|
|
|
|
bind.sessionPeerOption(
|
2023-06-06 07:39:44 +08:00
|
|
|
sessionId: sessionId, name: 'os-password', value: text);
|
|
|
|
bind.sessionPeerOption(
|
|
|
|
sessionId: sessionId,
|
|
|
|
name: 'auto-login',
|
|
|
|
value: autoLogin ? 'Y' : '');
|
2023-03-30 11:47:32 +08:00
|
|
|
if (text != '' && login) {
|
2023-06-06 07:39:44 +08:00
|
|
|
bind.sessionInputOsPassword(sessionId: sessionId, value: text);
|
2023-03-30 11:47:32 +08:00
|
|
|
}
|
2023-07-04 20:26:43 +08:00
|
|
|
closeWithCallback();
|
2023-03-30 11:47:32 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
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),
|
2023-07-04 20:26:43 +08:00
|
|
|
onPressed: closeWithCallback,
|
2023-03-30 11:47:32 +08:00
|
|
|
isOutline: true,
|
|
|
|
),
|
|
|
|
dialogButton(
|
|
|
|
"OK",
|
|
|
|
icon: Icon(Icons.done_rounded),
|
|
|
|
onPressed: submit,
|
|
|
|
),
|
|
|
|
],
|
|
|
|
onSubmit: submit,
|
2023-07-04 20:26:43 +08:00
|
|
|
onCancel: closeWithCallback,
|
2023-03-30 11:47:32 +08:00
|
|
|
);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
showSetOSAccount(
|
2023-06-06 07:39:44 +08:00
|
|
|
SessionID sessionId,
|
2023-03-30 11:47:32 +08:00
|
|
|
OverlayDialogManager dialogManager,
|
|
|
|
) async {
|
|
|
|
final usernameController = TextEditingController();
|
|
|
|
final passwdController = TextEditingController();
|
2023-06-06 07:39:44 +08:00
|
|
|
var username =
|
|
|
|
await bind.sessionGetOption(sessionId: sessionId, arg: 'os-username') ??
|
|
|
|
'';
|
|
|
|
var password =
|
|
|
|
await bind.sessionGetOption(sessionId: sessionId, arg: 'os-password') ??
|
|
|
|
'';
|
2023-03-30 11:47:32 +08:00
|
|
|
usernameController.text = username;
|
|
|
|
passwdController.text = password;
|
2023-05-08 12:34:19 +08:00
|
|
|
dialogManager.show((setState, close, context) {
|
2023-03-30 11:47:32 +08:00
|
|
|
submit() {
|
|
|
|
final username = usernameController.text.trim();
|
|
|
|
final password = usernameController.text.trim();
|
2023-06-06 07:39:44 +08:00
|
|
|
bind.sessionPeerOption(
|
|
|
|
sessionId: sessionId, name: 'os-username', value: username);
|
|
|
|
bind.sessionPeerOption(
|
|
|
|
sessionId: sessionId, name: 'os-password', value: password);
|
2023-03-30 11:47:32 +08:00
|
|
|
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,
|
|
|
|
);
|
|
|
|
});
|
|
|
|
}
|
2023-04-12 09:41:13 +08:00
|
|
|
|
2023-07-01 09:33:48 +08:00
|
|
|
showAuditDialog(FFI ffi) async {
|
|
|
|
final controller = TextEditingController(text: ffi.auditNote);
|
|
|
|
ffi.dialogManager.show((setState, close, context) {
|
2023-04-12 09:41:13 +08:00
|
|
|
submit() {
|
2023-07-01 09:33:48 +08:00
|
|
|
var text = controller.text;
|
|
|
|
bind.sessionSendNote(sessionId: ffi.sessionId, note: text);
|
|
|
|
ffi.auditNote = text;
|
2023-04-12 09:41:13 +08:00
|
|
|
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,
|
|
|
|
)),
|
|
|
|
actions: [
|
|
|
|
dialogButton('Cancel', onPressed: close, isOutline: true),
|
|
|
|
dialogButton('OK', onPressed: submit)
|
|
|
|
],
|
|
|
|
onSubmit: submit,
|
|
|
|
onCancel: close,
|
|
|
|
);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
void showConfirmSwitchSidesDialog(
|
2023-06-06 07:39:44 +08:00
|
|
|
SessionID sessionId, String id, OverlayDialogManager dialogManager) async {
|
2023-05-08 12:34:19 +08:00
|
|
|
dialogManager.show((setState, close, context) {
|
2023-04-12 09:41:13 +08:00
|
|
|
submit() async {
|
2023-06-06 07:39:44 +08:00
|
|
|
await bind.sessionSwitchSides(sessionId: sessionId);
|
2023-04-12 09:41:13 +08:00
|
|
|
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,
|
|
|
|
);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2023-06-06 07:39:44 +08:00
|
|
|
customImageQualityDialog(SessionID sessionId, String id, FFI ffi) async {
|
2023-11-13 19:29:16 +08:00
|
|
|
double initQuality = kDefaultQuality;
|
|
|
|
double initFps = kDefaultFps;
|
2023-04-12 09:41:13 +08:00
|
|
|
bool qualitySet = false;
|
2023-11-09 15:24:57 +08:00
|
|
|
bool fpsSet = false;
|
2023-11-02 20:39:56 +08:00
|
|
|
|
|
|
|
bool? direct;
|
|
|
|
try {
|
|
|
|
direct =
|
|
|
|
ConnectionTypeState.find(id).direct.value == ConnectionType.strDirect;
|
|
|
|
} catch (_) {}
|
2023-11-09 15:24:57 +08:00
|
|
|
bool hideFps = (await bind.mainIsUsingPublicServer() && direct != true) ||
|
|
|
|
versionCmp(ffi.ffiModel.pi.version, '1.2.0') < 0;
|
2023-11-02 20:39:56 +08:00
|
|
|
bool hideMoreQuality =
|
|
|
|
(await bind.mainIsUsingPublicServer() && direct != true) ||
|
|
|
|
versionCmp(ffi.ffiModel.pi.version, '1.2.2') < 0;
|
|
|
|
|
2023-11-09 15:24:57 +08:00
|
|
|
setCustomValues({double? quality, double? fps}) async {
|
2023-11-13 19:29:16 +08:00
|
|
|
debugPrint("setCustomValues quality:$quality, fps:$fps");
|
2023-04-12 09:41:13 +08:00
|
|
|
if (quality != null) {
|
|
|
|
qualitySet = true;
|
2023-06-06 07:39:44 +08:00
|
|
|
await bind.sessionSetCustomImageQuality(
|
|
|
|
sessionId: sessionId, value: quality.toInt());
|
2023-04-12 09:41:13 +08:00
|
|
|
}
|
2023-11-09 15:24:57 +08:00
|
|
|
if (fps != null) {
|
|
|
|
fpsSet = true;
|
|
|
|
await bind.sessionSetCustomFps(sessionId: sessionId, fps: fps.toInt());
|
|
|
|
}
|
2023-04-12 09:41:13 +08:00
|
|
|
if (!qualitySet) {
|
|
|
|
qualitySet = true;
|
|
|
|
await bind.sessionSetCustomImageQuality(
|
2023-11-13 19:29:16 +08:00
|
|
|
sessionId: sessionId, value: initQuality.toInt());
|
2023-04-12 09:41:13 +08:00
|
|
|
}
|
2023-11-09 15:24:57 +08:00
|
|
|
if (!hideFps && !fpsSet) {
|
|
|
|
fpsSet = true;
|
|
|
|
await bind.sessionSetCustomFps(
|
2023-11-13 19:29:16 +08:00
|
|
|
sessionId: sessionId, fps: initFps.toInt());
|
2023-11-09 15:24:57 +08:00
|
|
|
}
|
2023-04-12 09:41:13 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
final btnClose = dialogButton('Close', onPressed: () async {
|
|
|
|
await setCustomValues();
|
|
|
|
ffi.dialogManager.dismissAll();
|
|
|
|
});
|
|
|
|
|
|
|
|
// quality
|
2023-06-06 07:39:44 +08:00
|
|
|
final quality = await bind.sessionGetCustomImageQuality(sessionId: sessionId);
|
2023-11-13 19:29:16 +08:00
|
|
|
initQuality = quality != null && quality.isNotEmpty
|
|
|
|
? quality[0].toDouble()
|
|
|
|
: kDefaultQuality;
|
|
|
|
if (initQuality < kMinQuality ||
|
|
|
|
initQuality > (!hideMoreQuality ? kMaxMoreQuality : kMaxQuality)) {
|
|
|
|
initQuality = kDefaultQuality;
|
2023-11-09 15:24:57 +08:00
|
|
|
}
|
|
|
|
// fps
|
|
|
|
final fpsOption =
|
|
|
|
await bind.sessionGetOption(sessionId: sessionId, arg: 'custom-fps');
|
2023-11-13 19:29:16 +08:00
|
|
|
initFps = fpsOption == null
|
|
|
|
? kDefaultFps
|
|
|
|
: double.tryParse(fpsOption) ?? kDefaultFps;
|
|
|
|
if (initFps < kMinFps || initFps > kMaxFps) {
|
|
|
|
initFps = kDefaultFps;
|
2023-11-09 15:24:57 +08:00
|
|
|
}
|
2023-08-31 20:30:20 +08:00
|
|
|
|
|
|
|
final content = customImageQualityWidget(
|
2023-11-13 19:29:16 +08:00
|
|
|
initQuality: initQuality,
|
|
|
|
initFps: initFps,
|
2023-08-31 20:30:20 +08:00
|
|
|
setQuality: (v) => setCustomValues(quality: v),
|
2023-11-09 15:24:57 +08:00
|
|
|
setFps: (v) => setCustomValues(fps: v),
|
|
|
|
showFps: !hideFps,
|
2023-11-02 20:39:56 +08:00
|
|
|
showMoreQuality: !hideMoreQuality);
|
2023-04-12 09:41:13 +08:00
|
|
|
msgBoxCommon(ffi.dialogManager, 'Custom Image Quality', content, [btnClose]);
|
|
|
|
}
|
2023-08-03 16:48:14 +08:00
|
|
|
|
2024-03-20 15:05:54 +08:00
|
|
|
void deleteConfirmDialog(Function onSubmit, String title) async {
|
2023-08-03 16:48:14 +08:00
|
|
|
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,
|
|
|
|
),
|
2023-08-08 18:06:31 +08:00
|
|
|
Expanded(
|
|
|
|
child: Text(title, overflow: TextOverflow.ellipsis).paddingOnly(
|
|
|
|
left: 10,
|
|
|
|
),
|
2023-08-03 16:48:14 +08:00
|
|
|
),
|
|
|
|
],
|
|
|
|
),
|
|
|
|
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;
|
|
|
|
|
2024-03-20 15:05:54 +08:00
|
|
|
final tags = List.of(gFFI.abModel.currentAbTags);
|
2023-08-03 16:48:14 +08:00
|
|
|
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),
|
|
|
|
),
|
|
|
|
),
|
2023-08-18 16:13:24 +08:00
|
|
|
// NOT use Offstage to wrap LinearProgressIndicator
|
|
|
|
if (isInProgress) const LinearProgressIndicator(),
|
2023-08-03 16:48:14 +08:00
|
|
|
],
|
|
|
|
),
|
|
|
|
actions: [
|
|
|
|
dialogButton("Cancel", onPressed: close, isOutline: true),
|
|
|
|
dialogButton("OK", onPressed: submit),
|
|
|
|
],
|
|
|
|
onSubmit: submit,
|
|
|
|
onCancel: close,
|
|
|
|
);
|
|
|
|
});
|
|
|
|
}
|
2023-08-10 10:08:33 +08:00
|
|
|
|
|
|
|
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,
|
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
2023-08-18 16:13:24 +08:00
|
|
|
// NOT use Offstage to wrap LinearProgressIndicator
|
|
|
|
Obx(() =>
|
|
|
|
isInProgress.value ? const LinearProgressIndicator() : Offstage())
|
2023-08-10 10:08:33 +08:00
|
|
|
],
|
|
|
|
),
|
|
|
|
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,
|
|
|
|
);
|
|
|
|
});
|
|
|
|
}
|
2024-01-19 15:35:58 +08:00
|
|
|
|
2024-06-27 16:18:41 +08:00
|
|
|
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(
|
2024-06-29 10:46:21 +08:00
|
|
|
hintText: translate('Token'),
|
2024-06-27 16:18:41 +08:00
|
|
|
),
|
|
|
|
);
|
|
|
|
|
|
|
|
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,
|
|
|
|
);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2024-01-19 15:35:58 +08:00
|
|
|
void change2fa({Function()? callback}) async {
|
|
|
|
if (bind.mainHasValid2FaSync()) {
|
|
|
|
await bind.mainSetOption(key: "2fa", value: "");
|
|
|
|
callback?.call();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
var new2fa = (await bind.mainGenerate2Fa());
|
|
|
|
final secretRegex = RegExp(r'secret=([^&]+)');
|
|
|
|
final secret = secretRegex.firstMatch(new2fa)?.group(1);
|
Refact/verification code input check (#6924)
* Refact, login verification code, input check
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Refact, settings, enable 2fa, dialog input
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Refact. Connect, 2fa code, input check
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* refact, 2Fa text field, input formatter, only digits
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* refact, error message
Signed-off-by: fufesou <shuanglongchen@yeah.net>
---------
Signed-off-by: fufesou <shuanglongchen@yeah.net>
2024-01-20 15:36:32 +08:00
|
|
|
String? errorText;
|
|
|
|
final controller = TextEditingController();
|
2024-01-19 15:35:58 +08:00
|
|
|
gFFI.dialogManager.show((setState, close, context) {
|
Refact/verification code input check (#6924)
* Refact, login verification code, input check
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Refact, settings, enable 2fa, dialog input
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Refact. Connect, 2fa code, input check
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* refact, 2Fa text field, input formatter, only digits
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* refact, error message
Signed-off-by: fufesou <shuanglongchen@yeah.net>
---------
Signed-off-by: fufesou <shuanglongchen@yeah.net>
2024-01-20 15:36:32 +08:00
|
|
|
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;
|
|
|
|
|
2024-01-19 15:35:58 +08:00
|
|
|
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),
|
Refact/verification code input check (#6924)
* Refact, login verification code, input check
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Refact, settings, enable 2fa, dialog input
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Refact. Connect, 2fa code, input check
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* refact, 2Fa text field, input formatter, only digits
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* refact, error message
Signed-off-by: fufesou <shuanglongchen@yeah.net>
---------
Signed-off-by: fufesou <shuanglongchen@yeah.net>
2024-01-20 15:36:32 +08:00
|
|
|
Row(children: [Expanded(child: codeField)]),
|
2024-01-19 15:35:58 +08:00
|
|
|
],
|
|
|
|
),
|
|
|
|
actions: [
|
|
|
|
dialogButton("Cancel", onPressed: close, isOutline: true),
|
Refact/verification code input check (#6924)
* Refact, login verification code, input check
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Refact, settings, enable 2fa, dialog input
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Refact. Connect, 2fa code, input check
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* refact, 2Fa text field, input formatter, only digits
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* refact, error message
Signed-off-by: fufesou <shuanglongchen@yeah.net>
---------
Signed-off-by: fufesou <shuanglongchen@yeah.net>
2024-01-20 15:36:32 +08:00
|
|
|
dialogButton("OK", onPressed: getOnSubmit()),
|
2024-01-19 15:35:58 +08:00
|
|
|
],
|
|
|
|
onCancel: close,
|
|
|
|
);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
void enter2FaDialog(
|
|
|
|
SessionID sessionId, OverlayDialogManager dialogManager) async {
|
Refact/verification code input check (#6924)
* Refact, login verification code, input check
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Refact, settings, enable 2fa, dialog input
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Refact. Connect, 2fa code, input check
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* refact, 2Fa text field, input formatter, only digits
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* refact, error message
Signed-off-by: fufesou <shuanglongchen@yeah.net>
---------
Signed-off-by: fufesou <shuanglongchen@yeah.net>
2024-01-20 15:36:32 +08:00
|
|
|
final controller = TextEditingController();
|
|
|
|
final RxBool submitReady = false.obs;
|
|
|
|
|
2024-01-19 15:35:58 +08:00
|
|
|
dialogManager.dismissAll();
|
|
|
|
dialogManager.show((setState, close, context) {
|
|
|
|
cancel() {
|
|
|
|
close();
|
|
|
|
closeConnection();
|
|
|
|
}
|
|
|
|
|
|
|
|
submit() {
|
Refact/verification code input check (#6924)
* Refact, login verification code, input check
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Refact, settings, enable 2fa, dialog input
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Refact. Connect, 2fa code, input check
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* refact, 2Fa text field, input formatter, only digits
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* refact, error message
Signed-off-by: fufesou <shuanglongchen@yeah.net>
---------
Signed-off-by: fufesou <shuanglongchen@yeah.net>
2024-01-20 15:36:32 +08:00
|
|
|
gFFI.send2FA(sessionId, controller.text.trim());
|
2024-01-19 15:35:58 +08:00
|
|
|
close();
|
|
|
|
dialogManager.showLoading(translate('Logging in...'),
|
|
|
|
onCancel: closeConnection);
|
|
|
|
}
|
|
|
|
|
Refact/verification code input check (#6924)
* Refact, login verification code, input check
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Refact, settings, enable 2fa, dialog input
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Refact. Connect, 2fa code, input check
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* refact, 2Fa text field, input formatter, only digits
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* refact, error message
Signed-off-by: fufesou <shuanglongchen@yeah.net>
---------
Signed-off-by: fufesou <shuanglongchen@yeah.net>
2024-01-20 15:36:32 +08:00
|
|
|
late Dialog2FaField codeField;
|
|
|
|
|
|
|
|
codeField = Dialog2FaField(
|
|
|
|
controller: controller,
|
|
|
|
title: translate('Verification code'),
|
|
|
|
onChanged: () => submitReady.value = codeField.isReady,
|
|
|
|
);
|
|
|
|
|
2024-01-19 15:35:58 +08:00
|
|
|
return CustomAlertDialog(
|
Refact/verification code input check (#6924)
* Refact, login verification code, input check
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Refact, settings, enable 2fa, dialog input
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Refact. Connect, 2fa code, input check
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* refact, 2Fa text field, input formatter, only digits
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* refact, error message
Signed-off-by: fufesou <shuanglongchen@yeah.net>
---------
Signed-off-by: fufesou <shuanglongchen@yeah.net>
2024-01-20 15:36:32 +08:00
|
|
|
title: Text(translate('enter-2fa-title')),
|
|
|
|
content: codeField,
|
2024-01-19 15:35:58 +08:00
|
|
|
actions: [
|
2024-01-21 21:53:29 +08:00
|
|
|
dialogButton('Cancel',
|
|
|
|
onPressed: cancel,
|
|
|
|
isOutline: true,
|
|
|
|
style: TextStyle(
|
|
|
|
color: Theme.of(context).textTheme.bodyMedium?.color)),
|
2024-01-19 15:35:58 +08:00
|
|
|
Obx(() => dialogButton(
|
|
|
|
'OK',
|
Refact/verification code input check (#6924)
* Refact, login verification code, input check
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Refact, settings, enable 2fa, dialog input
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Refact. Connect, 2fa code, input check
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* refact, 2Fa text field, input formatter, only digits
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* refact, error message
Signed-off-by: fufesou <shuanglongchen@yeah.net>
---------
Signed-off-by: fufesou <shuanglongchen@yeah.net>
2024-01-20 15:36:32 +08:00
|
|
|
onPressed: submitReady.isTrue ? submit : null,
|
2024-01-19 15:35:58 +08:00
|
|
|
)),
|
2024-01-22 13:44:10 +08:00
|
|
|
],
|
|
|
|
onSubmit: submit,
|
|
|
|
onCancel: cancel);
|
2024-01-19 15:35:58 +08:00
|
|
|
});
|
|
|
|
}
|
2024-02-14 23:59:17 +08:00
|
|
|
|
refactor windows specific session (#7170)
1. Modify the process to have the control side lead the session switching: After the control side sends a `LoginRequest`, the controlled side will add all session information and the current session ID in the `LoginResponse`. Upon receiving the `LoginResponse`, the control side will check if the current session ID matches the ID in the `LoginConfigHandler`. If they match, the control side will send the current session ID. If they don't match, a session selection dialog will pop up, the selected session id will be sent. Upon receiving this message, the controlled side will restart if different or sub service if same .
2. Always show physical console session on the top
3. Show running session and distinguish sessions with the same name
4. Not sub service until correct session id is ensured
5. Fix switch sides not work for multisession session
6. Remove all session string join/split except get_available_sessions in
windows.rs
7. Fix prelogin, when share rdp is enabled and there is a rdp session,
the console is in login screen, get_active_username will be the rdp's
username and prelogin will be false, cm can't be created an that
causes disconnection in a loop
8. Rename all user session to windows session
Known issue:
1. Use current process session id for `run_as_user`, sahil says it can
be wrong but I didn't reproduce.
2. Have not change tray process to current session
3. File transfer doesn't update home directory when session changed
4. When it's in login screen, remote file directory is empty, because cm
have not start up
Signed-off-by: 21pages <pages21@163.com>
2024-02-18 22:08:25 +08:00
|
|
|
// This dialog should not be dismissed, otherwise it will be black screen, have not reproduced this.
|
2024-02-14 23:59:17 +08:00
|
|
|
void showWindowsSessionsDialog(
|
|
|
|
String type,
|
|
|
|
String title,
|
|
|
|
String text,
|
|
|
|
OverlayDialogManager dialogManager,
|
|
|
|
SessionID sessionId,
|
|
|
|
String peerId,
|
|
|
|
String sessions) {
|
refactor windows specific session (#7170)
1. Modify the process to have the control side lead the session switching: After the control side sends a `LoginRequest`, the controlled side will add all session information and the current session ID in the `LoginResponse`. Upon receiving the `LoginResponse`, the control side will check if the current session ID matches the ID in the `LoginConfigHandler`. If they match, the control side will send the current session ID. If they don't match, a session selection dialog will pop up, the selected session id will be sent. Upon receiving this message, the controlled side will restart if different or sub service if same .
2. Always show physical console session on the top
3. Show running session and distinguish sessions with the same name
4. Not sub service until correct session id is ensured
5. Fix switch sides not work for multisession session
6. Remove all session string join/split except get_available_sessions in
windows.rs
7. Fix prelogin, when share rdp is enabled and there is a rdp session,
the console is in login screen, get_active_username will be the rdp's
username and prelogin will be false, cm can't be created an that
causes disconnection in a loop
8. Rename all user session to windows session
Known issue:
1. Use current process session id for `run_as_user`, sahil says it can
be wrong but I didn't reproduce.
2. Have not change tray process to current session
3. File transfer doesn't update home directory when session changed
4. When it's in login screen, remote file directory is empty, because cm
have not start up
Signed-off-by: 21pages <pages21@163.com>
2024-02-18 22:08:25 +08:00
|
|
|
List<dynamic> sessionsList = [];
|
|
|
|
try {
|
|
|
|
sessionsList = json.decode(sessions);
|
|
|
|
} catch (e) {
|
|
|
|
print(e);
|
|
|
|
}
|
|
|
|
List<String> sids = [];
|
|
|
|
List<String> names = [];
|
2024-02-14 23:59:17 +08:00
|
|
|
for (var session in sessionsList) {
|
refactor windows specific session (#7170)
1. Modify the process to have the control side lead the session switching: After the control side sends a `LoginRequest`, the controlled side will add all session information and the current session ID in the `LoginResponse`. Upon receiving the `LoginResponse`, the control side will check if the current session ID matches the ID in the `LoginConfigHandler`. If they match, the control side will send the current session ID. If they don't match, a session selection dialog will pop up, the selected session id will be sent. Upon receiving this message, the controlled side will restart if different or sub service if same .
2. Always show physical console session on the top
3. Show running session and distinguish sessions with the same name
4. Not sub service until correct session id is ensured
5. Fix switch sides not work for multisession session
6. Remove all session string join/split except get_available_sessions in
windows.rs
7. Fix prelogin, when share rdp is enabled and there is a rdp session,
the console is in login screen, get_active_username will be the rdp's
username and prelogin will be false, cm can't be created an that
causes disconnection in a loop
8. Rename all user session to windows session
Known issue:
1. Use current process session id for `run_as_user`, sahil says it can
be wrong but I didn't reproduce.
2. Have not change tray process to current session
3. File transfer doesn't update home directory when session changed
4. When it's in login screen, remote file directory is empty, because cm
have not start up
Signed-off-by: 21pages <pages21@163.com>
2024-02-18 22:08:25 +08:00
|
|
|
sids.add(session['sid']);
|
|
|
|
names.add(session['name']);
|
2024-02-14 23:59:17 +08:00
|
|
|
}
|
refactor windows specific session (#7170)
1. Modify the process to have the control side lead the session switching: After the control side sends a `LoginRequest`, the controlled side will add all session information and the current session ID in the `LoginResponse`. Upon receiving the `LoginResponse`, the control side will check if the current session ID matches the ID in the `LoginConfigHandler`. If they match, the control side will send the current session ID. If they don't match, a session selection dialog will pop up, the selected session id will be sent. Upon receiving this message, the controlled side will restart if different or sub service if same .
2. Always show physical console session on the top
3. Show running session and distinguish sessions with the same name
4. Not sub service until correct session id is ensured
5. Fix switch sides not work for multisession session
6. Remove all session string join/split except get_available_sessions in
windows.rs
7. Fix prelogin, when share rdp is enabled and there is a rdp session,
the console is in login screen, get_active_username will be the rdp's
username and prelogin will be false, cm can't be created an that
causes disconnection in a loop
8. Rename all user session to windows session
Known issue:
1. Use current process session id for `run_as_user`, sahil says it can
be wrong but I didn't reproduce.
2. Have not change tray process to current session
3. File transfer doesn't update home directory when session changed
4. When it's in login screen, remote file directory is empty, because cm
have not start up
Signed-off-by: 21pages <pages21@163.com>
2024-02-18 22:08:25 +08:00
|
|
|
String selectedUserValue = sids.first;
|
2024-02-14 23:59:17 +08:00
|
|
|
dialogManager.dismissAll();
|
|
|
|
dialogManager.show((setState, close, context) {
|
refactor windows specific session (#7170)
1. Modify the process to have the control side lead the session switching: After the control side sends a `LoginRequest`, the controlled side will add all session information and the current session ID in the `LoginResponse`. Upon receiving the `LoginResponse`, the control side will check if the current session ID matches the ID in the `LoginConfigHandler`. If they match, the control side will send the current session ID. If they don't match, a session selection dialog will pop up, the selected session id will be sent. Upon receiving this message, the controlled side will restart if different or sub service if same .
2. Always show physical console session on the top
3. Show running session and distinguish sessions with the same name
4. Not sub service until correct session id is ensured
5. Fix switch sides not work for multisession session
6. Remove all session string join/split except get_available_sessions in
windows.rs
7. Fix prelogin, when share rdp is enabled and there is a rdp session,
the console is in login screen, get_active_username will be the rdp's
username and prelogin will be false, cm can't be created an that
causes disconnection in a loop
8. Rename all user session to windows session
Known issue:
1. Use current process session id for `run_as_user`, sahil says it can
be wrong but I didn't reproduce.
2. Have not change tray process to current session
3. File transfer doesn't update home directory when session changed
4. When it's in login screen, remote file directory is empty, because cm
have not start up
Signed-off-by: 21pages <pages21@163.com>
2024-02-18 22:08:25 +08:00
|
|
|
submit() {
|
|
|
|
bind.sessionSendSelectedSessionId(
|
|
|
|
sessionId: sessionId, sid: selectedUserValue);
|
|
|
|
close();
|
2024-02-14 23:59:17 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return CustomAlertDialog(
|
|
|
|
title: null,
|
|
|
|
content: msgboxContent(type, title, text),
|
|
|
|
actions: [
|
refactor windows specific session (#7170)
1. Modify the process to have the control side lead the session switching: After the control side sends a `LoginRequest`, the controlled side will add all session information and the current session ID in the `LoginResponse`. Upon receiving the `LoginResponse`, the control side will check if the current session ID matches the ID in the `LoginConfigHandler`. If they match, the control side will send the current session ID. If they don't match, a session selection dialog will pop up, the selected session id will be sent. Upon receiving this message, the controlled side will restart if different or sub service if same .
2. Always show physical console session on the top
3. Show running session and distinguish sessions with the same name
4. Not sub service until correct session id is ensured
5. Fix switch sides not work for multisession session
6. Remove all session string join/split except get_available_sessions in
windows.rs
7. Fix prelogin, when share rdp is enabled and there is a rdp session,
the console is in login screen, get_active_username will be the rdp's
username and prelogin will be false, cm can't be created an that
causes disconnection in a loop
8. Rename all user session to windows session
Known issue:
1. Use current process session id for `run_as_user`, sahil says it can
be wrong but I didn't reproduce.
2. Have not change tray process to current session
3. File transfer doesn't update home directory when session changed
4. When it's in login screen, remote file directory is empty, because cm
have not start up
Signed-off-by: 21pages <pages21@163.com>
2024-02-18 22:08:25 +08:00
|
|
|
ComboBox(
|
|
|
|
keys: sids,
|
|
|
|
values: names,
|
|
|
|
initialKey: selectedUserValue,
|
|
|
|
onChanged: (value) {
|
|
|
|
selectedUserValue = value;
|
|
|
|
}),
|
|
|
|
dialogButton('Connect', onPressed: submit, isOutline: false),
|
2024-02-14 23:59:17 +08:00
|
|
|
],
|
|
|
|
);
|
|
|
|
});
|
|
|
|
}
|
2024-03-20 15:05:54 +08:00
|
|
|
|
|
|
|
void addPeersToAbDialog(
|
|
|
|
List<Peer> peers,
|
|
|
|
) async {
|
|
|
|
Future<bool> addTo(String abname) async {
|
|
|
|
final mapList = peers.map((e) {
|
|
|
|
var json = e.toJson();
|
2024-04-02 22:08:47 +08:00
|
|
|
// remove password when add to another address book to avoid re-share
|
2024-03-20 15:05:54 +08:00
|
|
|
json.remove('password');
|
2024-04-02 22:08:47 +08:00
|
|
|
json.remove('hash');
|
2024-03-20 15:05:54 +08:00
|
|
|
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: [
|
2024-04-02 22:08:47 +08:00
|
|
|
// https://github.com/flutter/flutter/issues/145081
|
2024-03-20 15:05:54 +08:00
|
|
|
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) {
|
2024-04-02 22:08:47 +08:00
|
|
|
TextEditingController controller = TextEditingController(text: '');
|
2024-03-20 15:05:54 +08:00
|
|
|
RxBool isInProgress = false.obs;
|
2024-04-02 22:08:47 +08:00
|
|
|
RxBool isInputEmpty = true.obs;
|
|
|
|
bool passwordVisible = false;
|
|
|
|
controller.addListener(() {
|
|
|
|
isInputEmpty.value = controller.text.isEmpty;
|
|
|
|
});
|
2024-03-20 15:05:54 +08:00
|
|
|
gFFI.dialogManager.show((setState, close, context) {
|
2024-04-02 22:08:47 +08:00
|
|
|
change(String password) async {
|
2024-03-20 15:05:54 +08:00
|
|
|
isInProgress.value = true;
|
2024-04-02 22:08:47 +08:00
|
|
|
bool res =
|
|
|
|
await gFFI.abModel.changeSharedPassword(abName, peer.id, password);
|
2024-03-20 15:05:54 +08:00
|
|
|
isInProgress.value = false;
|
|
|
|
if (res) {
|
|
|
|
showToast(translate('Successful'));
|
|
|
|
}
|
2024-04-02 22:08:47 +08:00
|
|
|
close();
|
2024-03-20 15:05:54 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
cancel() {
|
|
|
|
close();
|
|
|
|
}
|
|
|
|
|
|
|
|
return CustomAlertDialog(
|
|
|
|
title: Row(
|
|
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
|
|
children: [
|
|
|
|
Icon(Icons.key, color: MyTheme.accent),
|
2024-04-02 22:08:47 +08:00
|
|
|
Text(translate(peer.password.isEmpty
|
|
|
|
? 'Set shared password'
|
|
|
|
: 'Change Password'))
|
|
|
|
.paddingOnly(left: 10),
|
2024-03-20 15:05:54 +08:00
|
|
|
],
|
|
|
|
),
|
|
|
|
content: Obx(() => Column(children: [
|
|
|
|
TextField(
|
|
|
|
controller: controller,
|
|
|
|
autofocus: true,
|
2024-04-02 22:08:47 +08:00
|
|
|
obscureText: !passwordVisible,
|
|
|
|
decoration: InputDecoration(
|
|
|
|
suffixIcon: IconButton(
|
|
|
|
icon: Icon(
|
|
|
|
passwordVisible ? Icons.visibility : Icons.visibility_off,
|
|
|
|
color: MyTheme.lightTheme.primaryColor),
|
|
|
|
onPressed: () {
|
|
|
|
setState(() {
|
|
|
|
passwordVisible = !passwordVisible;
|
|
|
|
});
|
|
|
|
},
|
|
|
|
),
|
|
|
|
),
|
2024-03-20 15:05:54 +08:00
|
|
|
),
|
2024-04-02 22:08:47 +08:00
|
|
|
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),
|
2024-03-20 15:05:54 +08:00
|
|
|
// NOT use Offstage to wrap LinearProgressIndicator
|
|
|
|
isInProgress.value ? const LinearProgressIndicator() : Offstage()
|
|
|
|
])),
|
|
|
|
actions: [
|
|
|
|
dialogButton(
|
|
|
|
"Cancel",
|
|
|
|
icon: Icon(Icons.close_rounded),
|
|
|
|
onPressed: cancel,
|
|
|
|
isOutline: true,
|
|
|
|
),
|
2024-04-02 22:08:47 +08:00
|
|
|
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),
|
|
|
|
)),
|
2024-03-20 15:05:54 +08:00
|
|
|
],
|
2024-04-02 22:08:47 +08:00
|
|
|
onSubmit: isInputEmpty.value ? null : () => change(controller.text),
|
2024-03-20 15:05:54 +08:00
|
|
|
onCancel: cancel,
|
|
|
|
);
|
|
|
|
});
|
|
|
|
}
|
2024-07-02 14:32:22 +08:00
|
|
|
|
|
|
|
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,
|
|
|
|
);
|
|
|
|
});
|
|
|
|
}
|