mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-04-02 17:51:28 +03:00
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) {
|
||||
|
||||
Reference in New Issue
Block a user