diff --git a/flutter/lib/common/widgets/address_book.dart b/flutter/lib/common/widgets/address_book.dart index 5b3527fa0..799b0be67 100644 --- a/flutter/lib/common/widgets/address_book.dart +++ b/flutter/lib/common/widgets/address_book.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter_hbb/common/formatter/id_formatter.dart'; import 'package:flutter_hbb/common/widgets/peer_card.dart'; import 'package:flutter_hbb/common/widgets/peers_view.dart'; import 'package:flutter_hbb/desktop/widgets/popup_menu.dart'; @@ -237,29 +238,32 @@ class _AddressBookState extends State { } void abAddId() async { - var field = ""; - var msg = ""; var isInProgress = false; - TextEditingController controller = TextEditingController(text: field); + IDTextEditingController idController = IDTextEditingController(text: ''); + TextEditingController aliasController = TextEditingController(text: ''); + final tags = List.of(gFFI.abModel.tags); + var selectedTag = List.empty(growable: true).obs; + final style = TextStyle(fontSize: 14.0); + String? errorMsg; gFFI.dialogManager.show((setState, close) { submit() async { setState(() { - msg = ""; isInProgress = true; + errorMsg = null; }); - field = controller.text.trim(); - if (field.isEmpty) { + String id = idController.id; + if (id.isEmpty) { // pass } else { - final ids = field.trim().split(RegExp(r"[\s,;\n]+")); - field = ids.join(','); - for (final newId in ids) { - if (gFFI.abModel.idContainBy(newId)) { - continue; - } - gFFI.abModel.addId(newId); + if (gFFI.abModel.idContainBy(id)) { + setState(() { + isInProgress = false; + errorMsg = translate('ID already exists'); + }); + return; } + gFFI.abModel.addId(id, aliasController.text.trim(), selectedTag); await gFFI.abModel.pushAb(); this.setState(() {}); // final currentPeers @@ -272,21 +276,70 @@ class _AddressBookState extends State { content: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(translate("whitelist_sep")), - const SizedBox( - height: 8.0, - ), - Row( + Column( children: [ - Expanded( - child: TextField( - maxLines: null, - decoration: InputDecoration( - border: const OutlineInputBorder(), - errorText: msg.isEmpty ? null : translate(msg), + Align( + alignment: Alignment.centerLeft, + child: Row( + children: [ + Text( + '*', + style: TextStyle(color: Colors.red, fontSize: 14), ), - controller: controller, - focusNode: FocusNode()..requestFocus()), + Text( + 'ID', + style: style, + ), + ], + ), + ), + TextField( + controller: idController, + inputFormatters: [IDTextInputFormatter()], + decoration: InputDecoration( + isDense: true, + border: OutlineInputBorder(), + errorText: errorMsg), + style: style, + ), + Align( + alignment: Alignment.centerLeft, + child: Text( + translate('Alias'), + style: style, + ), + ).marginOnly(top: 8, bottom: 2), + TextField( + controller: aliasController, + decoration: InputDecoration( + border: OutlineInputBorder(), + isDense: true, + ), + style: style, + ), + Align( + alignment: Alignment.centerLeft, + child: Text( + translate('Tags'), + style: style, + ), + ).marginOnly(top: 8), + Container( + child: Wrap( + children: tags + .map((e) => AddressBookTag( + name: e, + tags: selectedTag, + onTap: () { + if (selectedTag.contains(e)) { + selectedTag.remove(e); + } else { + selectedTag.add(e); + } + }, + showActionMenu: false)) + .toList(growable: false), + ), ), ], ), diff --git a/flutter/lib/common/widgets/peer_card.dart b/flutter/lib/common/widgets/peer_card.dart index 8df84af6c..4486a6e27 100644 --- a/flutter/lib/common/widgets/peer_card.dart +++ b/flutter/lib/common/widgets/peer_card.dart @@ -586,6 +586,26 @@ abstract class BasePeerCard extends StatelessWidget { ); } + @protected + MenuEntryBase _addToAb(Peer peer) { + return MenuEntryButton( + childBuilder: (TextStyle? style) => Text( + translate('Add to Address Book'), + style: style, + ), + proc: () { + () async { + if (!gFFI.abModel.idContainBy(peer.id)) { + gFFI.abModel.addPeer(peer); + await gFFI.abModel.pushAb(); + } + }(); + }, + padding: menuPadding, + dismissOnClicked: true, + ); + } + void _rename(String id, bool isAddressBook) async { RxBool isInProgress = false.obs; var name = peer.alias; @@ -679,6 +699,9 @@ class RecentPeerCard extends BasePeerCard { menuItems.add(_unrememberPasswordAction(peer.id)); } menuItems.add(_addFavAction(peer.id)); + if (!gFFI.abModel.idContainBy(peer.id)) { + menuItems.add(_addToAb(peer)); + } return menuItems; } } @@ -716,6 +739,9 @@ class FavoritePeerCard extends BasePeerCard { menuItems.add(_rmFavAction(peer.id, () async { await bind.mainLoadFavPeers(); })); + if (!gFFI.abModel.idContainBy(peer.id)) { + menuItems.add(_addToAb(peer)); + } return menuItems; } } @@ -744,6 +770,9 @@ class DiscoveredPeerCard extends BasePeerCard { } menuItems.add(MenuEntryDivider()); menuItems.add(_removeAction(peer.id, () async {})); + if (!gFFI.abModel.idContainBy(peer.id)) { + menuItems.add(_addToAb(peer)); + } return menuItems; } } diff --git a/flutter/lib/models/ab_model.dart b/flutter/lib/models/ab_model.dart index afee97e75..a24c01366 100644 --- a/flutter/lib/models/ab_model.dart +++ b/flutter/lib/models/ab_model.dart @@ -68,11 +68,21 @@ class AbModel { peers.clear(); } - void addId(String id) async { + void addId(String id, String alias, List tags) { if (idContainBy(id)) { return; } - peers.add(Peer.fromJson({"id": id})); + final peer = Peer.fromJson({ + 'id': id, + 'alias': alias, + 'tags': tags, + }); + peers.add(peer); + } + + void addPeer(Peer peer) { + peers.removeWhere((e) => e.id == peer.id); + peers.add(peer); } void addTag(String tag) async { diff --git a/src/lang/ca.rs b/src/lang/ca.rs index 6fc919b8d..70190729c 100644 --- a/src/lang/ca.rs +++ b/src/lang/ca.rs @@ -399,5 +399,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("hide_cm_tip", ""), ("wayland_experiment_tip", ""), ("Right click to select tabs", ""), + ("Add to Address Book", ""), ].iter().cloned().collect(); } diff --git a/src/lang/cn.rs b/src/lang/cn.rs index c429d9539..daa2af065 100644 --- a/src/lang/cn.rs +++ b/src/lang/cn.rs @@ -400,5 +400,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("wayland_experiment_tip", ""), ("Right click to select tabs", "右键选择选项卡"), ("Skipped", "已跳过"), + ("Add to Address Book", "添加到地址簿"), ].iter().cloned().collect(); } diff --git a/src/lang/cs.rs b/src/lang/cs.rs index 6455023de..33c6492f7 100644 --- a/src/lang/cs.rs +++ b/src/lang/cs.rs @@ -399,5 +399,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("hide_cm_tip", ""), ("wayland_experiment_tip", ""), ("Right click to select tabs", ""), + ("Add to Address Book", ""), ].iter().cloned().collect(); } diff --git a/src/lang/da.rs b/src/lang/da.rs index afd6476ee..1aa53ca57 100644 --- a/src/lang/da.rs +++ b/src/lang/da.rs @@ -399,5 +399,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("hide_cm_tip", ""), ("wayland_experiment_tip", ""), ("Right click to select tabs", ""), + ("Add to Address Book", ""), ].iter().cloned().collect(); } diff --git a/src/lang/de.rs b/src/lang/de.rs index 273a607ed..223237def 100644 --- a/src/lang/de.rs +++ b/src/lang/de.rs @@ -399,5 +399,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("hide_cm_tip", "Dies ist nur möglich, wenn der Zugriff nur über ein permanentes Passwort erfolgt."), // Sehr unklar. Muss noch angepasst werden. Original: Allow hiding only if accepting sessions via password and using pernament passw"), ("wayland_experiment_tip", ""), ("Right click to select tabs", ""), + ("Add to Address Book", ""), ].iter().cloned().collect(); } diff --git a/src/lang/eo.rs b/src/lang/eo.rs index 3a38b6601..c2748a9bc 100644 --- a/src/lang/eo.rs +++ b/src/lang/eo.rs @@ -399,5 +399,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("hide_cm_tip", ""), ("wayland_experiment_tip", ""), ("Right click to select tabs", ""), + ("Add to Address Book", ""), ].iter().cloned().collect(); } diff --git a/src/lang/es.rs b/src/lang/es.rs index d8b00b9bf..1069b4905 100644 --- a/src/lang/es.rs +++ b/src/lang/es.rs @@ -399,5 +399,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("hide_cm_tip", "Permitir ocultar solo si se aceptan sesiones a través de contraseña y usando contraseña permanente"), ("wayland_experiment_tip", "El soporte para Wayland está en fase experimental, por favor, use X11 si necesita acceso desatendido."), ("Right click to select tabs", "Clic derecho para seleccionar pestañas"), + ("Add to Address Book", ""), ].iter().cloned().collect(); } diff --git a/src/lang/fa.rs b/src/lang/fa.rs index 7513f84e8..11c17887a 100644 --- a/src/lang/fa.rs +++ b/src/lang/fa.rs @@ -399,5 +399,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("hide_cm_tip", "فقط در صورت پذیرفتن جلسات از طریق رمز عبور و استفاده از رمز عبور دائمی، مخفی شدن مجاز است"), ("wayland_experiment_tip", ""), ("Right click to select tabs", ""), + ("Add to Address Book", ""), ].iter().cloned().collect(); } diff --git a/src/lang/fr.rs b/src/lang/fr.rs index a4eedfa2a..c3d241bf8 100644 --- a/src/lang/fr.rs +++ b/src/lang/fr.rs @@ -399,5 +399,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("hide_cm_tip", "Autoriser le masquage uniquement si vous acceptez des sessions via un mot de passe et utilisez un mot de passe permanent"), ("wayland_experiment_tip", ""), ("Right click to select tabs", ""), + ("Add to Address Book", ""), ].iter().cloned().collect(); } diff --git a/src/lang/gr.rs b/src/lang/gr.rs index e4055e16f..ecabd8f31 100644 --- a/src/lang/gr.rs +++ b/src/lang/gr.rs @@ -399,5 +399,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("hide_cm_tip", "Να επιτρέπεται η απόκρυψη, μόνο εάν αποδέχεστε συνδέσεις μέσω κωδικού πρόσβασης και χρησιμοποιείτε μόνιμο κωδικό πρόσβασης"), ("wayland_experiment_tip", "Η υποστήριξη Wayland βρίσκεται σε πειραματικό στάδιο, χρησιμοποιήστε το X11 εάν χρειάζεστε πρόσβαση χωρίς επίβλεψη."), ("Right click to select tabs", "Κάντε δεξί κλικ για να επιλέξετε καρτέλες"), + ("Add to Address Book", ""), ].iter().cloned().collect(); } diff --git a/src/lang/hu.rs b/src/lang/hu.rs index 941caeac1..d0f2f4412 100644 --- a/src/lang/hu.rs +++ b/src/lang/hu.rs @@ -399,5 +399,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("hide_cm_tip", ""), ("wayland_experiment_tip", ""), ("Right click to select tabs", ""), + ("Add to Address Book", ""), ].iter().cloned().collect(); } diff --git a/src/lang/id.rs b/src/lang/id.rs index 7f4a50523..b8f9e392d 100644 --- a/src/lang/id.rs +++ b/src/lang/id.rs @@ -399,5 +399,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("hide_cm_tip", ""), ("wayland_experiment_tip", ""), ("Right click to select tabs", ""), + ("Add to Address Book", ""), ].iter().cloned().collect(); } diff --git a/src/lang/it.rs b/src/lang/it.rs index 98ea9366e..f11a06c80 100644 --- a/src/lang/it.rs +++ b/src/lang/it.rs @@ -399,5 +399,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("hide_cm_tip", "Permetti di nascondere solo se si accettano sessioni con password permanente"), ("wayland_experiment_tip", ""), ("Right click to select tabs", ""), + ("Add to Address Book", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ja.rs b/src/lang/ja.rs index 4121dd55c..4ca33e76a 100644 --- a/src/lang/ja.rs +++ b/src/lang/ja.rs @@ -399,5 +399,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("hide_cm_tip", ""), ("wayland_experiment_tip", ""), ("Right click to select tabs", ""), + ("Add to Address Book", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ko.rs b/src/lang/ko.rs index a1cd730e9..93338165b 100644 --- a/src/lang/ko.rs +++ b/src/lang/ko.rs @@ -399,5 +399,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("hide_cm_tip", ""), ("wayland_experiment_tip", ""), ("Right click to select tabs", ""), + ("Add to Address Book", ""), ].iter().cloned().collect(); } diff --git a/src/lang/kz.rs b/src/lang/kz.rs index 1e623c0b6..a7d6f299d 100644 --- a/src/lang/kz.rs +++ b/src/lang/kz.rs @@ -399,5 +399,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("hide_cm_tip", ""), ("wayland_experiment_tip", ""), ("Right click to select tabs", ""), + ("Add to Address Book", ""), ].iter().cloned().collect(); } diff --git a/src/lang/pl.rs b/src/lang/pl.rs index ed14e3ca2..d3f991d44 100644 --- a/src/lang/pl.rs +++ b/src/lang/pl.rs @@ -399,5 +399,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("hide_cm_tip", ""), ("wayland_experiment_tip", ""), ("Right click to select tabs", ""), + ("Add to Address Book", ""), ].iter().cloned().collect(); } diff --git a/src/lang/pt_PT.rs b/src/lang/pt_PT.rs index 65620e3e8..4a457218c 100644 --- a/src/lang/pt_PT.rs +++ b/src/lang/pt_PT.rs @@ -399,5 +399,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("hide_cm_tip", ""), ("wayland_experiment_tip", ""), ("Right click to select tabs", ""), + ("Add to Address Book", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ptbr.rs b/src/lang/ptbr.rs index cf388a88c..af59e4f2e 100644 --- a/src/lang/ptbr.rs +++ b/src/lang/ptbr.rs @@ -399,5 +399,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("hide_cm_tip", ""), ("wayland_experiment_tip", ""), ("Right click to select tabs", ""), + ("Add to Address Book", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ru.rs b/src/lang/ru.rs index e7cc686c2..ff8dfc316 100644 --- a/src/lang/ru.rs +++ b/src/lang/ru.rs @@ -399,5 +399,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("hide_cm_tip", "Разрешать скрытие случае, если принимаются сеансы по паролю или используется постоянный пароль"), ("wayland_experiment_tip", "Поддержка Wayland находится на экспериментальной стадии, используйте X11, если вам требуется автоматический доступ."), ("Right click to select tabs", "Выбор вкладок щелчком правой кнопки мыши"), + ("Add to Address Book", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sk.rs b/src/lang/sk.rs index e7a9b12b5..13672d086 100644 --- a/src/lang/sk.rs +++ b/src/lang/sk.rs @@ -399,5 +399,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("hide_cm_tip", ""), ("wayland_experiment_tip", ""), ("Right click to select tabs", ""), + ("Add to Address Book", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sq.rs b/src/lang/sq.rs index 1926c849b..5ec59c4be 100644 --- a/src/lang/sq.rs +++ b/src/lang/sq.rs @@ -399,5 +399,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("hide_cm_tip", "Kjo është e mundur vetëm nëse aksesi bëhet nëpërmjet një fjalëkalimi të përhershëm"), ("wayland_experiment_tip", ""), ("Right click to select tabs", ""), + ("Add to Address Book", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sv.rs b/src/lang/sv.rs index cfef903a7..1feb5d55e 100644 --- a/src/lang/sv.rs +++ b/src/lang/sv.rs @@ -399,5 +399,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("hide_cm_tip", "Tillåt att gömma endast om accepterande sessioner med lösenord och permanenta lösenord"), ("wayland_experiment_tip", ""), ("Right click to select tabs", ""), + ("Add to Address Book", ""), ].iter().cloned().collect(); } diff --git a/src/lang/template.rs b/src/lang/template.rs index ed7189ce6..6993cb43c 100644 --- a/src/lang/template.rs +++ b/src/lang/template.rs @@ -399,5 +399,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("hide_cm_tip", ""), ("wayland_experiment_tip", ""), ("Right click to select tabs", ""), + ("Add to Address Book", ""), ].iter().cloned().collect(); } diff --git a/src/lang/tr.rs b/src/lang/tr.rs index 6c518da81..7b66af60e 100644 --- a/src/lang/tr.rs +++ b/src/lang/tr.rs @@ -399,5 +399,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("hide_cm_tip", ""), ("wayland_experiment_tip", ""), ("Right click to select tabs", ""), + ("Add to Address Book", ""), ].iter().cloned().collect(); } diff --git a/src/lang/tw.rs b/src/lang/tw.rs index e7c024420..6f0e8806b 100644 --- a/src/lang/tw.rs +++ b/src/lang/tw.rs @@ -399,5 +399,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("hide_cm_tip", "在只允許密碼連接並且只用固定密碼的情況下才允許隱藏"), ("wayland_experiment_tip", ""), ("Right click to select tabs", "右鍵選擇選項卡"), + ("Add to Address Book", "添加到地址簿"), ].iter().cloned().collect(); } diff --git a/src/lang/ua.rs b/src/lang/ua.rs index 713d15c69..92fd2db8a 100644 --- a/src/lang/ua.rs +++ b/src/lang/ua.rs @@ -399,5 +399,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("hide_cm_tip", ""), ("wayland_experiment_tip", ""), ("Right click to select tabs", ""), + ("Add to Address Book", ""), ].iter().cloned().collect(); } diff --git a/src/lang/vn.rs b/src/lang/vn.rs index c59de33fc..1d32aad5e 100644 --- a/src/lang/vn.rs +++ b/src/lang/vn.rs @@ -399,5 +399,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("hide_cm_tip", ""), ("wayland_experiment_tip", ""), ("Right click to select tabs", ""), + ("Add to Address Book", ""), ].iter().cloned().collect(); }