mirror of
https://github.com/weyne85/rustdesk.git
synced 2025-10-29 17:00:05 +00:00
@@ -2095,19 +2095,28 @@ List<String>? urlLinkToCmdArgs(Uri uri) {
|
||||
return null;
|
||||
}
|
||||
|
||||
connectMainDesktop(
|
||||
String id, {
|
||||
required bool isFileTransfer,
|
||||
required bool isTcpTunneling,
|
||||
required bool isRDP,
|
||||
bool? forceRelay,
|
||||
}) async {
|
||||
connectMainDesktop(String id,
|
||||
{required bool isFileTransfer,
|
||||
required bool isTcpTunneling,
|
||||
required bool isRDP,
|
||||
bool? forceRelay,
|
||||
String? password,
|
||||
bool? isSharedPassword}) async {
|
||||
if (isFileTransfer) {
|
||||
await rustDeskWinManager.newFileTransfer(id, forceRelay: forceRelay);
|
||||
await rustDeskWinManager.newFileTransfer(id,
|
||||
password: password,
|
||||
isSharedPassword: isSharedPassword,
|
||||
forceRelay: forceRelay);
|
||||
} else if (isTcpTunneling || isRDP) {
|
||||
await rustDeskWinManager.newPortForward(id, isRDP, forceRelay: forceRelay);
|
||||
await rustDeskWinManager.newPortForward(id, isRDP,
|
||||
password: password,
|
||||
isSharedPassword: isSharedPassword,
|
||||
forceRelay: forceRelay);
|
||||
} else {
|
||||
await rustDeskWinManager.newRemoteDesktop(id, forceRelay: forceRelay);
|
||||
await rustDeskWinManager.newRemoteDesktop(id,
|
||||
password: password,
|
||||
isSharedPassword: isSharedPassword,
|
||||
forceRelay: forceRelay);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2115,14 +2124,13 @@ connectMainDesktop(
|
||||
/// If [isFileTransfer], starts a session only for file transfer.
|
||||
/// If [isTcpTunneling], starts a session only for tcp tunneling.
|
||||
/// If [isRDP], starts a session only for rdp.
|
||||
connect(
|
||||
BuildContext context,
|
||||
String id, {
|
||||
bool isFileTransfer = false,
|
||||
bool isTcpTunneling = false,
|
||||
bool isRDP = false,
|
||||
bool forceRelay = false,
|
||||
}) async {
|
||||
connect(BuildContext context, String id,
|
||||
{bool isFileTransfer = false,
|
||||
bool isTcpTunneling = false,
|
||||
bool isRDP = false,
|
||||
bool forceRelay = false,
|
||||
String? password,
|
||||
bool? isSharedPassword}) async {
|
||||
if (id == '') return;
|
||||
if (!isDesktop || desktopType == DesktopType.main) {
|
||||
try {
|
||||
@@ -2150,6 +2158,8 @@ connect(
|
||||
isFileTransfer: isFileTransfer,
|
||||
isTcpTunneling: isTcpTunneling,
|
||||
isRDP: isRDP,
|
||||
password: password,
|
||||
isSharedPassword: isSharedPassword,
|
||||
forceRelay: forceRelay2,
|
||||
);
|
||||
} else {
|
||||
@@ -2158,6 +2168,8 @@ connect(
|
||||
'isFileTransfer': isFileTransfer,
|
||||
'isTcpTunneling': isTcpTunneling,
|
||||
'isRDP': isRDP,
|
||||
'password': password,
|
||||
'isSharedPassword': isSharedPassword,
|
||||
'forceRelay': forceRelay,
|
||||
});
|
||||
}
|
||||
@@ -2171,14 +2183,16 @@ connect(
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) => FileManagerPage(id: id),
|
||||
builder: (BuildContext context) => FileManagerPage(
|
||||
id: id, password: password, isSharedPassword: isSharedPassword),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) => RemotePage(id: id),
|
||||
builder: (BuildContext context) => RemotePage(
|
||||
id: id, password: password, isSharedPassword: isSharedPassword),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'dart:convert';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hbb/common.dart';
|
||||
import 'package:flutter_hbb/consts.dart';
|
||||
|
||||
import 'package:flutter_hbb/models/peer_model.dart';
|
||||
@@ -188,3 +189,107 @@ class RequestException implements Exception {
|
||||
return "RequestException, statusCode: $statusCode, error: $cause";
|
||||
}
|
||||
}
|
||||
|
||||
enum ShareRule {
|
||||
read(1),
|
||||
readWrite(2),
|
||||
fullControl(3);
|
||||
|
||||
const ShareRule(this.value);
|
||||
final int value;
|
||||
|
||||
static String desc(int v) {
|
||||
if (v == ShareRule.read.value) {
|
||||
return translate('Read-only');
|
||||
}
|
||||
if (v == ShareRule.readWrite.value) {
|
||||
return translate('Read/Write');
|
||||
}
|
||||
if (v == ShareRule.fullControl.value) {
|
||||
return translate('Full Control');
|
||||
}
|
||||
return v.toString();
|
||||
}
|
||||
|
||||
static String shortDesc(int v) {
|
||||
if (v == ShareRule.read.value) {
|
||||
return 'R';
|
||||
}
|
||||
if (v == ShareRule.readWrite.value) {
|
||||
return 'RW';
|
||||
}
|
||||
if (v == ShareRule.fullControl.value) {
|
||||
return 'F';
|
||||
}
|
||||
return v.toString();
|
||||
}
|
||||
|
||||
static ShareRule? fromValue(int v) {
|
||||
if (v == ShareRule.read.value) {
|
||||
return ShareRule.read;
|
||||
}
|
||||
if (v == ShareRule.readWrite.value) {
|
||||
return ShareRule.readWrite;
|
||||
}
|
||||
if (v == ShareRule.fullControl.value) {
|
||||
return ShareRule.fullControl;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
enum ShareLevel {
|
||||
user(1),
|
||||
group(2),
|
||||
team(3);
|
||||
|
||||
const ShareLevel(this.value);
|
||||
final int value;
|
||||
|
||||
static String teamName = translate('Everyone');
|
||||
}
|
||||
|
||||
class AbProfile {
|
||||
String guid;
|
||||
String name;
|
||||
String owner;
|
||||
String? note;
|
||||
int rule;
|
||||
|
||||
AbProfile(this.guid, this.name, this.owner, this.note, this.rule);
|
||||
|
||||
AbProfile.fromJson(Map<String, dynamic> json)
|
||||
: guid = json['guid'] ?? '',
|
||||
name = json['name'] ?? '',
|
||||
owner = json['owner'] ?? '',
|
||||
note = json['note'] ?? '',
|
||||
rule = json['rule'] ?? 0;
|
||||
}
|
||||
|
||||
class AbTag {
|
||||
String name;
|
||||
int color;
|
||||
|
||||
AbTag(this.name, this.color);
|
||||
|
||||
AbTag.fromJson(Map<String, dynamic> json)
|
||||
: name = json['name'] ?? '',
|
||||
color = json['color'] ?? '';
|
||||
}
|
||||
|
||||
class AbRulePayload {
|
||||
String guid;
|
||||
int level;
|
||||
String name;
|
||||
int rule;
|
||||
String? group;
|
||||
|
||||
AbRulePayload(this.guid, this.level, this.name, this.rule, {this.group});
|
||||
|
||||
AbRulePayload.fromJson(Map<String, dynamic> json)
|
||||
: guid = json['guid'] ?? '',
|
||||
level = json['level'] ?? 0,
|
||||
name = json['name'] ?? '',
|
||||
rule = json['rule'] ?? 0,
|
||||
group = json['group'];
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -9,9 +9,6 @@ import 'package:flutter_hbb/common/widgets/peer_card.dart';
|
||||
Future<List<Peer>> getAllPeers() async {
|
||||
Map<String, dynamic> recentPeers = jsonDecode(bind.mainLoadRecentPeersSync());
|
||||
Map<String, dynamic> lanPeers = jsonDecode(bind.mainLoadLanPeersSync());
|
||||
Map<String, dynamic> abPeers = jsonDecode(bind.mainLoadAbSync());
|
||||
Map<String, dynamic> groupPeers = jsonDecode(bind.mainLoadGroupSync());
|
||||
|
||||
Map<String, dynamic> combinedPeers = {};
|
||||
|
||||
void mergePeers(Map<String, dynamic> peers) {
|
||||
@@ -42,8 +39,16 @@ Future<List<Peer>> getAllPeers() async {
|
||||
|
||||
mergePeers(recentPeers);
|
||||
mergePeers(lanPeers);
|
||||
mergePeers(abPeers);
|
||||
mergePeers(groupPeers);
|
||||
for (var p in gFFI.abModel.allPeers()) {
|
||||
if (!combinedPeers.containsKey(p.id)) {
|
||||
combinedPeers[p.id] = p.toJson();
|
||||
}
|
||||
}
|
||||
for (var p in gFFI.groupModel.peers.map((e) => Peer.copy(e)).toList()) {
|
||||
if (!combinedPeers.containsKey(p.id)) {
|
||||
combinedPeers[p.id] = p.toJson();
|
||||
}
|
||||
}
|
||||
|
||||
List<Peer> parsedPeers = [];
|
||||
|
||||
@@ -181,7 +186,7 @@ class AutocompletePeerTileState extends State<AutocompletePeerTile> {
|
||||
],
|
||||
))));
|
||||
final colors = _frontN(widget.peer.tags, 25)
|
||||
.map((e) => gFFI.abModel.getTagColor(e))
|
||||
.map((e) => gFFI.abModel.getCurrentAbTagColor(e))
|
||||
.toList();
|
||||
return Tooltip(
|
||||
message: isMobile
|
||||
|
||||
@@ -2,11 +2,14 @@ import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:bot_toast/bot_toast.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_hbb/common/shared_state.dart';
|
||||
import 'package:flutter_hbb/common/widgets/setting_widgets.dart';
|
||||
import 'package:flutter_hbb/consts.dart';
|
||||
import 'package:flutter_hbb/models/peer_model.dart';
|
||||
import 'package:flutter_hbb/models/peer_tab_model.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:qr_flutter/qr_flutter.dart';
|
||||
|
||||
@@ -1583,7 +1586,7 @@ customImageQualityDialog(SessionID sessionId, String id, FFI ffi) async {
|
||||
msgBoxCommon(ffi.dialogManager, 'Custom Image Quality', content, [btnClose]);
|
||||
}
|
||||
|
||||
void deletePeerConfirmDialog(Function onSubmit, String title) async {
|
||||
void deleteConfirmDialog(Function onSubmit, String title) async {
|
||||
gFFI.dialogManager.show(
|
||||
(setState, close, context) {
|
||||
submit() async {
|
||||
@@ -1631,7 +1634,7 @@ void editAbTagDialog(
|
||||
List<dynamic> currentTags, Function(List<dynamic>) onSubmit) {
|
||||
var isInProgress = false;
|
||||
|
||||
final tags = List.of(gFFI.abModel.tags);
|
||||
final tags = List.of(gFFI.abModel.currentAbTags);
|
||||
var selectedTag = currentTags.obs;
|
||||
|
||||
gFFI.dialogManager.show((setState, close, context) {
|
||||
@@ -1909,3 +1912,178 @@ void showWindowsSessionsDialog(
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
void addPeersToAbDialog(
|
||||
List<Peer> peers,
|
||||
) async {
|
||||
Future<bool> addTo(String abname) async {
|
||||
final mapList = peers.map((e) {
|
||||
var json = e.toJson();
|
||||
// remove shared password when add to other address book
|
||||
json.remove('password');
|
||||
if (gFFI.abModel.addressbooks[abname]?.isPersonal() != true) {
|
||||
json.remove('hash');
|
||||
}
|
||||
return json;
|
||||
}).toList();
|
||||
final errMsg = await gFFI.abModel.addPeersTo(mapList, abname);
|
||||
if (errMsg == null) {
|
||||
showToast(translate('Successful'));
|
||||
return true;
|
||||
} else {
|
||||
BotToast.showText(text: errMsg, contentColor: Colors.red);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// if only one address book and it is personal, add to it directly
|
||||
if (gFFI.abModel.addressbooks.length == 1 &&
|
||||
gFFI.abModel.current.isPersonal()) {
|
||||
await addTo(gFFI.abModel.currentName.value);
|
||||
return;
|
||||
}
|
||||
|
||||
RxBool isInProgress = false.obs;
|
||||
final names = gFFI.abModel.addressBooksCanWrite();
|
||||
RxString currentName = gFFI.abModel.currentName.value.obs;
|
||||
TextEditingController controller = TextEditingController();
|
||||
if (gFFI.peerTabModel.currentTab == PeerTabIndex.ab.index) {
|
||||
names.remove(currentName.value);
|
||||
}
|
||||
if (names.isEmpty) {
|
||||
debugPrint('no address book to add peers to, should not happen');
|
||||
return;
|
||||
}
|
||||
if (!names.contains(currentName.value)) {
|
||||
currentName.value = names[0];
|
||||
}
|
||||
gFFI.dialogManager.show((setState, close, context) {
|
||||
submit() async {
|
||||
if (controller.text != gFFI.abModel.translatedName(currentName.value)) {
|
||||
BotToast.showText(
|
||||
text: 'illegal address book name: ${controller.text}',
|
||||
contentColor: Colors.red);
|
||||
return;
|
||||
}
|
||||
isInProgress.value = true;
|
||||
if (await addTo(currentName.value)) {
|
||||
close();
|
||||
}
|
||||
isInProgress.value = false;
|
||||
}
|
||||
|
||||
cancel() {
|
||||
close();
|
||||
}
|
||||
|
||||
return CustomAlertDialog(
|
||||
title: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(IconFont.addressBook, color: MyTheme.accent),
|
||||
Text(translate('Add to address book')).paddingOnly(left: 10),
|
||||
],
|
||||
),
|
||||
content: Obx(() => Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
DropdownMenu(
|
||||
initialSelection: currentName.value,
|
||||
onSelected: (value) {
|
||||
if (value != null) {
|
||||
currentName.value = value;
|
||||
}
|
||||
},
|
||||
dropdownMenuEntries: names
|
||||
.map((e) => DropdownMenuEntry(
|
||||
value: e, label: gFFI.abModel.translatedName(e)))
|
||||
.toList(),
|
||||
inputDecorationTheme: InputDecorationTheme(
|
||||
isDense: true, border: UnderlineInputBorder()),
|
||||
enableFilter: true,
|
||||
controller: controller,
|
||||
),
|
||||
// NOT use Offstage to wrap LinearProgressIndicator
|
||||
isInProgress.value ? const LinearProgressIndicator() : Offstage()
|
||||
],
|
||||
)),
|
||||
actions: [
|
||||
dialogButton(
|
||||
"Cancel",
|
||||
icon: Icon(Icons.close_rounded),
|
||||
onPressed: cancel,
|
||||
isOutline: true,
|
||||
),
|
||||
dialogButton(
|
||||
"OK",
|
||||
icon: Icon(Icons.done_rounded),
|
||||
onPressed: submit,
|
||||
),
|
||||
],
|
||||
onSubmit: submit,
|
||||
onCancel: cancel,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
void setSharedAbPasswordDialog(String abName, Peer peer) {
|
||||
TextEditingController controller = TextEditingController(text: peer.password);
|
||||
RxBool isInProgress = false.obs;
|
||||
gFFI.dialogManager.show((setState, close, context) {
|
||||
submit() async {
|
||||
isInProgress.value = true;
|
||||
bool res = await gFFI.abModel
|
||||
.changeSharedPassword(abName, peer.id, controller.text);
|
||||
close();
|
||||
isInProgress.value = false;
|
||||
if (res) {
|
||||
showToast(translate('Successful'));
|
||||
}
|
||||
}
|
||||
|
||||
cancel() {
|
||||
close();
|
||||
}
|
||||
|
||||
return CustomAlertDialog(
|
||||
title: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(Icons.key, color: MyTheme.accent),
|
||||
Text(translate('Set shared password')).paddingOnly(left: 10),
|
||||
],
|
||||
),
|
||||
content: Obx(() => Column(children: [
|
||||
TextField(
|
||||
controller: controller,
|
||||
obscureText: true,
|
||||
autofocus: true,
|
||||
),
|
||||
Row(children: [
|
||||
Icon(Icons.info, color: Colors.amber).marginOnly(right: 4),
|
||||
Text(
|
||||
translate('share_warning_tip'),
|
||||
style: TextStyle(fontSize: 12),
|
||||
)
|
||||
]).marginSymmetric(vertical: 10),
|
||||
// NOT use Offstage to wrap LinearProgressIndicator
|
||||
isInProgress.value ? const LinearProgressIndicator() : Offstage()
|
||||
])),
|
||||
actions: [
|
||||
dialogButton(
|
||||
"Cancel",
|
||||
icon: Icon(Icons.close_rounded),
|
||||
onPressed: cancel,
|
||||
isOutline: true,
|
||||
),
|
||||
dialogButton(
|
||||
"OK",
|
||||
icon: Icon(Icons.done_rounded),
|
||||
onPressed: submit,
|
||||
),
|
||||
],
|
||||
onSubmit: submit,
|
||||
onCancel: cancel,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ class _MyGroupState extends State<MyGroup> {
|
||||
alignment: Alignment.topLeft,
|
||||
child: MyGroupPeerView(
|
||||
menuPadding: widget.menuPadding,
|
||||
initPeers: gFFI.groupModel.peers)),
|
||||
getInitPeers: () => gFFI.groupModel.peers)),
|
||||
)
|
||||
],
|
||||
);
|
||||
@@ -115,7 +115,7 @@ class _MyGroupState extends State<MyGroup> {
|
||||
alignment: Alignment.topLeft,
|
||||
child: MyGroupPeerView(
|
||||
menuPadding: widget.menuPadding,
|
||||
initPeers: gFFI.groupModel.peers)),
|
||||
getInitPeers: () => gFFI.groupModel.peers)),
|
||||
)
|
||||
],
|
||||
);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:bot_toast/bot_toast.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_hbb/common/widgets/dialog.dart';
|
||||
@@ -70,12 +71,12 @@ class _PeerCardState extends State<_PeerCard>
|
||||
peerTabModel.select(peer);
|
||||
} else {
|
||||
if (!isWebDesktop) {
|
||||
connectInPeerTab(context, peer.id, widget.tab);
|
||||
connectInPeerTab(context, peer, widget.tab);
|
||||
}
|
||||
}
|
||||
},
|
||||
onDoubleTap: isWebDesktop
|
||||
? () => connectInPeerTab(context, peer.id, widget.tab)
|
||||
? () => connectInPeerTab(context, peer, widget.tab)
|
||||
: null,
|
||||
onLongPress: () {
|
||||
peerTabModel.select(peer);
|
||||
@@ -199,8 +200,9 @@ class _PeerCardState extends State<_PeerCard>
|
||||
)
|
||||
],
|
||||
);
|
||||
final colors =
|
||||
_frontN(peer.tags, 25).map((e) => gFFI.abModel.getTagColor(e)).toList();
|
||||
final colors = _frontN(peer.tags, 25)
|
||||
.map((e) => gFFI.abModel.getCurrentAbTagColor(e))
|
||||
.toList();
|
||||
return Tooltip(
|
||||
message: isMobile
|
||||
? ''
|
||||
@@ -216,6 +218,12 @@ class _PeerCardState extends State<_PeerCard>
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
if (_shouldBuildPasswordIcon(peer))
|
||||
Positioned(
|
||||
top: 2,
|
||||
left: isMobile ? 60 : 50,
|
||||
child: Icon(Icons.key, size: 12),
|
||||
),
|
||||
if (colors.isNotEmpty)
|
||||
Positioned(
|
||||
top: 2,
|
||||
@@ -310,14 +318,21 @@ class _PeerCardState extends State<_PeerCard>
|
||||
),
|
||||
);
|
||||
|
||||
final colors =
|
||||
_frontN(peer.tags, 25).map((e) => gFFI.abModel.getTagColor(e)).toList();
|
||||
final colors = _frontN(peer.tags, 25)
|
||||
.map((e) => gFFI.abModel.getCurrentAbTagColor(e))
|
||||
.toList();
|
||||
return Tooltip(
|
||||
message: peer.tags.isNotEmpty
|
||||
? '${translate('Tags')}: ${peer.tags.join(', ')}'
|
||||
: '',
|
||||
child: Stack(children: [
|
||||
child,
|
||||
if (_shouldBuildPasswordIcon(peer))
|
||||
Positioned(
|
||||
top: 4,
|
||||
left: 12,
|
||||
child: Icon(Icons.key, size: 12),
|
||||
),
|
||||
if (colors.isNotEmpty)
|
||||
Positioned(
|
||||
top: 4,
|
||||
@@ -401,6 +416,12 @@ class _PeerCardState extends State<_PeerCard>
|
||||
onPointerUp: (_) => _showPeerMenu(peer.id),
|
||||
child: build_more(context));
|
||||
|
||||
bool _shouldBuildPasswordIcon(Peer peer) {
|
||||
if (gFFI.peerTabModel.currentTab != PeerTabIndex.ab.index) return false;
|
||||
if (gFFI.abModel.current.isPersonal()) return false;
|
||||
return peer.password.isNotEmpty;
|
||||
}
|
||||
|
||||
/// Show the peer menu and handle user's choice.
|
||||
/// User might remove the peer or send a file to the peer.
|
||||
void _showPeerMenu(String id) async {
|
||||
@@ -431,7 +452,7 @@ abstract class BasePeerCard extends StatelessWidget {
|
||||
peer: peer,
|
||||
tab: tab,
|
||||
connect: (BuildContext context, String id) =>
|
||||
connectInPeerTab(context, id, tab),
|
||||
connectInPeerTab(context, peer, tab),
|
||||
popupMenuEntryBuilder: _buildPopupMenuEntry,
|
||||
);
|
||||
}
|
||||
@@ -453,7 +474,6 @@ abstract class BasePeerCard extends StatelessWidget {
|
||||
|
||||
MenuEntryBase<String> _connectCommonAction(
|
||||
BuildContext context,
|
||||
String id,
|
||||
String title, {
|
||||
bool isFileTransfer = false,
|
||||
bool isTcpTunneling = false,
|
||||
@@ -467,7 +487,7 @@ abstract class BasePeerCard extends StatelessWidget {
|
||||
proc: () {
|
||||
connectInPeerTab(
|
||||
context,
|
||||
peer.id,
|
||||
peer,
|
||||
tab,
|
||||
isFileTransfer: isFileTransfer,
|
||||
isTcpTunneling: isTcpTunneling,
|
||||
@@ -480,10 +500,9 @@ abstract class BasePeerCard extends StatelessWidget {
|
||||
}
|
||||
|
||||
@protected
|
||||
MenuEntryBase<String> _connectAction(BuildContext context, Peer peer) {
|
||||
MenuEntryBase<String> _connectAction(BuildContext context) {
|
||||
return _connectCommonAction(
|
||||
context,
|
||||
peer.id,
|
||||
(peer.alias.isEmpty
|
||||
? translate('Connect')
|
||||
: '${translate('Connect')} ${peer.id}'),
|
||||
@@ -491,20 +510,18 @@ abstract class BasePeerCard extends StatelessWidget {
|
||||
}
|
||||
|
||||
@protected
|
||||
MenuEntryBase<String> _transferFileAction(BuildContext context, String id) {
|
||||
MenuEntryBase<String> _transferFileAction(BuildContext context) {
|
||||
return _connectCommonAction(
|
||||
context,
|
||||
id,
|
||||
translate('Transfer file'),
|
||||
isFileTransfer: true,
|
||||
);
|
||||
}
|
||||
|
||||
@protected
|
||||
MenuEntryBase<String> _tcpTunnelingAction(BuildContext context, String id) {
|
||||
MenuEntryBase<String> _tcpTunnelingAction(BuildContext context) {
|
||||
return _connectCommonAction(
|
||||
context,
|
||||
id,
|
||||
translate('TCP tunneling'),
|
||||
isTcpTunneling: true,
|
||||
);
|
||||
@@ -541,7 +558,7 @@ abstract class BasePeerCard extends StatelessWidget {
|
||||
],
|
||||
)),
|
||||
proc: () {
|
||||
connectInPeerTab(context, id, tab, isRDP: true);
|
||||
connectInPeerTab(context, peer, tab, isRDP: true);
|
||||
},
|
||||
padding: menuPadding,
|
||||
dismissOnClicked: true,
|
||||
@@ -648,9 +665,8 @@ abstract class BasePeerCard extends StatelessWidget {
|
||||
onSubmit: (String newName) async {
|
||||
if (newName != oldName) {
|
||||
if (tab == PeerTabIndex.ab) {
|
||||
gFFI.abModel.changeAlias(id: id, alias: newName);
|
||||
await gFFI.abModel.changeAlias(id: id, alias: newName);
|
||||
await bind.mainSetPeerAlias(id: id, alias: newName);
|
||||
gFFI.abModel.pushAb();
|
||||
} else {
|
||||
await bind.mainSetPeerAlias(id: id, alias: newName);
|
||||
showToast(translate('Successful'));
|
||||
@@ -702,11 +718,7 @@ abstract class BasePeerCard extends StatelessWidget {
|
||||
await bind.mainLoadLanPeers();
|
||||
break;
|
||||
case PeerTabIndex.ab:
|
||||
gFFI.abModel.deletePeer(id);
|
||||
final future = gFFI.abModel.pushAb();
|
||||
if (await bind.mainPeerExists(id: peer.id)) {
|
||||
gFFI.abModel.reSyncToast(future);
|
||||
}
|
||||
await gFFI.abModel.deletePeers([id]);
|
||||
break;
|
||||
case PeerTabIndex.group:
|
||||
break;
|
||||
@@ -716,7 +728,7 @@ abstract class BasePeerCard extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
deletePeerConfirmDialog(onSubmit,
|
||||
deleteConfirmDialog(onSubmit,
|
||||
'${translate('Delete')} "${peer.alias.isEmpty ? formatID(peer.id) : peer.alias}"?');
|
||||
},
|
||||
padding: menuPadding,
|
||||
@@ -732,14 +744,14 @@ abstract class BasePeerCard extends StatelessWidget {
|
||||
style: style,
|
||||
),
|
||||
proc: () async {
|
||||
bool result = gFFI.abModel.changePassword(id, '');
|
||||
bool succ = await gFFI.abModel.changePersonalHashPassword(id, '');
|
||||
await bind.mainForgetPassword(id: id);
|
||||
bool toast = false;
|
||||
if (result) {
|
||||
toast = tab == PeerTabIndex.ab;
|
||||
gFFI.abModel.pushAb(toastIfFail: toast, toastIfSucc: toast);
|
||||
if (succ) {
|
||||
showToast(translate('Successful'));
|
||||
} else {
|
||||
BotToast.showText(
|
||||
contentColor: Colors.red, text: translate("Failed"));
|
||||
}
|
||||
if (!toast) showToast(translate('Successful'));
|
||||
},
|
||||
padding: menuPadding,
|
||||
dismissOnClicked: true,
|
||||
@@ -824,13 +836,7 @@ abstract class BasePeerCard extends StatelessWidget {
|
||||
),
|
||||
proc: () {
|
||||
() async {
|
||||
if (gFFI.abModel.isFull(true)) {
|
||||
return;
|
||||
}
|
||||
if (!gFFI.abModel.idContainBy(peer.id)) {
|
||||
gFFI.abModel.addPeer(peer);
|
||||
gFFI.abModel.pushAb();
|
||||
}
|
||||
addPeersToAbDialog([Peer.copy(peer)]);
|
||||
}();
|
||||
},
|
||||
padding: menuPadding,
|
||||
@@ -858,14 +864,14 @@ class RecentPeerCard extends BasePeerCard {
|
||||
Future<List<MenuEntryBase<String>>> _buildMenuItems(
|
||||
BuildContext context) async {
|
||||
final List<MenuEntryBase<String>> menuItems = [
|
||||
_connectAction(context, peer),
|
||||
_transferFileAction(context, peer.id),
|
||||
_connectAction(context),
|
||||
_transferFileAction(context),
|
||||
];
|
||||
|
||||
final List favs = (await bind.mainGetFav()).toList();
|
||||
|
||||
if (isDesktop && peer.platform != kPeerPlatformAndroid) {
|
||||
menuItems.add(_tcpTunnelingAction(context, peer.id));
|
||||
menuItems.add(_tcpTunnelingAction(context));
|
||||
}
|
||||
// menuItems.add(await _openNewConnInOptAction(peer.id));
|
||||
menuItems.add(await _forceAlwaysRelayAction(peer.id));
|
||||
@@ -888,9 +894,7 @@ class RecentPeerCard extends BasePeerCard {
|
||||
}
|
||||
|
||||
if (gFFI.userModel.userName.isNotEmpty) {
|
||||
if (!gFFI.abModel.idContainBy(peer.id)) {
|
||||
menuItems.add(_addToAb(peer));
|
||||
}
|
||||
menuItems.add(_addToAb(peer));
|
||||
}
|
||||
|
||||
menuItems.add(MenuEntryDivider());
|
||||
@@ -915,11 +919,11 @@ class FavoritePeerCard extends BasePeerCard {
|
||||
Future<List<MenuEntryBase<String>>> _buildMenuItems(
|
||||
BuildContext context) async {
|
||||
final List<MenuEntryBase<String>> menuItems = [
|
||||
_connectAction(context, peer),
|
||||
_transferFileAction(context, peer.id),
|
||||
_connectAction(context),
|
||||
_transferFileAction(context),
|
||||
];
|
||||
if (isDesktop && peer.platform != kPeerPlatformAndroid) {
|
||||
menuItems.add(_tcpTunnelingAction(context, peer.id));
|
||||
menuItems.add(_tcpTunnelingAction(context));
|
||||
}
|
||||
// menuItems.add(await _openNewConnInOptAction(peer.id));
|
||||
menuItems.add(await _forceAlwaysRelayAction(peer.id));
|
||||
@@ -939,9 +943,7 @@ class FavoritePeerCard extends BasePeerCard {
|
||||
}));
|
||||
|
||||
if (gFFI.userModel.userName.isNotEmpty) {
|
||||
if (!gFFI.abModel.idContainBy(peer.id)) {
|
||||
menuItems.add(_addToAb(peer));
|
||||
}
|
||||
menuItems.add(_addToAb(peer));
|
||||
}
|
||||
|
||||
menuItems.add(MenuEntryDivider());
|
||||
@@ -966,14 +968,14 @@ class DiscoveredPeerCard extends BasePeerCard {
|
||||
Future<List<MenuEntryBase<String>>> _buildMenuItems(
|
||||
BuildContext context) async {
|
||||
final List<MenuEntryBase<String>> menuItems = [
|
||||
_connectAction(context, peer),
|
||||
_transferFileAction(context, peer.id),
|
||||
_connectAction(context),
|
||||
_transferFileAction(context),
|
||||
];
|
||||
|
||||
final List favs = (await bind.mainGetFav()).toList();
|
||||
|
||||
if (isDesktop && peer.platform != kPeerPlatformAndroid) {
|
||||
menuItems.add(_tcpTunnelingAction(context, peer.id));
|
||||
menuItems.add(_tcpTunnelingAction(context));
|
||||
}
|
||||
// menuItems.add(await _openNewConnInOptAction(peer.id));
|
||||
menuItems.add(await _forceAlwaysRelayAction(peer.id));
|
||||
@@ -992,9 +994,7 @@ class DiscoveredPeerCard extends BasePeerCard {
|
||||
}
|
||||
|
||||
if (gFFI.userModel.userName.isNotEmpty) {
|
||||
if (!gFFI.abModel.idContainBy(peer.id)) {
|
||||
menuItems.add(_addToAb(peer));
|
||||
}
|
||||
menuItems.add(_addToAb(peer));
|
||||
}
|
||||
|
||||
menuItems.add(MenuEntryDivider());
|
||||
@@ -1019,31 +1019,45 @@ class AddressBookPeerCard extends BasePeerCard {
|
||||
Future<List<MenuEntryBase<String>>> _buildMenuItems(
|
||||
BuildContext context) async {
|
||||
final List<MenuEntryBase<String>> menuItems = [
|
||||
_connectAction(context, peer),
|
||||
_transferFileAction(context, peer.id),
|
||||
_connectAction(context),
|
||||
_transferFileAction(context),
|
||||
];
|
||||
if (isDesktop && peer.platform != kPeerPlatformAndroid) {
|
||||
menuItems.add(_tcpTunnelingAction(context, peer.id));
|
||||
menuItems.add(_tcpTunnelingAction(context));
|
||||
}
|
||||
// menuItems.add(await _openNewConnInOptAction(peer.id));
|
||||
menuItems.add(await _forceAlwaysRelayAction(peer.id));
|
||||
// menuItems.add(await _forceAlwaysRelayAction(peer.id));
|
||||
if (Platform.isWindows && peer.platform == kPeerPlatformWindows) {
|
||||
menuItems.add(_rdpAction(context, peer.id));
|
||||
}
|
||||
if (Platform.isWindows) {
|
||||
menuItems.add(_createShortCutAction(peer.id));
|
||||
}
|
||||
menuItems.add(MenuEntryDivider());
|
||||
menuItems.add(_renameAction(peer.id));
|
||||
if (peer.hash.isNotEmpty) {
|
||||
menuItems.add(_unrememberPasswordAction(peer.id));
|
||||
if (gFFI.abModel.current.canWrite()) {
|
||||
menuItems.add(MenuEntryDivider());
|
||||
menuItems.add(_renameAction(peer.id));
|
||||
if (gFFI.abModel.current.isPersonal() && peer.hash.isNotEmpty) {
|
||||
menuItems.add(_unrememberPasswordAction(peer.id));
|
||||
}
|
||||
if (!gFFI.abModel.current.isPersonal()) {
|
||||
menuItems.add(_changeSharedAbPassword());
|
||||
}
|
||||
if (gFFI.abModel.currentAbTags.isNotEmpty) {
|
||||
menuItems.add(_editTagAction(peer.id));
|
||||
}
|
||||
}
|
||||
if (gFFI.abModel.tags.isNotEmpty) {
|
||||
menuItems.add(_editTagAction(peer.id));
|
||||
final addressbooks = gFFI.abModel.addressBooksCanWrite();
|
||||
if (gFFI.peerTabModel.currentTab == PeerTabIndex.ab.index) {
|
||||
addressbooks.remove(gFFI.abModel.currentName.value);
|
||||
}
|
||||
if (addressbooks.isNotEmpty) {
|
||||
menuItems.add(_addToAb(peer));
|
||||
}
|
||||
menuItems.add(_existIn());
|
||||
if (gFFI.abModel.current.canWrite()) {
|
||||
menuItems.add(MenuEntryDivider());
|
||||
menuItems.add(_removeAction(peer.id));
|
||||
}
|
||||
|
||||
menuItems.add(MenuEntryDivider());
|
||||
menuItems.add(_removeAction(peer.id));
|
||||
return menuItems;
|
||||
}
|
||||
|
||||
@@ -1060,8 +1074,7 @@ class AddressBookPeerCard extends BasePeerCard {
|
||||
),
|
||||
proc: () {
|
||||
editAbTagDialog(gFFI.abModel.getPeerTags(id), (selectedTag) async {
|
||||
gFFI.abModel.changeTagForPeer(id, selectedTag);
|
||||
gFFI.abModel.pushAb();
|
||||
await gFFI.abModel.changeTagForPeers([id], selectedTag);
|
||||
});
|
||||
},
|
||||
padding: super.menuPadding,
|
||||
@@ -1073,6 +1086,52 @@ class AddressBookPeerCard extends BasePeerCard {
|
||||
@override
|
||||
Future<String> _getAlias(String id) async =>
|
||||
gFFI.abModel.find(id)?.alias ?? '';
|
||||
|
||||
MenuEntryBase<String> _changeSharedAbPassword() {
|
||||
return MenuEntryButton<String>(
|
||||
childBuilder: (TextStyle? style) => Text(
|
||||
translate('Set shared password'),
|
||||
style: style,
|
||||
),
|
||||
proc: () {
|
||||
setSharedAbPasswordDialog(gFFI.abModel.currentName.value, peer);
|
||||
},
|
||||
padding: super.menuPadding,
|
||||
dismissOnClicked: true,
|
||||
);
|
||||
}
|
||||
|
||||
MenuEntryBase<String> _existIn() {
|
||||
final names = gFFI.abModel.idExistIn(peer.id);
|
||||
final text = names.join(', ');
|
||||
return MenuEntryButton<String>(
|
||||
childBuilder: (TextStyle? style) => Text(
|
||||
translate('Exist in'),
|
||||
style: style,
|
||||
),
|
||||
proc: () {
|
||||
gFFI.dialogManager.show((setState, close, context) {
|
||||
return CustomAlertDialog(
|
||||
title: Text(translate('Exist in')),
|
||||
content: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [Text(text)]),
|
||||
actions: [
|
||||
dialogButton(
|
||||
"OK",
|
||||
icon: Icon(Icons.done_rounded),
|
||||
onPressed: close,
|
||||
),
|
||||
],
|
||||
onSubmit: close,
|
||||
onCancel: close,
|
||||
);
|
||||
});
|
||||
},
|
||||
padding: super.menuPadding,
|
||||
dismissOnClicked: true,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MyGroupPeerCard extends BasePeerCard {
|
||||
@@ -1087,11 +1146,11 @@ class MyGroupPeerCard extends BasePeerCard {
|
||||
Future<List<MenuEntryBase<String>>> _buildMenuItems(
|
||||
BuildContext context) async {
|
||||
final List<MenuEntryBase<String>> menuItems = [
|
||||
_connectAction(context, peer),
|
||||
_transferFileAction(context, peer.id),
|
||||
_connectAction(context),
|
||||
_transferFileAction(context),
|
||||
];
|
||||
if (isDesktop && peer.platform != kPeerPlatformAndroid) {
|
||||
menuItems.add(_tcpTunnelingAction(context, peer.id));
|
||||
menuItems.add(_tcpTunnelingAction(context));
|
||||
}
|
||||
// menuItems.add(await _openNewConnInOptAction(peer.id));
|
||||
// menuItems.add(await _forceAlwaysRelayAction(peer.id));
|
||||
@@ -1107,9 +1166,7 @@ class MyGroupPeerCard extends BasePeerCard {
|
||||
// menuItems.add(_unrememberPasswordAction(peer.id));
|
||||
// }
|
||||
if (gFFI.userModel.userName.isNotEmpty) {
|
||||
if (!gFFI.abModel.idContainBy(peer.id)) {
|
||||
menuItems.add(_addToAb(peer));
|
||||
}
|
||||
menuItems.add(_addToAb(peer));
|
||||
}
|
||||
return menuItems;
|
||||
}
|
||||
@@ -1305,24 +1362,32 @@ class TagPainter extends CustomPainter {
|
||||
}
|
||||
}
|
||||
|
||||
void connectInPeerTab(BuildContext context, String id, PeerTabIndex tab,
|
||||
void connectInPeerTab(BuildContext context, Peer peer, PeerTabIndex tab,
|
||||
{bool isFileTransfer = false,
|
||||
bool isTcpTunneling = false,
|
||||
bool isRDP = false}) async {
|
||||
var password = '';
|
||||
bool isSharedPassword = false;
|
||||
if (tab == PeerTabIndex.ab) {
|
||||
// If recent peer's alias is empty, set it to ab's alias
|
||||
// Because the platform is not set, it may not take effect, but it is more important not to display if the connection is not successful
|
||||
Peer? p = gFFI.abModel.find(id);
|
||||
if (p != null &&
|
||||
p.alias.isNotEmpty &&
|
||||
(await bind.mainGetPeerOption(id: id, key: "alias")).isEmpty) {
|
||||
if (peer.alias.isNotEmpty &&
|
||||
(await bind.mainGetPeerOption(id: peer.id, key: "alias")).isEmpty) {
|
||||
await bind.mainSetPeerAlias(
|
||||
id: id,
|
||||
alias: p.alias,
|
||||
id: peer.id,
|
||||
alias: peer.alias,
|
||||
);
|
||||
}
|
||||
if (!gFFI.abModel.current.isPersonal()) {
|
||||
if (peer.password.isNotEmpty) {
|
||||
password = peer.password;
|
||||
isSharedPassword = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
connect(context, id,
|
||||
connect(context, peer.id,
|
||||
password: password,
|
||||
isSharedPassword: isSharedPassword,
|
||||
isFileTransfer: isFileTransfer,
|
||||
isTcpTunneling: isTcpTunneling,
|
||||
isRDP: isRDP);
|
||||
|
||||
@@ -13,6 +13,7 @@ import 'package:flutter_hbb/desktop/widgets/material_mod_popup_menu.dart'
|
||||
as mod_menu;
|
||||
import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart';
|
||||
import 'package:flutter_hbb/models/ab_model.dart';
|
||||
import 'package:flutter_hbb/models/peer_model.dart';
|
||||
|
||||
import 'package:flutter_hbb/models/peer_tab_model.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
@@ -392,21 +393,7 @@ class _PeerTabPageState extends State<PeerTabPage>
|
||||
await bind.mainLoadLanPeers();
|
||||
break;
|
||||
case 3:
|
||||
{
|
||||
bool hasSynced = false;
|
||||
if (shouldSyncAb()) {
|
||||
for (var p in peers) {
|
||||
if (await bind.mainPeerExists(id: p.id)) {
|
||||
hasSynced = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
gFFI.abModel.deletePeers(peers.map((p) => p.id).toList());
|
||||
final future = gFFI.abModel.pushAb();
|
||||
if (hasSynced) {
|
||||
gFFI.abModel.reSyncToast(future);
|
||||
}
|
||||
}
|
||||
await gFFI.abModel.deletePeers(peers.map((p) => p.id).toList());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -415,7 +402,7 @@ class _PeerTabPageState extends State<PeerTabPage>
|
||||
if (model.currentTab != 3) showToast(translate('Successful'));
|
||||
}
|
||||
|
||||
deletePeerConfirmDialog(onSubmit, translate('Delete'));
|
||||
deleteConfirmDialog(onSubmit, translate('Delete'));
|
||||
},
|
||||
child: Tooltip(
|
||||
message: translate('Delete'),
|
||||
@@ -450,24 +437,18 @@ class _PeerTabPageState extends State<PeerTabPage>
|
||||
|
||||
Widget addSelectionToAb() {
|
||||
final model = Provider.of<PeerTabModel>(context);
|
||||
final addressbooks = gFFI.abModel.addressBooksCanWrite();
|
||||
if (model.currentTab == PeerTabIndex.ab.index) {
|
||||
addressbooks.remove(gFFI.abModel.currentName.value);
|
||||
}
|
||||
return Offstage(
|
||||
offstage:
|
||||
!gFFI.userModel.isLogin || model.currentTab == PeerTabIndex.ab.index,
|
||||
offstage: !gFFI.userModel.isLogin || addressbooks.isEmpty,
|
||||
child: _hoverAction(
|
||||
context: context,
|
||||
onTap: () {
|
||||
if (gFFI.abModel.isFull(true)) {
|
||||
return;
|
||||
}
|
||||
final peers = model.selectedPeers;
|
||||
gFFI.abModel.addPeers(peers);
|
||||
final future = gFFI.abModel.pushAb();
|
||||
final peers = model.selectedPeers.map((e) => Peer.copy(e)).toList();
|
||||
addPeersToAbDialog(peers);
|
||||
model.setMultiSelectionMode(false);
|
||||
Future.delayed(Duration.zero, () async {
|
||||
await future;
|
||||
await Future.delayed(Duration(seconds: 2)); // toast
|
||||
gFFI.abModel.isFull(true);
|
||||
});
|
||||
},
|
||||
child: Tooltip(
|
||||
message: translate('Add to address book'),
|
||||
@@ -481,15 +462,14 @@ class _PeerTabPageState extends State<PeerTabPage>
|
||||
return Offstage(
|
||||
offstage: !gFFI.userModel.isLogin ||
|
||||
model.currentTab != PeerTabIndex.ab.index ||
|
||||
gFFI.abModel.tags.isEmpty,
|
||||
gFFI.abModel.currentAbTags.isEmpty,
|
||||
child: _hoverAction(
|
||||
context: context,
|
||||
onTap: () {
|
||||
editAbTagDialog(List.empty(), (selectedTags) async {
|
||||
final peers = model.selectedPeers;
|
||||
gFFI.abModel.changeTagForPeers(
|
||||
await gFFI.abModel.changeTagForPeers(
|
||||
peers.map((p) => p.id).toList(), selectedTags);
|
||||
gFFI.abModel.pushAb();
|
||||
model.setMultiSelectionMode(false);
|
||||
showToast(translate('Successful'));
|
||||
});
|
||||
@@ -556,7 +536,8 @@ class _PeerTabPageState extends State<PeerTabPage>
|
||||
final model = Provider.of<PeerTabModel>(context);
|
||||
return [
|
||||
const PeerSearchBar().marginOnly(right: isMobile ? 0 : 13),
|
||||
_createRefresh(index: PeerTabIndex.ab, loading: gFFI.abModel.abLoading),
|
||||
_createRefresh(
|
||||
index: PeerTabIndex.ab, loading: gFFI.abModel.currentAbLoading),
|
||||
_createRefresh(
|
||||
index: PeerTabIndex.group, loading: gFFI.groupModel.groupLoading),
|
||||
Offstage(
|
||||
@@ -624,7 +605,8 @@ class _PeerTabPageState extends State<PeerTabPage>
|
||||
List<Widget> actions = [
|
||||
const PeerSearchBar(),
|
||||
if (model.currentTab == PeerTabIndex.ab.index)
|
||||
_createRefresh(index: PeerTabIndex.ab, loading: gFFI.abModel.abLoading),
|
||||
_createRefresh(
|
||||
index: PeerTabIndex.ab, loading: gFFI.abModel.currentAbLoading),
|
||||
if (model.currentTab == PeerTabIndex.group.index)
|
||||
_createRefresh(
|
||||
index: PeerTabIndex.group, loading: gFFI.groupModel.groupLoading),
|
||||
|
||||
@@ -196,18 +196,25 @@ class _PeersViewState extends State<_PeersView> with WindowListener {
|
||||
// No need to listen the currentTab change event.
|
||||
// Because the currentTab change event will trigger the peers change event,
|
||||
// and the peers change event will trigger _buildPeersView().
|
||||
final currentTab = Provider.of<PeerTabModel>(context, listen: false).currentTab;
|
||||
final hideAbTagsPanel = bind.mainGetLocalOption(key: "hideAbTagsPanel").isNotEmpty;
|
||||
final currentTab =
|
||||
Provider.of<PeerTabModel>(context, listen: false).currentTab;
|
||||
final hideAbTagsPanel =
|
||||
bind.mainGetLocalOption(key: "hideAbTagsPanel").isNotEmpty;
|
||||
return isDesktop
|
||||
? Obx(
|
||||
() => SizedBox(
|
||||
width: peerCardUiType.value != PeerUiType.list
|
||||
? 220
|
||||
: currentTab == PeerTabIndex.group.index || (currentTab == PeerTabIndex.ab.index && !hideAbTagsPanel)
|
||||
? windowWidth - 390 :
|
||||
windowWidth - 227,
|
||||
height:
|
||||
peerCardUiType.value == PeerUiType.grid ? 140 : peerCardUiType.value != PeerUiType.list ? 42 : 45,
|
||||
: currentTab == PeerTabIndex.group.index ||
|
||||
(currentTab == PeerTabIndex.ab.index &&
|
||||
!hideAbTagsPanel)
|
||||
? windowWidth - 390
|
||||
: windowWidth - 227,
|
||||
height: peerCardUiType.value == PeerUiType.grid
|
||||
? 140
|
||||
: peerCardUiType.value != PeerUiType.list
|
||||
? 42
|
||||
: 45,
|
||||
child: visibilityChild,
|
||||
),
|
||||
)
|
||||
@@ -354,7 +361,7 @@ abstract class BasePeersView extends StatelessWidget {
|
||||
final String loadEvent;
|
||||
final PeerFilter? peerFilter;
|
||||
final PeerCardBuilder peerCardBuilder;
|
||||
final RxList<Peer>? initPeers;
|
||||
final GetInitPeers? getInitPeers;
|
||||
|
||||
const BasePeersView({
|
||||
Key? key,
|
||||
@@ -362,13 +369,14 @@ abstract class BasePeersView extends StatelessWidget {
|
||||
required this.loadEvent,
|
||||
this.peerFilter,
|
||||
required this.peerCardBuilder,
|
||||
required this.initPeers,
|
||||
required this.getInitPeers,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return _PeersView(
|
||||
peers: Peers(name: name, loadEvent: loadEvent, initPeers: initPeers),
|
||||
peers:
|
||||
Peers(name: name, loadEvent: loadEvent, getInitPeers: getInitPeers),
|
||||
peerFilter: peerFilter,
|
||||
peerCardBuilder: peerCardBuilder);
|
||||
}
|
||||
@@ -385,7 +393,7 @@ class RecentPeersView extends BasePeersView {
|
||||
peer: peer,
|
||||
menuPadding: menuPadding,
|
||||
),
|
||||
initPeers: null,
|
||||
getInitPeers: null,
|
||||
);
|
||||
|
||||
@override
|
||||
@@ -407,7 +415,7 @@ class FavoritePeersView extends BasePeersView {
|
||||
peer: peer,
|
||||
menuPadding: menuPadding,
|
||||
),
|
||||
initPeers: null,
|
||||
getInitPeers: null,
|
||||
);
|
||||
|
||||
@override
|
||||
@@ -429,7 +437,7 @@ class DiscoveredPeersView extends BasePeersView {
|
||||
peer: peer,
|
||||
menuPadding: menuPadding,
|
||||
),
|
||||
initPeers: null,
|
||||
getInitPeers: null,
|
||||
);
|
||||
|
||||
@override
|
||||
@@ -445,7 +453,7 @@ class AddressBookPeersView extends BasePeersView {
|
||||
{Key? key,
|
||||
EdgeInsets? menuPadding,
|
||||
ScrollController? scrollController,
|
||||
required RxList<Peer> initPeers})
|
||||
required GetInitPeers getInitPeers})
|
||||
: super(
|
||||
key: key,
|
||||
name: 'address book peer',
|
||||
@@ -456,7 +464,7 @@ class AddressBookPeersView extends BasePeersView {
|
||||
peer: peer,
|
||||
menuPadding: menuPadding,
|
||||
),
|
||||
initPeers: initPeers,
|
||||
getInitPeers: getInitPeers,
|
||||
);
|
||||
|
||||
static bool _hitTag(List<dynamic> selectedTags, List<dynamic> idents) {
|
||||
@@ -486,7 +494,7 @@ class MyGroupPeerView extends BasePeersView {
|
||||
{Key? key,
|
||||
EdgeInsets? menuPadding,
|
||||
ScrollController? scrollController,
|
||||
required RxList<Peer> initPeers})
|
||||
required GetInitPeers getInitPeers})
|
||||
: super(
|
||||
key: key,
|
||||
name: 'group peer',
|
||||
@@ -496,7 +504,7 @@ class MyGroupPeerView extends BasePeersView {
|
||||
peer: peer,
|
||||
menuPadding: menuPadding,
|
||||
),
|
||||
initPeers: initPeers,
|
||||
getInitPeers: getInitPeers,
|
||||
);
|
||||
|
||||
static bool filter(Peer peer) {
|
||||
|
||||
@@ -359,6 +359,7 @@ class _ConnectionPageState extends State<ConnectionPage>
|
||||
platform: '',
|
||||
tags: [],
|
||||
hash: '',
|
||||
password: '',
|
||||
forceAlwaysRelay: false,
|
||||
rdpPort: '',
|
||||
rdpUsername: '',
|
||||
|
||||
@@ -800,6 +800,7 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
||||
isFileTransfer: call.arguments['isFileTransfer'],
|
||||
isTcpTunneling: call.arguments['isTcpTunneling'],
|
||||
isRDP: call.arguments['isRDP'],
|
||||
password: call.arguments['password'],
|
||||
forceRelay: call.arguments['forceRelay'],
|
||||
);
|
||||
} else if (call.method == kWindowEventMoveTabToNewWindow) {
|
||||
|
||||
@@ -53,11 +53,13 @@ class FileManagerPage extends StatefulWidget {
|
||||
{Key? key,
|
||||
required this.id,
|
||||
required this.password,
|
||||
required this.isSharedPassword,
|
||||
required this.tabController,
|
||||
this.forceRelay})
|
||||
: super(key: key);
|
||||
final String id;
|
||||
final String? password;
|
||||
final bool? isSharedPassword;
|
||||
final bool? forceRelay;
|
||||
final DesktopTabController tabController;
|
||||
|
||||
@@ -84,6 +86,7 @@ class _FileManagerPageState extends State<FileManagerPage>
|
||||
_ffi.start(widget.id,
|
||||
isFileTransfer: true,
|
||||
password: widget.password,
|
||||
isSharedPassword: widget.isSharedPassword,
|
||||
forceRelay: widget.forceRelay);
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_ffi.dialogManager
|
||||
|
||||
@@ -45,6 +45,7 @@ class _FileManagerTabPageState extends State<FileManagerTabPage> {
|
||||
key: ValueKey(params['id']),
|
||||
id: params['id'],
|
||||
password: params['password'],
|
||||
isSharedPassword: params['isSharedPassword'],
|
||||
tabController: tabController,
|
||||
forceRelay: params['forceRelay'],
|
||||
)));
|
||||
@@ -74,6 +75,7 @@ class _FileManagerTabPageState extends State<FileManagerTabPage> {
|
||||
key: ValueKey(id),
|
||||
id: id,
|
||||
password: args['password'],
|
||||
isSharedPassword: args['isSharedPassword'],
|
||||
tabController: tabController,
|
||||
forceRelay: args['forceRelay'],
|
||||
)));
|
||||
|
||||
@@ -25,19 +25,21 @@ class _PortForward {
|
||||
}
|
||||
|
||||
class PortForwardPage extends StatefulWidget {
|
||||
const PortForwardPage(
|
||||
{Key? key,
|
||||
required this.id,
|
||||
required this.password,
|
||||
required this.tabController,
|
||||
required this.isRDP,
|
||||
this.forceRelay})
|
||||
: super(key: key);
|
||||
const PortForwardPage({
|
||||
Key? key,
|
||||
required this.id,
|
||||
required this.password,
|
||||
required this.tabController,
|
||||
required this.isRDP,
|
||||
required this.isSharedPassword,
|
||||
this.forceRelay,
|
||||
}) : super(key: key);
|
||||
final String id;
|
||||
final String? password;
|
||||
final DesktopTabController tabController;
|
||||
final bool isRDP;
|
||||
final bool? forceRelay;
|
||||
final bool? isSharedPassword;
|
||||
|
||||
@override
|
||||
State<PortForwardPage> createState() => _PortForwardPageState();
|
||||
@@ -58,6 +60,7 @@ class _PortForwardPageState extends State<PortForwardPage>
|
||||
_ffi.start(widget.id,
|
||||
isPortForward: true,
|
||||
password: widget.password,
|
||||
isSharedPassword: widget.isSharedPassword,
|
||||
forceRelay: widget.forceRelay,
|
||||
isRdp: widget.isRDP);
|
||||
Get.put(_ffi, tag: 'pf_${widget.id}');
|
||||
|
||||
@@ -44,6 +44,7 @@ class _PortForwardTabPageState extends State<PortForwardTabPage> {
|
||||
key: ValueKey(params['id']),
|
||||
id: params['id'],
|
||||
password: params['password'],
|
||||
isSharedPassword: params['isSharedPassword'],
|
||||
tabController: tabController,
|
||||
isRDP: isRDP,
|
||||
forceRelay: params['forceRelay'],
|
||||
@@ -79,6 +80,7 @@ class _PortForwardTabPageState extends State<PortForwardTabPage> {
|
||||
key: ValueKey(args['id']),
|
||||
id: id,
|
||||
password: args['password'],
|
||||
isSharedPassword: args['isSharedPassword'],
|
||||
isRDP: isRDP,
|
||||
tabController: tabController,
|
||||
forceRelay: args['forceRelay'],
|
||||
|
||||
@@ -45,6 +45,7 @@ class RemotePage extends StatefulWidget {
|
||||
required this.tabController,
|
||||
this.switchUuid,
|
||||
this.forceRelay,
|
||||
this.isSharedPassword,
|
||||
}) : super(key: key);
|
||||
|
||||
final String id;
|
||||
@@ -56,6 +57,7 @@ class RemotePage extends StatefulWidget {
|
||||
final ToolbarState toolbarState;
|
||||
final String? switchUuid;
|
||||
final bool? forceRelay;
|
||||
final bool? isSharedPassword;
|
||||
final SimpleWrapper<State<RemotePage>?> _lastState = SimpleWrapper(null);
|
||||
final DesktopTabController tabController;
|
||||
|
||||
@@ -111,6 +113,7 @@ class _RemotePageState extends State<RemotePage>
|
||||
_ffi.start(
|
||||
widget.id,
|
||||
password: widget.password,
|
||||
isSharedPassword: widget.isSharedPassword,
|
||||
switchUuid: widget.switchUuid,
|
||||
forceRelay: widget.forceRelay,
|
||||
tabWindowId: widget.tabWindowId,
|
||||
|
||||
@@ -95,6 +95,7 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
|
||||
tabController: tabController,
|
||||
switchUuid: params['switch_uuid'],
|
||||
forceRelay: params['forceRelay'],
|
||||
isSharedPassword: params['isSharedPassword'],
|
||||
),
|
||||
));
|
||||
_update_remote_count();
|
||||
@@ -153,6 +154,7 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
|
||||
tabController: tabController,
|
||||
switchUuid: switchUuid,
|
||||
forceRelay: args['forceRelay'],
|
||||
isSharedPassword: args['isSharedPassword'],
|
||||
),
|
||||
));
|
||||
} else if (call.method == kWindowDisableGrabKeyboard) {
|
||||
|
||||
@@ -166,6 +166,7 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
||||
platform: '',
|
||||
tags: [],
|
||||
hash: '',
|
||||
password: '',
|
||||
forceAlwaysRelay: false,
|
||||
rdpPort: '',
|
||||
rdpUsername: '',
|
||||
|
||||
@@ -12,8 +12,12 @@ import '../../common.dart';
|
||||
import '../../common/widgets/dialog.dart';
|
||||
|
||||
class FileManagerPage extends StatefulWidget {
|
||||
FileManagerPage({Key? key, required this.id}) : super(key: key);
|
||||
FileManagerPage(
|
||||
{Key? key, required this.id, this.password, this.isSharedPassword})
|
||||
: super(key: key);
|
||||
final String id;
|
||||
final String? password;
|
||||
final bool? isSharedPassword;
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _FileManagerPageState();
|
||||
@@ -68,7 +72,10 @@ class _FileManagerPageState extends State<FileManagerPage> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
gFFI.start(widget.id, isFileTransfer: true);
|
||||
gFFI.start(widget.id,
|
||||
isFileTransfer: true,
|
||||
password: widget.password,
|
||||
isSharedPassword: widget.isSharedPassword);
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
gFFI.dialogManager
|
||||
.showLoading(translate('Connecting...'), onCancel: closeConnection);
|
||||
|
||||
@@ -25,9 +25,12 @@ import '../widgets/dialog.dart';
|
||||
final initText = '1' * 1024;
|
||||
|
||||
class RemotePage extends StatefulWidget {
|
||||
RemotePage({Key? key, required this.id}) : super(key: key);
|
||||
RemotePage({Key? key, required this.id, this.password, this.isSharedPassword})
|
||||
: super(key: key);
|
||||
|
||||
final String id;
|
||||
final String? password;
|
||||
final bool? isSharedPassword;
|
||||
|
||||
@override
|
||||
State<RemotePage> createState() => _RemotePageState();
|
||||
@@ -54,7 +57,11 @@ class _RemotePageState extends State<RemotePage> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
gFFI.start(widget.id);
|
||||
gFFI.start(
|
||||
widget.id,
|
||||
password: widget.password,
|
||||
isSharedPassword: widget.isSharedPassword,
|
||||
);
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: []);
|
||||
gFFI.dialogManager
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -355,10 +355,8 @@ class FfiModel with ChangeNotifier {
|
||||
final id = evt['id'];
|
||||
final password = evt['password'];
|
||||
if (id != null && password != null) {
|
||||
if (gFFI.abModel
|
||||
.changePassword(id.toString(), password.toString())) {
|
||||
gFFI.abModel.pushAb(toastIfFail: false, toastIfSucc: false);
|
||||
}
|
||||
gFFI.abModel
|
||||
.changePersonalHashPassword(id.toString(), password.toString());
|
||||
}
|
||||
}
|
||||
} else if (name == "cm_file_transfer_log") {
|
||||
@@ -2179,6 +2177,7 @@ class FFI {
|
||||
bool isRdp = false,
|
||||
String? switchUuid,
|
||||
String? password,
|
||||
bool? isSharedPassword,
|
||||
bool? forceRelay,
|
||||
int? tabWindowId,
|
||||
int? display,
|
||||
@@ -2212,6 +2211,7 @@ class FFI {
|
||||
switchUuid: switchUuid ?? '',
|
||||
forceRelay: forceRelay ?? false,
|
||||
password: password ?? '',
|
||||
isSharedPassword: isSharedPassword ?? false,
|
||||
);
|
||||
} else if (display != null) {
|
||||
if (displays == null) {
|
||||
|
||||
@@ -7,7 +7,8 @@ import 'package:collection/collection.dart';
|
||||
|
||||
class Peer {
|
||||
final String id;
|
||||
String hash;
|
||||
String hash; // personal ab hash password
|
||||
String password; // shared ab password
|
||||
String username; // pc username
|
||||
String hostname;
|
||||
String platform;
|
||||
@@ -18,6 +19,7 @@ class Peer {
|
||||
String rdpUsername;
|
||||
bool online = false;
|
||||
String loginName; //login username
|
||||
bool? sameServer;
|
||||
|
||||
String getId() {
|
||||
if (alias != '') {
|
||||
@@ -29,6 +31,7 @@ class Peer {
|
||||
Peer.fromJson(Map<String, dynamic> json)
|
||||
: id = json['id'] ?? '',
|
||||
hash = json['hash'] ?? '',
|
||||
password = json['password'] ?? '',
|
||||
username = json['username'] ?? '',
|
||||
hostname = json['hostname'] ?? '',
|
||||
platform = json['platform'] ?? '',
|
||||
@@ -37,12 +40,14 @@ class Peer {
|
||||
forceAlwaysRelay = json['forceAlwaysRelay'] == 'true',
|
||||
rdpPort = json['rdpPort'] ?? '',
|
||||
rdpUsername = json['rdpUsername'] ?? '',
|
||||
loginName = json['loginName'] ?? '';
|
||||
loginName = json['loginName'] ?? '',
|
||||
sameServer = json['same_server'];
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return <String, dynamic>{
|
||||
"id": id,
|
||||
"hash": hash,
|
||||
"password": password,
|
||||
"username": username,
|
||||
"hostname": hostname,
|
||||
"platform": platform,
|
||||
@@ -52,13 +57,43 @@ class Peer {
|
||||
"rdpPort": rdpPort,
|
||||
"rdpUsername": rdpUsername,
|
||||
'loginName': loginName,
|
||||
'same_server': sameServer,
|
||||
};
|
||||
}
|
||||
|
||||
Map<String, dynamic> toAbUploadJson() {
|
||||
Map<String, dynamic> toPersonalAbUploadJson(bool includingHash) {
|
||||
var res = <String, dynamic>{
|
||||
"id": id,
|
||||
"username": username,
|
||||
"hostname": hostname,
|
||||
"platform": platform,
|
||||
"alias": alias,
|
||||
"tags": tags,
|
||||
};
|
||||
if (includingHash) {
|
||||
res['hash'] = hash;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
Map<String, dynamic> toSharedAbUploadJson(bool includingPassword) {
|
||||
var res = <String, dynamic>{
|
||||
"id": id,
|
||||
"username": username,
|
||||
"hostname": hostname,
|
||||
"platform": platform,
|
||||
"alias": alias,
|
||||
"tags": tags,
|
||||
};
|
||||
if (includingPassword) {
|
||||
res['password'] = password;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
Map<String, dynamic> toSharedAbCacheJson() {
|
||||
return <String, dynamic>{
|
||||
"id": id,
|
||||
"hash": hash,
|
||||
"username": username,
|
||||
"hostname": hostname,
|
||||
"platform": platform,
|
||||
@@ -80,6 +115,7 @@ class Peer {
|
||||
Peer({
|
||||
required this.id,
|
||||
required this.hash,
|
||||
required this.password,
|
||||
required this.username,
|
||||
required this.hostname,
|
||||
required this.platform,
|
||||
@@ -89,12 +125,14 @@ class Peer {
|
||||
required this.rdpPort,
|
||||
required this.rdpUsername,
|
||||
required this.loginName,
|
||||
this.sameServer,
|
||||
});
|
||||
|
||||
Peer.loading()
|
||||
: this(
|
||||
id: '...',
|
||||
hash: '',
|
||||
password: '',
|
||||
username: '...',
|
||||
hostname: '...',
|
||||
platform: '...',
|
||||
@@ -108,6 +146,7 @@ class Peer {
|
||||
bool equal(Peer other) {
|
||||
return id == other.id &&
|
||||
hash == other.hash &&
|
||||
password == other.password &&
|
||||
username == other.username &&
|
||||
hostname == other.hostname &&
|
||||
platform == other.platform &&
|
||||
@@ -121,33 +160,38 @@ class Peer {
|
||||
|
||||
Peer.copy(Peer other)
|
||||
: this(
|
||||
id: other.id,
|
||||
hash: other.hash,
|
||||
username: other.username,
|
||||
hostname: other.hostname,
|
||||
platform: other.platform,
|
||||
alias: other.alias,
|
||||
tags: other.tags.toList(),
|
||||
forceAlwaysRelay: other.forceAlwaysRelay,
|
||||
rdpPort: other.rdpPort,
|
||||
rdpUsername: other.rdpUsername,
|
||||
loginName: other.loginName,
|
||||
);
|
||||
id: other.id,
|
||||
hash: other.hash,
|
||||
password: other.password,
|
||||
username: other.username,
|
||||
hostname: other.hostname,
|
||||
platform: other.platform,
|
||||
alias: other.alias,
|
||||
tags: other.tags.toList(),
|
||||
forceAlwaysRelay: other.forceAlwaysRelay,
|
||||
rdpPort: other.rdpPort,
|
||||
rdpUsername: other.rdpUsername,
|
||||
loginName: other.loginName,
|
||||
sameServer: other.sameServer);
|
||||
}
|
||||
|
||||
enum UpdateEvent { online, load }
|
||||
|
||||
typedef GetInitPeers = RxList<Peer> Function();
|
||||
|
||||
class Peers extends ChangeNotifier {
|
||||
final String name;
|
||||
final String loadEvent;
|
||||
List<Peer> peers = List.empty(growable: true);
|
||||
final RxList<Peer>? initPeers;
|
||||
final GetInitPeers? getInitPeers;
|
||||
UpdateEvent event = UpdateEvent.load;
|
||||
static const _cbQueryOnlines = 'callback_query_onlines';
|
||||
|
||||
Peers(
|
||||
{required this.name, required this.initPeers, required this.loadEvent}) {
|
||||
peers = initPeers ?? [];
|
||||
{required this.name,
|
||||
required this.getInitPeers,
|
||||
required this.loadEvent}) {
|
||||
peers = getInitPeers?.call() ?? [];
|
||||
platformFFI.registerEventHandler(_cbQueryOnlines, name, (evt) async {
|
||||
_updateOnlineState(evt);
|
||||
});
|
||||
@@ -198,8 +242,8 @@ class Peers extends ChangeNotifier {
|
||||
|
||||
void _updatePeers(Map<String, dynamic> evt) {
|
||||
final onlineStates = _getOnlineStates();
|
||||
if (initPeers != null) {
|
||||
peers = initPeers!;
|
||||
if (getInitPeers != null) {
|
||||
peers = getInitPeers?.call() ?? [];
|
||||
} else {
|
||||
peers = _decodePeers(evt['peers']);
|
||||
}
|
||||
|
||||
@@ -194,6 +194,7 @@ class RustDeskMultiWindowManager {
|
||||
bool? forceRelay,
|
||||
String? switchUuid,
|
||||
bool? isRDP,
|
||||
bool? isSharedPassword,
|
||||
}) async {
|
||||
var params = {
|
||||
"type": type.index,
|
||||
@@ -207,6 +208,9 @@ class RustDeskMultiWindowManager {
|
||||
if (isRDP != null) {
|
||||
params['isRDP'] = isRDP;
|
||||
}
|
||||
if (isSharedPassword != null) {
|
||||
params['isSharedPassword'] = isSharedPassword;
|
||||
}
|
||||
final msg = jsonEncode(params);
|
||||
|
||||
// separate window for file transfer is not supported
|
||||
@@ -228,6 +232,7 @@ class RustDeskMultiWindowManager {
|
||||
Future<MultiWindowCallResult> newRemoteDesktop(
|
||||
String remoteId, {
|
||||
String? password,
|
||||
bool? isSharedPassword,
|
||||
String? switchUuid,
|
||||
bool? forceRelay,
|
||||
}) async {
|
||||
@@ -239,11 +244,12 @@ class RustDeskMultiWindowManager {
|
||||
password: password,
|
||||
forceRelay: forceRelay,
|
||||
switchUuid: switchUuid,
|
||||
isSharedPassword: isSharedPassword,
|
||||
);
|
||||
}
|
||||
|
||||
Future<MultiWindowCallResult> newFileTransfer(String remoteId,
|
||||
{String? password, bool? forceRelay}) async {
|
||||
{String? password, bool? isSharedPassword, bool? forceRelay}) async {
|
||||
return await newSession(
|
||||
WindowType.FileTransfer,
|
||||
kWindowEventNewFileTransfer,
|
||||
@@ -251,11 +257,12 @@ class RustDeskMultiWindowManager {
|
||||
_fileTransferWindows,
|
||||
password: password,
|
||||
forceRelay: forceRelay,
|
||||
isSharedPassword: isSharedPassword,
|
||||
);
|
||||
}
|
||||
|
||||
Future<MultiWindowCallResult> newPortForward(String remoteId, bool isRDP,
|
||||
{String? password, bool? forceRelay}) async {
|
||||
{String? password, bool? isSharedPassword, bool? forceRelay}) async {
|
||||
return await newSession(
|
||||
WindowType.PortForward,
|
||||
kWindowEventNewPortForward,
|
||||
@@ -264,6 +271,7 @@ class RustDeskMultiWindowManager {
|
||||
password: password,
|
||||
forceRelay: forceRelay,
|
||||
isRDP: isRDP,
|
||||
isSharedPassword: isSharedPassword,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -616,6 +616,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.80.1"
|
||||
flutter_simple_treeview:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_simple_treeview
|
||||
sha256: ad4978d2668dd078d3a09966832da111bef9102dd636e572c50c80133b7ff4d9
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
flutter_svg:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
||||
@@ -104,6 +104,7 @@ dependencies:
|
||||
pull_down_button: ^0.9.3
|
||||
device_info_plus: ^9.1.0
|
||||
qr_flutter: ^4.1.0
|
||||
flutter_simple_treeview: ^3.0.2
|
||||
|
||||
dev_dependencies:
|
||||
icons_launcher: ^2.0.4
|
||||
|
||||
Reference in New Issue
Block a user