diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index 4c298d917..b9077b0cb 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -923,7 +923,8 @@ bool option2bool(String option, String value) { } else if (option.startsWith("allow-") || option == "stop-service" || option == "direct-server" || - option == "stop-rendezvous-service") { + option == "stop-rendezvous-service" || + option == "force-always-relay") { res = value == "Y"; } else { assert(false); @@ -939,7 +940,8 @@ String bool2option(String option, bool b) { } else if (option.startsWith('allow-') || option == "stop-service" || option == "direct-server" || - option == "stop-rendezvous-service") { + option == "stop-rendezvous-service" || + option == "force-always-relay") { res = b ? 'Y' : ''; } else { assert(false); diff --git a/flutter/lib/common/widgets/peer_card.dart b/flutter/lib/common/widgets/peer_card.dart index 4486a6e27..449b67092 100644 --- a/flutter/lib/common/widgets/peer_card.dart +++ b/flutter/lib/common/widgets/peer_card.dart @@ -56,6 +56,9 @@ class _PeerCardState extends State<_PeerCard> Widget _buildMobile() { final peer = super.widget.peer; + final name = + '${peer.username}${peer.username.isNotEmpty && peer.hostname.isNotEmpty ? '@' : ''}${peer.hostname}'; + return Card( margin: EdgeInsets.symmetric(horizontal: 2), child: GestureDetector( @@ -90,7 +93,7 @@ class _PeerCardState extends State<_PeerCard> ? formatID(peer.id) : peer.alias) ]), - Text('${peer.username}@${peer.hostname}') + Text(name) ], ).paddingOnly(left: 8.0), ), @@ -145,6 +148,8 @@ class _PeerCardState extends State<_PeerCard> Widget _buildPeerTile( BuildContext context, Peer peer, Rx deco) { + final name = + '${peer.username}${peer.username.isNotEmpty && peer.hostname.isNotEmpty ? '@' : ''}${peer.hostname}'; final greyStyle = TextStyle( fontSize: 11, color: Theme.of(context).textTheme.titleLarge?.color?.withOpacity(0.6)); @@ -184,7 +189,7 @@ class _PeerCardState extends State<_PeerCard> Align( alignment: Alignment.centerLeft, child: Text( - '${peer.username}@${peer.hostname}', + name, style: greyStyle, textAlign: TextAlign.start, overflow: TextOverflow.ellipsis, @@ -206,7 +211,8 @@ class _PeerCardState extends State<_PeerCard> Widget _buildPeerCard( BuildContext context, Peer peer, Rx deco) { - final name = '${peer.username}@${peer.hostname}'; + final name = + '${peer.username}${peer.username.isNotEmpty && peer.hostname.isNotEmpty ? '@' : ''}${peer.hostname}'; return Card( color: Colors.transparent, elevation: 0, @@ -310,11 +316,20 @@ class _PeerCardState extends State<_PeerCard> bool get wantKeepAlive => true; } +enum CardType { + recent, + fav, + lan, + ab, +} + abstract class BasePeerCard extends StatelessWidget { final Peer peer; final EdgeInsets? menuPadding; + final CardType cardType; - BasePeerCard({required this.peer, this.menuPadding, Key? key}) + BasePeerCard( + {required this.peer, required this.cardType, this.menuPadding, Key? key}) : super(key: key); @override @@ -419,7 +434,7 @@ abstract class BasePeerCard extends StatelessWidget { if (Navigator.canPop(context)) { Navigator.pop(context); } - _rdpDialog(id); + _rdpDialog(id, cardType); }, )), )) @@ -471,17 +486,16 @@ abstract class BasePeerCard extends StatelessWidget { switchType: SwitchType.scheckbox, text: translate('Always connect via relay'), getter: () async { - return (await bind.mainGetPeerOption(id: id, key: option)).isNotEmpty; + if (cardType == CardType.ab) { + return gFFI.abModel.find(id)?.forceAlwaysRelay ?? false; + } else { + return (await bind.mainGetPeerOption(id: id, key: option)).isNotEmpty; + } }, setter: (bool v) async { - String value; - String oldValue = await bind.mainGetPeerOption(id: id, key: option); - if (oldValue.isEmpty) { - value = 'Y'; - } else { - value = ''; - } - await bind.mainSetPeerOption(id: id, key: option, value: value); + gFFI.abModel.setPeerForceAlwaysRelay(id, v); + await bind.mainSetPeerOption( + id: id, key: option, value: bool2option('force-always-relay', v)); }, padding: menuPadding, dismissOnClicked: true, @@ -489,14 +503,14 @@ abstract class BasePeerCard extends StatelessWidget { } @protected - MenuEntryBase _renameAction(String id, bool isAddressBook) { + MenuEntryBase _renameAction(String id) { return MenuEntryButton( childBuilder: (TextStyle? style) => Text( translate('Rename'), style: style, ), proc: () { - _rename(id, isAddressBook); + _rename(id); }, padding: menuPadding, dismissOnClicked: true, @@ -606,33 +620,22 @@ abstract class BasePeerCard extends StatelessWidget { ); } - void _rename(String id, bool isAddressBook) async { + void _rename(String id) async { RxBool isInProgress = false.obs; - var name = peer.alias; - var controller = TextEditingController(text: name); - if (isAddressBook) { - final peer = gFFI.abModel.peers.firstWhereOrNull((p) => id == p.id); - if (peer == null) { - // this should not happen - } else { - name = peer.alias; - } + String name; + if (cardType == CardType.ab) { + name = gFFI.abModel.find(id)?.alias ?? ""; + } else { + name = await bind.mainGetPeerOption(id: id, key: 'alias'); } + var controller = TextEditingController(text: name); gFFI.dialogManager.show((setState, close) { submit() async { isInProgress.value = true; - name = controller.text; + String name = controller.text.trim(); await bind.mainSetPeerAlias(id: id, alias: name); - if (isAddressBook) { - gFFI.abModel.setPeerAlias(id, name); - await gFFI.abModel.pushAb(); - } - if (isAddressBook) { - gFFI.abModel.pullAb(); - } else { - bind.mainLoadRecentPeers(); - bind.mainLoadFavPeers(); - } + gFFI.abModel.setPeerAlias(id, name); + update(); close(); isInProgress.value = false; } @@ -666,11 +669,32 @@ abstract class BasePeerCard extends StatelessWidget { ); }); } + + void update() { + switch (cardType) { + case CardType.recent: + bind.mainLoadRecentPeers(); + break; + case CardType.fav: + bind.mainLoadFavPeers(); + break; + case CardType.lan: + bind.mainLoadLanPeers(); + break; + case CardType.ab: + gFFI.abModel.pullAb(); + break; + } + } } class RecentPeerCard extends BasePeerCard { RecentPeerCard({required Peer peer, EdgeInsets? menuPadding, Key? key}) - : super(peer: peer, menuPadding: menuPadding, key: key); + : super( + peer: peer, + cardType: CardType.recent, + menuPadding: menuPadding, + key: key); @override Future>> _buildMenuItems( @@ -691,7 +715,7 @@ class RecentPeerCard extends BasePeerCard { menuItems.add(_createShortCutAction(peer.id)); } menuItems.add(MenuEntryDivider()); - menuItems.add(_renameAction(peer.id, false)); + menuItems.add(_renameAction(peer.id)); menuItems.add(_removeAction(peer.id, () async { await bind.mainLoadRecentPeers(); })); @@ -708,7 +732,11 @@ class RecentPeerCard extends BasePeerCard { class FavoritePeerCard extends BasePeerCard { FavoritePeerCard({required Peer peer, EdgeInsets? menuPadding, Key? key}) - : super(peer: peer, menuPadding: menuPadding, key: key); + : super( + peer: peer, + cardType: CardType.fav, + menuPadding: menuPadding, + key: key); @override Future>> _buildMenuItems( @@ -729,7 +757,7 @@ class FavoritePeerCard extends BasePeerCard { menuItems.add(_createShortCutAction(peer.id)); } menuItems.add(MenuEntryDivider()); - menuItems.add(_renameAction(peer.id, false)); + menuItems.add(_renameAction(peer.id)); menuItems.add(_removeAction(peer.id, () async { await bind.mainLoadFavPeers(); })); @@ -748,7 +776,11 @@ class FavoritePeerCard extends BasePeerCard { class DiscoveredPeerCard extends BasePeerCard { DiscoveredPeerCard({required Peer peer, EdgeInsets? menuPadding, Key? key}) - : super(peer: peer, menuPadding: menuPadding, key: key); + : super( + peer: peer, + cardType: CardType.lan, + menuPadding: menuPadding, + key: key); @override Future>> _buildMenuItems( @@ -779,7 +811,11 @@ class DiscoveredPeerCard extends BasePeerCard { class AddressBookPeerCard extends BasePeerCard { AddressBookPeerCard({required Peer peer, EdgeInsets? menuPadding, Key? key}) - : super(peer: peer, menuPadding: menuPadding, key: key); + : super( + peer: peer, + cardType: CardType.ab, + menuPadding: menuPadding, + key: key); @override Future>> _buildMenuItems( @@ -800,7 +836,7 @@ class AddressBookPeerCard extends BasePeerCard { menuItems.add(_createShortCutAction(peer.id)); } menuItems.add(MenuEntryDivider()); - menuItems.add(_renameAction(peer.id, false)); + menuItems.add(_renameAction(peer.id)); menuItems.add(_removeAction(peer.id, () async {})); if (await bind.mainPeerHasPassword(id: peer.id)) { menuItems.add(_unrememberPasswordAction(peer.id)); @@ -901,23 +937,33 @@ class AddressBookPeerCard extends BasePeerCard { } } -void _rdpDialog(String id) async { - final portController = TextEditingController( - text: await bind.mainGetPeerOption(id: id, key: 'rdp_port')); - final userController = TextEditingController( - text: await bind.mainGetPeerOption(id: id, key: 'rdp_username')); +void _rdpDialog(String id, CardType card) async { + String port, username; + if (card == CardType.ab) { + port = gFFI.abModel.find(id)?.rdpPort ?? ''; + username = gFFI.abModel.find(id)?.rdpUsername ?? ''; + } else { + port = await bind.mainGetPeerOption(id: id, key: 'rdp_port'); + username = await bind.mainGetPeerOption(id: id, key: 'rdp_username'); + } + + final portController = TextEditingController(text: port); + final userController = TextEditingController(text: username); final passwordController = TextEditingController( text: await bind.mainGetPeerOption(id: id, key: 'rdp_password')); RxBool secure = true.obs; gFFI.dialogManager.show((setState, close) { submit() async { + String port = portController.text.trim(); + String username = userController.text; + String password = passwordController.text; + await bind.mainSetPeerOption(id: id, key: 'rdp_port', value: port); await bind.mainSetPeerOption( - id: id, key: 'rdp_port', value: portController.text.trim()); + id: id, key: 'rdp_username', value: username); await bind.mainSetPeerOption( - id: id, key: 'rdp_username', value: userController.text); - await bind.mainSetPeerOption( - id: id, key: 'rdp_password', value: passwordController.text); + id: id, key: 'rdp_password', value: password); + gFFI.abModel.setRdp(id, port, username); close(); } diff --git a/flutter/lib/models/ab_model.dart b/flutter/lib/models/ab_model.dart index a24c01366..ab5a7cb80 100644 --- a/flutter/lib/models/ab_model.dart +++ b/flutter/lib/models/ab_model.dart @@ -122,6 +122,10 @@ class AbModel { } } + Peer? find(String id) { + return peers.firstWhereOrNull((e) => e.id == id); + } + bool idContainBy(String id) { return peers.where((element) => element.id == id).isNotEmpty; } @@ -160,13 +164,28 @@ class AbModel { } } - void setPeerAlias(String id, String value) { + Future setPeerAlias(String id, String value) async { final it = peers.where((p0) => p0.id == id); - if (it.isEmpty) { - debugPrint("$id is not exists"); - return; - } else { + if (it.isNotEmpty) { it.first.alias = value; + await pushAb(); + } + } + + Future setPeerForceAlwaysRelay(String id, bool value) async { + final it = peers.where((p0) => p0.id == id); + if (it.isNotEmpty) { + it.first.forceAlwaysRelay = value; + await pushAb(); + } + } + + Future setRdp(String id, String port, String username) async { + final it = peers.where((p0) => p0.id == id); + if (it.isNotEmpty) { + it.first.rdpPort = port; + it.first.rdpUsername = username; + await pushAb(); } } diff --git a/flutter/lib/models/peer_model.dart b/flutter/lib/models/peer_model.dart index 6dd94bcf4..ad5183ae3 100644 --- a/flutter/lib/models/peer_model.dart +++ b/flutter/lib/models/peer_model.dart @@ -9,6 +9,9 @@ class Peer { final String platform; String alias; List tags; + bool forceAlwaysRelay = false; + String rdpPort; + String rdpUsername; bool online = false; Peer.fromJson(Map json) @@ -17,7 +20,10 @@ class Peer { hostname = json['hostname'] ?? '', platform = json['platform'] ?? '', alias = json['alias'] ?? '', - tags = json['tags'] ?? []; + tags = json['tags'] ?? [], + forceAlwaysRelay = json['forceAlwaysRelay'] == 'true', + rdpPort = json['rdpPort'] ?? '', + rdpUsername = json['rdpUsername'] ?? ''; Map toJson() { return { @@ -27,6 +33,9 @@ class Peer { "platform": platform, "alias": alias, "tags": tags, + "forceAlwaysRelay": forceAlwaysRelay.toString(), + "rdpPort": rdpPort, + "rdpUsername": rdpUsername, }; } @@ -37,16 +46,23 @@ class Peer { required this.platform, required this.alias, required this.tags, + required this.forceAlwaysRelay, + required this.rdpPort, + required this.rdpUsername, }); Peer.loading() : this( - id: '...', - username: '...', - hostname: '...', - platform: '...', - alias: '', - tags: []); + id: '...', + username: '...', + hostname: '...', + platform: '...', + alias: '', + tags: [], + forceAlwaysRelay: false, + rdpPort: '', + rdpUsername: '', + ); } class Peers extends ChangeNotifier { diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index 23ba4ef4b..1250f7e19 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -641,45 +641,11 @@ pub fn main_peer_has_password(id: String) -> bool { peer_has_password(id) } -pub fn main_get_recent_peers() -> String { - if !config::APP_DIR.read().unwrap().is_empty() { - let peers: Vec> = PeerConfig::peers() - .drain(..) - .map(|(id, _, p)| { - HashMap::<&str, String>::from_iter([ - ("id", id), - ("username", p.info.username.clone()), - ("hostname", p.info.hostname.clone()), - ("platform", p.info.platform.clone()), - ( - "alias", - p.options.get("alias").unwrap_or(&"".to_owned()).to_owned(), - ), - ]) - }) - .collect(); - serde_json::ser::to_string(&peers).unwrap_or("".to_owned()) - } else { - String::new() - } -} - pub fn main_load_recent_peers() { if !config::APP_DIR.read().unwrap().is_empty() { let peers: Vec> = PeerConfig::peers() .drain(..) - .map(|(id, _, p)| { - HashMap::<&str, String>::from_iter([ - ("id", id), - ("username", p.info.username.clone()), - ("hostname", p.info.hostname.clone()), - ("platform", p.info.platform.clone()), - ( - "alias", - p.options.get("alias").unwrap_or(&"".to_owned()).to_owned(), - ), - ]) - }) + .map(|(id, _, p)| peer_to_map(id, p)) .collect(); if let Some(s) = flutter::GLOBAL_EVENT_STREAM .read() @@ -705,16 +671,7 @@ pub fn main_load_fav_peers() { .into_iter() .filter_map(|(id, _, p)| { if favs.contains(&id) { - Some(HashMap::<&str, String>::from_iter([ - ("id", id), - ("username", p.info.username.clone()), - ("hostname", p.info.hostname.clone()), - ("platform", p.info.platform.clone()), - ( - "alias", - p.options.get("alias").unwrap_or(&"".to_owned()).to_owned(), - ), - ])) + Some(peer_to_map(id, p)) } else { None } diff --git a/src/ui_interface.rs b/src/ui_interface.rs index 41f22a563..59082d00d 100644 --- a/src/ui_interface.rs +++ b/src/ui_interface.rs @@ -685,6 +685,19 @@ pub fn discover() { }); } +pub fn peer_to_map(id: String, p: PeerConfig) -> HashMap<&'static str, String> { + HashMap::<&str, String>::from_iter([ + ("id", id), + ("username", p.info.username.clone()), + ("hostname", p.info.hostname.clone()), + ("platform", p.info.platform.clone()), + ( + "alias", + p.options.get("alias").unwrap_or(&"".to_owned()).to_owned(), + ), + ]) +} + #[inline] pub fn get_lan_peers() -> Vec> { config::LanPeers::load()