diff --git a/flutter/assets/device_group.ttf b/flutter/assets/device_group.ttf new file mode 100644 index 000000000..a6e42704f Binary files /dev/null and b/flutter/assets/device_group.ttf differ diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index 9252698cf..06af89fa6 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -103,6 +103,8 @@ enum DesktopType { class IconFont { static const _family1 = 'Tabbar'; static const _family2 = 'PeerSearchbar'; + static const _family3 = 'AddressBook'; + static const _family4 = 'DeviceGroup'; IconFont._(); static const IconData max = IconData(0xe606, fontFamily: _family1); @@ -113,8 +115,11 @@ class IconFont { static const IconData menu = IconData(0xe628, fontFamily: _family1); static const IconData search = IconData(0xe6a4, fontFamily: _family2); static const IconData roundClose = IconData(0xe6ed, fontFamily: _family2); - static const IconData addressBook = - IconData(0xe602, fontFamily: "AddressBook"); + static const IconData addressBook = IconData(0xe602, fontFamily: _family3); + static const IconData deviceGroupOutline = + IconData(0xe623, fontFamily: _family4); + static const IconData deviceGroupFill = + IconData(0xe748, fontFamily: _family4); } class ColorThemeExtension extends ThemeExtension { diff --git a/flutter/lib/common/hbbs/hbbs.dart b/flutter/lib/common/hbbs/hbbs.dart index e189cc7b2..a58ecf01e 100644 --- a/flutter/lib/common/hbbs/hbbs.dart +++ b/flutter/lib/common/hbbs/hbbs.dart @@ -67,6 +67,7 @@ class PeerPayload { int? status; String user = ''; String user_name = ''; + String? device_group_name; String note = ''; PeerPayload.fromJson(Map json) @@ -75,6 +76,7 @@ class PeerPayload { status = json['status'], user = json['user'] ?? '', user_name = json['user_name'] ?? '', + device_group_name = json['device_group_name'] ?? '', note = json['note'] ?? ''; static Peer toPeer(PeerPayload p) { @@ -84,6 +86,7 @@ class PeerPayload { "username": p.info['username'] ?? '', "platform": _platform(p.info['os']), "hostname": p.info['device_name'], + "device_group_name": p.device_group_name, }); } @@ -265,3 +268,19 @@ class AbTag { : name = json['name'] ?? '', color = json['color'] ?? ''; } + +class DeviceGroupPayload { + String name; + + DeviceGroupPayload(this.name); + + DeviceGroupPayload.fromJson(Map json) + : name = json['name'] ?? ''; + + Map toGroupCacheJson() { + final Map map = { + 'name': name, + }; + return map; + } +} diff --git a/flutter/lib/common/widgets/my_group.dart b/flutter/lib/common/widgets/my_group.dart index 359fbc7f7..efd1e5a24 100644 --- a/flutter/lib/common/widgets/my_group.dart +++ b/flutter/lib/common/widgets/my_group.dart @@ -20,8 +20,11 @@ class MyGroup extends StatefulWidget { } class _MyGroupState extends State { - RxString get selectedUser => gFFI.groupModel.selectedUser; - RxString get searchUserText => gFFI.groupModel.searchUserText; + RxBool get isSelectedDeviceGroup => gFFI.groupModel.isSelectedDeviceGroup; + RxString get selectedAccessibleItemName => + gFFI.groupModel.selectedAccessibleItemName; + RxString get searchAccessibleItemNameText => + gFFI.groupModel.searchAccessibleItemNameText; static TextEditingController searchUserController = TextEditingController(); @override @@ -72,7 +75,7 @@ class _MyGroupState extends State { child: Container( width: double.infinity, height: double.infinity, - child: _buildUserContacts(), + child: _buildLeftList(), ), ) ], @@ -105,7 +108,7 @@ class _MyGroupState extends State { _buildLeftHeader(), Container( width: double.infinity, - child: _buildUserContacts(), + child: _buildLeftList(), ) ], ), @@ -130,7 +133,7 @@ class _MyGroupState extends State { child: TextField( controller: searchUserController, onChanged: (value) { - searchUserText.value = value; + searchAccessibleItemNameText.value = value; }, textAlignVertical: TextAlignVertical.center, style: TextStyle(fontSize: fontSize), @@ -150,20 +153,30 @@ class _MyGroupState extends State { ); } - Widget _buildUserContacts() { + Widget _buildLeftList() { return Obx(() { - final items = gFFI.groupModel.users.where((p0) { - if (searchUserText.isNotEmpty) { + final userItems = gFFI.groupModel.users.where((p0) { + if (searchAccessibleItemNameText.isNotEmpty) { return p0.name .toLowerCase() - .contains(searchUserText.value.toLowerCase()); + .contains(searchAccessibleItemNameText.value.toLowerCase()); + } + return true; + }).toList(); + final deviceGroupItems = gFFI.groupModel.deviceGroups.where((p0) { + if (searchAccessibleItemNameText.isNotEmpty) { + return p0.name + .toLowerCase() + .contains(searchAccessibleItemNameText.value.toLowerCase()); } return true; }).toList(); listView(bool isPortrait) => ListView.builder( shrinkWrap: isPortrait, - itemCount: items.length, - itemBuilder: (context, index) => _buildUserItem(items[index])); + itemCount: deviceGroupItems.length + userItems.length, + itemBuilder: (context, index) => index < deviceGroupItems.length + ? _buildDeviceGroupItem(deviceGroupItems[index]) + : _buildUserItem(userItems[index - deviceGroupItems.length])); var maxHeight = max(MediaQuery.of(context).size.height / 6, 100.0); return Obx(() => stateGlobal.isPortrait.isFalse ? listView(false) @@ -174,14 +187,16 @@ class _MyGroupState extends State { Widget _buildUserItem(UserPayload user) { final username = user.name; return InkWell(onTap: () { - if (selectedUser.value != username) { - selectedUser.value = username; + isSelectedDeviceGroup.value = false; + if (selectedAccessibleItemName.value != username) { + selectedAccessibleItemName.value = username; } else { - selectedUser.value = ''; + selectedAccessibleItemName.value = ''; } }, child: Obx( () { - bool selected = selectedUser.value == username; + bool selected = !isSelectedDeviceGroup.value && + selectedAccessibleItemName.value == username; final isMe = username == gFFI.userModel.userName.value; final colorMe = MyTheme.color(context).me!; return Container( @@ -238,4 +253,43 @@ class _MyGroupState extends State { }, )).marginSymmetric(horizontal: 12).marginOnly(bottom: 6); } + + Widget _buildDeviceGroupItem(DeviceGroupPayload deviceGroup) { + final name = deviceGroup.name; + return InkWell(onTap: () { + isSelectedDeviceGroup.value = true; + if (selectedAccessibleItemName.value != name) { + selectedAccessibleItemName.value = name; + } else { + selectedAccessibleItemName.value = ''; + } + }, child: Obx( + () { + bool selected = isSelectedDeviceGroup.value && + selectedAccessibleItemName.value == name; + return Container( + decoration: BoxDecoration( + color: selected ? MyTheme.color(context).highlight : null, + border: Border( + bottom: BorderSide( + width: 0.7, + color: Theme.of(context).dividerColor.withOpacity(0.1))), + ), + child: Container( + child: Row( + children: [ + Container( + width: 20, + height: 20, + child: Icon(IconFont.deviceGroupOutline, + color: MyTheme.accent, size: 19), + ).marginOnly(right: 4), + Expanded(child: Text(name)), + ], + ).paddingSymmetric(vertical: 4), + ), + ); + }, + )).marginSymmetric(horizontal: 12).marginOnly(bottom: 6); + } } diff --git a/flutter/lib/common/widgets/peers_view.dart b/flutter/lib/common/widgets/peers_view.dart index 3e34f882d..3393921b2 100644 --- a/flutter/lib/common/widgets/peers_view.dart +++ b/flutter/lib/common/widgets/peers_view.dart @@ -562,14 +562,23 @@ class MyGroupPeerView extends BasePeersView { ); static bool filter(Peer peer) { - if (gFFI.groupModel.searchUserText.isNotEmpty) { - if (!peer.loginName.contains(gFFI.groupModel.searchUserText)) { + if (gFFI.groupModel.searchAccessibleItemNameText.isNotEmpty) { + if (!peer.loginName + .contains(gFFI.groupModel.searchAccessibleItemNameText)) { return false; } } - if (gFFI.groupModel.selectedUser.isNotEmpty) { - if (gFFI.groupModel.selectedUser.value != peer.loginName) { - return false; + if (gFFI.groupModel.selectedAccessibleItemName.isNotEmpty) { + if (gFFI.groupModel.isSelectedDeviceGroup.value) { + if (gFFI.groupModel.selectedAccessibleItemName.value != + peer.device_group_name) { + return false; + } + } else { + if (gFFI.groupModel.selectedAccessibleItemName.value != + peer.loginName) { + return false; + } } } return true; diff --git a/flutter/lib/desktop/pages/connection_page.dart b/flutter/lib/desktop/pages/connection_page.dart index 235e2185a..d9dc3eec4 100644 --- a/flutter/lib/desktop/pages/connection_page.dart +++ b/flutter/lib/desktop/pages/connection_page.dart @@ -350,6 +350,7 @@ class _ConnectionPageState extends State rdpPort: '', rdpUsername: '', loginName: '', + device_group_name: '', ); _autocompleteOpts = [emptyPeer]; } else { diff --git a/flutter/lib/mobile/pages/connection_page.dart b/flutter/lib/mobile/pages/connection_page.dart index 1d83b5744..295310338 100644 --- a/flutter/lib/mobile/pages/connection_page.dart +++ b/flutter/lib/mobile/pages/connection_page.dart @@ -174,6 +174,7 @@ class _ConnectionPageState extends State { rdpPort: '', rdpUsername: '', loginName: '', + device_group_name: '', ); _autocompleteOpts = [emptyPeer]; } else { diff --git a/flutter/lib/models/group_model.dart b/flutter/lib/models/group_model.dart index b14ccd46b..155bf99f6 100644 --- a/flutter/lib/models/group_model.dart +++ b/flutter/lib/models/group_model.dart @@ -12,16 +12,18 @@ import '../utils/http_service.dart' as http; class GroupModel { final RxBool groupLoading = false.obs; final RxString groupLoadError = "".obs; + final RxList deviceGroups = RxList.empty(growable: true); final RxList users = RxList.empty(growable: true); final RxList peers = RxList.empty(growable: true); - final RxString selectedUser = ''.obs; - final RxString searchUserText = ''.obs; + final RxBool isSelectedDeviceGroup = false.obs; + final RxString selectedAccessibleItemName = ''.obs; + final RxString searchAccessibleItemNameText = ''.obs; WeakReference parent; var initialized = false; var _cacheLoadOnceFlag = false; var _statusCode = 200; - bool get emtpy => users.isEmpty && peers.isEmpty; + bool get emtpy => deviceGroups.isEmpty && users.isEmpty && peers.isEmpty; late final Peers peersModel; @@ -55,6 +57,12 @@ class GroupModel { } Future _pull() async { + List tmpDeviceGroups = List.empty(growable: true); + if (!await _getDeviceGroups(tmpDeviceGroups)) { + // old hbbs doesn't support this api + // return; + } + tmpDeviceGroups.sort((a, b) => a.name.compareTo(b.name)); List tmpUsers = List.empty(growable: true); if (!await _getUsers(tmpUsers)) { return; @@ -63,6 +71,7 @@ class GroupModel { if (!await _getPeers(tmpPeers)) { return; } + deviceGroups.value = tmpDeviceGroups; // me first var index = tmpUsers .indexWhere((user) => user.name == gFFI.userModel.userName.value); @@ -71,8 +80,9 @@ class GroupModel { tmpUsers.insert(0, user); } users.value = tmpUsers; - if (!users.any((u) => u.name == selectedUser.value)) { - selectedUser.value = ''; + if (!users.any((u) => u.name == selectedAccessibleItemName.value) && + !deviceGroups.any((d) => d.name == selectedAccessibleItemName.value)) { + selectedAccessibleItemName.value = ''; } // recover online final oldOnlineIDs = peers.where((e) => e.online).map((e) => e.id).toList(); @@ -84,6 +94,63 @@ class GroupModel { groupLoadError.value = ''; } + Future _getDeviceGroups( + List tmpDeviceGroups) async { + final api = "${await bind.mainGetApiServer()}/api/device-group/accessible"; + try { + var uri0 = Uri.parse(api); + final pageSize = 100; + var total = 0; + int current = 0; + do { + current += 1; + var uri = Uri( + scheme: uri0.scheme, + host: uri0.host, + path: uri0.path, + port: uri0.port, + queryParameters: { + 'current': current.toString(), + 'pageSize': pageSize.toString(), + }); + final resp = await http.get(uri, headers: getHttpHeaders()); + _statusCode = resp.statusCode; + Map json = + _jsonDecodeResp(utf8.decode(resp.bodyBytes), resp.statusCode); + if (json.containsKey('error')) { + throw json['error']; + } + if (resp.statusCode != 200) { + throw 'HTTP ${resp.statusCode}'; + } + if (json.containsKey('total')) { + if (total == 0) total = json['total']; + if (json.containsKey('data')) { + final data = json['data']; + if (data is List) { + for (final user in data) { + final u = DeviceGroupPayload.fromJson(user); + int index = tmpDeviceGroups.indexWhere((e) => e.name == u.name); + if (index < 0) { + tmpDeviceGroups.add(u); + } else { + tmpDeviceGroups[index] = u; + } + } + } + } + } + } while (current * pageSize < total); + return true; + } catch (err) { + debugPrint('get accessible device groups: $err'); + // old hbbs doesn't support this api + // groupLoadError.value = + // '${translate('pull_group_failed_tip')}: ${translate(err.toString())}'; + } + return false; + } + Future _getUsers(List tmpUsers) async { final api = "${await bind.mainGetApiServer()}/api/users"; try { @@ -225,6 +292,7 @@ class GroupModel { try { final map = ({ "access_token": bind.mainGetLocalOption(key: 'access_token'), + "device_groups": deviceGroups.map((e) => e.toGroupCacheJson()).toList(), "users": users.map((e) => e.toGroupCacheJson()).toList(), 'peers': peers.map((e) => e.toGroupCacheJson()).toList() }); @@ -244,8 +312,14 @@ class GroupModel { if (groupLoading.value) return; final data = jsonDecode(cache); if (data == null || data['access_token'] != access_token) return; + deviceGroups.clear(); users.clear(); peers.clear(); + if (data['device_groups'] is List) { + for (var u in data['device_groups']) { + deviceGroups.add(DeviceGroupPayload.fromJson(u)); + } + } if (data['users'] is List) { for (var u in data['users']) { users.add(UserPayload.fromJson(u)); @@ -263,9 +337,10 @@ class GroupModel { reset() async { groupLoadError.value = ''; + deviceGroups.clear(); users.clear(); peers.clear(); - selectedUser.value = ''; + selectedAccessibleItemName.value = ''; await bind.mainClearGroup(); } } diff --git a/flutter/lib/models/peer_model.dart b/flutter/lib/models/peer_model.dart index 7ab5a2b80..d2a38c68b 100644 --- a/flutter/lib/models/peer_model.dart +++ b/flutter/lib/models/peer_model.dart @@ -19,6 +19,7 @@ class Peer { String rdpUsername; bool online = false; String loginName; //login username + String device_group_name; bool? sameServer; String getId() { @@ -41,6 +42,7 @@ class Peer { rdpPort = json['rdpPort'] ?? '', rdpUsername = json['rdpUsername'] ?? '', loginName = json['loginName'] ?? '', + device_group_name = json['device_group_name'] ?? '', sameServer = json['same_server']; Map toJson() { @@ -57,6 +59,7 @@ class Peer { "rdpPort": rdpPort, "rdpUsername": rdpUsername, 'loginName': loginName, + 'device_group_name': device_group_name, 'same_server': sameServer, }; } @@ -83,6 +86,7 @@ class Peer { "hostname": hostname, "platform": platform, "login_name": loginName, + "device_group_name": device_group_name, }; } @@ -99,6 +103,7 @@ class Peer { required this.rdpPort, required this.rdpUsername, required this.loginName, + required this.device_group_name, this.sameServer, }); @@ -116,6 +121,7 @@ class Peer { rdpPort: '', rdpUsername: '', loginName: '', + device_group_name: '', ); bool equal(Peer other) { return id == other.id && @@ -129,6 +135,7 @@ class Peer { forceAlwaysRelay == other.forceAlwaysRelay && rdpPort == other.rdpPort && rdpUsername == other.rdpUsername && + device_group_name == other.device_group_name && loginName == other.loginName; } @@ -146,6 +153,7 @@ class Peer { rdpPort: other.rdpPort, rdpUsername: other.rdpUsername, loginName: other.loginName, + device_group_name: other.device_group_name, sameServer: other.sameServer); } diff --git a/flutter/lib/models/peer_tab_model.dart b/flutter/lib/models/peer_tab_model.dart index 83df1f05d..d152f7349 100644 --- a/flutter/lib/models/peer_tab_model.dart +++ b/flutter/lib/models/peer_tab_model.dart @@ -28,14 +28,14 @@ class PeerTabModel with ChangeNotifier { 'Favorites', 'Discovered', 'Address book', - 'Group', + 'Accessible devices', ]; static const List icons = [ Icons.access_time_filled, Icons.star, Icons.explore, IconFont.addressBook, - Icons.group, + IconFont.deviceGroupFill, ]; List isEnabled = List.from([ true, diff --git a/flutter/pubspec.yaml b/flutter/pubspec.yaml index c8dbfec85..0c389e222 100644 --- a/flutter/pubspec.yaml +++ b/flutter/pubspec.yaml @@ -161,6 +161,9 @@ flutter: - family: AddressBook fonts: - asset: assets/address_book.ttf + - family: DeviceGroup + fonts: + - asset: assets/device_group.ttf # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware. diff --git a/libs/hbb_common b/libs/hbb_common index 97266d7c1..8b6700a33 160000 --- a/libs/hbb_common +++ b/libs/hbb_common @@ -1 +1 @@ -Subproject commit 97266d7c180feef8b43726ea6fcb4491e3fd8752 +Subproject commit 8b6700a33f8101fc0d9f0f2dcfcfeb0f53ac8f9b diff --git a/src/core_main.rs b/src/core_main.rs index 5264d5bfd..9745a32e3 100644 --- a/src/core_main.rs +++ b/src/core_main.rs @@ -427,15 +427,26 @@ pub fn core_main() -> Option> { if pos < max { address_book_tag = Some(args[pos + 1].to_owned()); } + let mut device_group_name = None; + let pos = args + .iter() + .position(|x| x == "--device_group_name") + .unwrap_or(max); + if pos < max { + device_group_name = Some(args[pos + 1].to_owned()); + } let mut body = serde_json::json!({ "id": id, "uuid": uuid, }); let header = "Authorization: Bearer ".to_owned() + &token; - if user_name.is_none() && strategy_name.is_none() && address_book_name.is_none() + if user_name.is_none() + && strategy_name.is_none() + && address_book_name.is_none() + && device_group_name.is_none() { println!( - "--user_name or --strategy_name or --address_book_name is required!" + "--user_name or --strategy_name or --address_book_name or --device_group_name is required!" ); } else { if let Some(name) = user_name { @@ -450,6 +461,9 @@ pub fn core_main() -> Option> { body["address_book_tag"] = serde_json::json!(name); } } + if let Some(name) = device_group_name { + body["device_group_name"] = serde_json::json!(name); + } let url = crate::ui_interface::get_api_server() + "/api/devices/cli"; match crate::post_request_sync(url, body.to_string(), &header) { Err(err) => println!("{}", err), diff --git a/src/hbbs_http/sync.rs b/src/hbbs_http/sync.rs index 91c18c4b2..1c8915cb3 100644 --- a/src/hbbs_http/sync.rs +++ b/src/hbbs_http/sync.rs @@ -99,6 +99,10 @@ async fn start_hbbs_sync_async() { if !strategy_name.is_empty() { v[keys::OPTION_PRESET_STRATEGY_NAME] = json!(strategy_name); } + let device_group_name = get_builtin_option(keys::OPTION_PRESET_DEVICE_GROUP_NAME); + if !device_group_name.is_empty() { + v[keys::OPTION_PRESET_DEVICE_GROUP_NAME] = json!(device_group_name); + } match crate::post_request(url.replace("heartbeat", "sysinfo"), v.to_string(), "").await { Ok(x) => { if x == "SYSINFO_UPDATED" { diff --git a/src/lang/ar.rs b/src/lang/ar.rs index 08eae5dbd..0ee1f3247 100644 --- a/src/lang/ar.rs +++ b/src/lang/ar.rs @@ -656,5 +656,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Update client clipboard", ""), ("Untagged", ""), ("new-version-of-{}-tip", ""), + ("Accessible devices", ""), ].iter().cloned().collect(); } diff --git a/src/lang/be.rs b/src/lang/be.rs index c23143776..3938da6d4 100644 --- a/src/lang/be.rs +++ b/src/lang/be.rs @@ -656,5 +656,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Update client clipboard", ""), ("Untagged", ""), ("new-version-of-{}-tip", ""), + ("Accessible devices", ""), ].iter().cloned().collect(); } diff --git a/src/lang/bg.rs b/src/lang/bg.rs index 469b1b4fb..21763753d 100644 --- a/src/lang/bg.rs +++ b/src/lang/bg.rs @@ -656,5 +656,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Update client clipboard", ""), ("Untagged", ""), ("new-version-of-{}-tip", ""), + ("Accessible devices", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ca.rs b/src/lang/ca.rs index 33992a338..305d750ce 100644 --- a/src/lang/ca.rs +++ b/src/lang/ca.rs @@ -656,5 +656,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Update client clipboard", ""), ("Untagged", ""), ("new-version-of-{}-tip", ""), + ("Accessible devices", ""), ].iter().cloned().collect(); } diff --git a/src/lang/cn.rs b/src/lang/cn.rs index 18bc5e638..f5fa04860 100644 --- a/src/lang/cn.rs +++ b/src/lang/cn.rs @@ -656,5 +656,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Update client clipboard", "更新客户端的粘贴板"), ("Untagged", "无标签"), ("new-version-of-{}-tip", "{} 版本更新"), + ("Accessible devices", "可访问的设备"), ].iter().cloned().collect(); } diff --git a/src/lang/cs.rs b/src/lang/cs.rs index e36c49361..310f2249a 100644 --- a/src/lang/cs.rs +++ b/src/lang/cs.rs @@ -656,5 +656,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Update client clipboard", ""), ("Untagged", ""), ("new-version-of-{}-tip", ""), + ("Accessible devices", ""), ].iter().cloned().collect(); } diff --git a/src/lang/da.rs b/src/lang/da.rs index 7988da242..e2493eb54 100644 --- a/src/lang/da.rs +++ b/src/lang/da.rs @@ -656,5 +656,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Update client clipboard", ""), ("Untagged", ""), ("new-version-of-{}-tip", ""), + ("Accessible devices", ""), ].iter().cloned().collect(); } diff --git a/src/lang/de.rs b/src/lang/de.rs index dbc6efc2d..5aea3c39e 100644 --- a/src/lang/de.rs +++ b/src/lang/de.rs @@ -656,5 +656,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Update client clipboard", "Client-Zwischenablage aktualisieren"), ("Untagged", "Unmarkiert"), ("new-version-of-{}-tip", "Es ist eine neue Version von {} verfügbar"), + ("Accessible devices", ""), ].iter().cloned().collect(); } diff --git a/src/lang/el.rs b/src/lang/el.rs index 2979c1aaa..01a0c2068 100644 --- a/src/lang/el.rs +++ b/src/lang/el.rs @@ -656,5 +656,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Update client clipboard", "Ενημέρωση απομακρισμένου προχείρου"), ("Untagged", "Χωρίς ετικέτα"), ("new-version-of-{}-tip", "Υπάρχει διαθέσιμη νέα έκδοση του {}"), + ("Accessible devices", ""), ].iter().cloned().collect(); } diff --git a/src/lang/eo.rs b/src/lang/eo.rs index 7370c2429..3ca19da3c 100644 --- a/src/lang/eo.rs +++ b/src/lang/eo.rs @@ -656,5 +656,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Update client clipboard", ""), ("Untagged", ""), ("new-version-of-{}-tip", ""), + ("Accessible devices", ""), ].iter().cloned().collect(); } diff --git a/src/lang/es.rs b/src/lang/es.rs index 3ad77afe5..680a10b8e 100644 --- a/src/lang/es.rs +++ b/src/lang/es.rs @@ -656,5 +656,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Update client clipboard", "Actualizar portapapeles del cliente"), ("Untagged", "Sin itiquetar"), ("new-version-of-{}-tip", "Hay una nueva versión de {} disponible"), + ("Accessible devices", ""), ].iter().cloned().collect(); } diff --git a/src/lang/et.rs b/src/lang/et.rs index b38b55bd6..0878d8a14 100644 --- a/src/lang/et.rs +++ b/src/lang/et.rs @@ -656,5 +656,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Update client clipboard", ""), ("Untagged", ""), ("new-version-of-{}-tip", ""), + ("Accessible devices", ""), ].iter().cloned().collect(); } diff --git a/src/lang/eu.rs b/src/lang/eu.rs index efb281496..929461d52 100644 --- a/src/lang/eu.rs +++ b/src/lang/eu.rs @@ -656,5 +656,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Update client clipboard", ""), ("Untagged", ""), ("new-version-of-{}-tip", ""), + ("Accessible devices", ""), ].iter().cloned().collect(); } diff --git a/src/lang/fa.rs b/src/lang/fa.rs index d1d3d4767..816fc6c3e 100644 --- a/src/lang/fa.rs +++ b/src/lang/fa.rs @@ -656,5 +656,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Update client clipboard", ""), ("Untagged", ""), ("new-version-of-{}-tip", ""), + ("Accessible devices", ""), ].iter().cloned().collect(); } diff --git a/src/lang/fr.rs b/src/lang/fr.rs index 19b4e58a6..e29ddf494 100644 --- a/src/lang/fr.rs +++ b/src/lang/fr.rs @@ -656,5 +656,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Update client clipboard", ""), ("Untagged", ""), ("new-version-of-{}-tip", ""), + ("Accessible devices", ""), ].iter().cloned().collect(); } diff --git a/src/lang/he.rs b/src/lang/he.rs index a5099f487..1122078c1 100644 --- a/src/lang/he.rs +++ b/src/lang/he.rs @@ -656,5 +656,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Update client clipboard", ""), ("Untagged", ""), ("new-version-of-{}-tip", ""), + ("Accessible devices", ""), ].iter().cloned().collect(); } diff --git a/src/lang/hr.rs b/src/lang/hr.rs index ba4723b8a..0c1e4a19e 100644 --- a/src/lang/hr.rs +++ b/src/lang/hr.rs @@ -656,5 +656,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Update client clipboard", ""), ("Untagged", ""), ("new-version-of-{}-tip", ""), + ("Accessible devices", ""), ].iter().cloned().collect(); } diff --git a/src/lang/hu.rs b/src/lang/hu.rs index 7b149938e..94c51a268 100644 --- a/src/lang/hu.rs +++ b/src/lang/hu.rs @@ -656,5 +656,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Update client clipboard", "A kliens vágólapjának frissítése"), ("Untagged", "Címkézetlen"), ("new-version-of-{}-tip", "A(z) {} új verziója"), + ("Accessible devices", ""), ].iter().cloned().collect(); } diff --git a/src/lang/id.rs b/src/lang/id.rs index 7d90a3ea4..8a93ccae6 100644 --- a/src/lang/id.rs +++ b/src/lang/id.rs @@ -656,5 +656,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Update client clipboard", ""), ("Untagged", ""), ("new-version-of-{}-tip", ""), + ("Accessible devices", ""), ].iter().cloned().collect(); } diff --git a/src/lang/it.rs b/src/lang/it.rs index 0f6657bbc..abb1919dd 100644 --- a/src/lang/it.rs +++ b/src/lang/it.rs @@ -656,5 +656,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Update client clipboard", "Aggiorna appunti client"), ("Untagged", "Senza tag"), ("new-version-of-{}-tip", "È disponibile una nuova versione di {}"), + ("Accessible devices", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ja.rs b/src/lang/ja.rs index 3a967afc6..78cc748fe 100644 --- a/src/lang/ja.rs +++ b/src/lang/ja.rs @@ -656,5 +656,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Update client clipboard", ""), ("Untagged", ""), ("new-version-of-{}-tip", ""), + ("Accessible devices", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ko.rs b/src/lang/ko.rs index 6a2815ace..a5bc432d8 100644 --- a/src/lang/ko.rs +++ b/src/lang/ko.rs @@ -656,5 +656,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Update client clipboard", "클라이언트 클립보드 업데이트"), ("Untagged", "태그 없음"), ("new-version-of-{}-tip", "{} 의 새로운 버전이 출시되었습니다."), + ("Accessible devices", ""), ].iter().cloned().collect(); } diff --git a/src/lang/kz.rs b/src/lang/kz.rs index 1f88ff773..7362ea713 100644 --- a/src/lang/kz.rs +++ b/src/lang/kz.rs @@ -656,5 +656,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Update client clipboard", ""), ("Untagged", ""), ("new-version-of-{}-tip", ""), + ("Accessible devices", ""), ].iter().cloned().collect(); } diff --git a/src/lang/lt.rs b/src/lang/lt.rs index 33db01f93..e19badc61 100644 --- a/src/lang/lt.rs +++ b/src/lang/lt.rs @@ -656,5 +656,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Update client clipboard", ""), ("Untagged", ""), ("new-version-of-{}-tip", ""), + ("Accessible devices", ""), ].iter().cloned().collect(); } diff --git a/src/lang/lv.rs b/src/lang/lv.rs index ae9626ff8..38bae71d8 100644 --- a/src/lang/lv.rs +++ b/src/lang/lv.rs @@ -656,5 +656,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Update client clipboard", "Atjaunināt klienta starpliktuvi"), ("Untagged", "Neatzīmēts"), ("new-version-of-{}-tip", "Ir pieejama jauna {} versija"), + ("Accessible devices", ""), ].iter().cloned().collect(); } diff --git a/src/lang/nb.rs b/src/lang/nb.rs index eb3564b86..2901ba40c 100644 --- a/src/lang/nb.rs +++ b/src/lang/nb.rs @@ -656,5 +656,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Update client clipboard", ""), ("Untagged", ""), ("new-version-of-{}-tip", ""), + ("Accessible devices", ""), ].iter().cloned().collect(); } diff --git a/src/lang/nl.rs b/src/lang/nl.rs index 90c87c324..9674f0e57 100644 --- a/src/lang/nl.rs +++ b/src/lang/nl.rs @@ -656,5 +656,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Update client clipboard", "Klembord van client bijwerken"), ("Untagged", "Ongemarkeerd"), ("new-version-of-{}-tip", "Er is een nieuwe versie van {} beschikbaar"), + ("Accessible devices", ""), ].iter().cloned().collect(); } diff --git a/src/lang/pl.rs b/src/lang/pl.rs index e28430f49..968a2c0bc 100644 --- a/src/lang/pl.rs +++ b/src/lang/pl.rs @@ -656,5 +656,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Update client clipboard", "Uaktualnij schowek klienta"), ("Untagged", ""), ("new-version-of-{}-tip", ""), + ("Accessible devices", ""), ].iter().cloned().collect(); } diff --git a/src/lang/pt_PT.rs b/src/lang/pt_PT.rs index 13f829f77..be332f7ae 100644 --- a/src/lang/pt_PT.rs +++ b/src/lang/pt_PT.rs @@ -656,5 +656,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Update client clipboard", ""), ("Untagged", ""), ("new-version-of-{}-tip", ""), + ("Accessible devices", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ptbr.rs b/src/lang/ptbr.rs index eff01dd5e..81d736e65 100644 --- a/src/lang/ptbr.rs +++ b/src/lang/ptbr.rs @@ -656,5 +656,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Update client clipboard", ""), ("Untagged", ""), ("new-version-of-{}-tip", ""), + ("Accessible devices", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ro.rs b/src/lang/ro.rs index 8bd79c189..618de4a77 100644 --- a/src/lang/ro.rs +++ b/src/lang/ro.rs @@ -656,5 +656,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Update client clipboard", ""), ("Untagged", ""), ("new-version-of-{}-tip", ""), + ("Accessible devices", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ru.rs b/src/lang/ru.rs index b035947d5..e428fbd16 100644 --- a/src/lang/ru.rs +++ b/src/lang/ru.rs @@ -656,5 +656,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Update client clipboard", "Обновить буфер обмена клиента"), ("Untagged", "Без метки"), ("new-version-of-{}-tip", "Доступна новая версия {}"), + ("Accessible devices", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sk.rs b/src/lang/sk.rs index 96c7977cc..23d3c932d 100644 --- a/src/lang/sk.rs +++ b/src/lang/sk.rs @@ -656,5 +656,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Update client clipboard", ""), ("Untagged", ""), ("new-version-of-{}-tip", ""), + ("Accessible devices", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sl.rs b/src/lang/sl.rs index 6cfd29d6c..0baf7f254 100755 --- a/src/lang/sl.rs +++ b/src/lang/sl.rs @@ -656,5 +656,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Update client clipboard", "Osveži odjemalčevo odložišče"), ("Untagged", "Neoznačeno"), ("new-version-of-{}-tip", "Na voljo je nova različica {}"), + ("Accessible devices", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sq.rs b/src/lang/sq.rs index ad76f2f9c..6d121f775 100644 --- a/src/lang/sq.rs +++ b/src/lang/sq.rs @@ -656,5 +656,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Update client clipboard", ""), ("Untagged", ""), ("new-version-of-{}-tip", ""), + ("Accessible devices", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sr.rs b/src/lang/sr.rs index 286658657..cc7a41291 100644 --- a/src/lang/sr.rs +++ b/src/lang/sr.rs @@ -656,5 +656,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Update client clipboard", ""), ("Untagged", ""), ("new-version-of-{}-tip", ""), + ("Accessible devices", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sv.rs b/src/lang/sv.rs index fcb2fe1ae..2f1af1857 100644 --- a/src/lang/sv.rs +++ b/src/lang/sv.rs @@ -656,5 +656,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Update client clipboard", ""), ("Untagged", ""), ("new-version-of-{}-tip", ""), + ("Accessible devices", ""), ].iter().cloned().collect(); } diff --git a/src/lang/template.rs b/src/lang/template.rs index 9f1293ce1..b5edd1f83 100644 --- a/src/lang/template.rs +++ b/src/lang/template.rs @@ -656,5 +656,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Update client clipboard", ""), ("Untagged", ""), ("new-version-of-{}-tip", ""), + ("Accessible devices", ""), ].iter().cloned().collect(); } diff --git a/src/lang/th.rs b/src/lang/th.rs index 83fac2ab8..fd04d3605 100644 --- a/src/lang/th.rs +++ b/src/lang/th.rs @@ -656,5 +656,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Update client clipboard", ""), ("Untagged", ""), ("new-version-of-{}-tip", ""), + ("Accessible devices", ""), ].iter().cloned().collect(); } diff --git a/src/lang/tr.rs b/src/lang/tr.rs index 630add8bb..57c1c9254 100644 --- a/src/lang/tr.rs +++ b/src/lang/tr.rs @@ -656,5 +656,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Update client clipboard", ""), ("Untagged", ""), ("new-version-of-{}-tip", ""), + ("Accessible devices", ""), ].iter().cloned().collect(); } diff --git a/src/lang/tw.rs b/src/lang/tw.rs index d7eb8dc69..95b41989b 100644 --- a/src/lang/tw.rs +++ b/src/lang/tw.rs @@ -656,5 +656,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Update client clipboard", "更新客戶端的剪貼簿"), ("Untagged", "無標籤"), ("new-version-of-{}-tip", "有新版本的 {} 可用"), + ("Accessible devices", ""), ].iter().cloned().collect(); } diff --git a/src/lang/uk.rs b/src/lang/uk.rs index 9f0dfdefb..065d73fbe 100644 --- a/src/lang/uk.rs +++ b/src/lang/uk.rs @@ -656,5 +656,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Update client clipboard", "Оновити буфер обміну клієнта"), ("Untagged", "Без міток"), ("new-version-of-{}-tip", "Доступна нова версія {}"), + ("Accessible devices", ""), ].iter().cloned().collect(); } diff --git a/src/lang/vn.rs b/src/lang/vn.rs index 1ee2cd6d0..7f803bf66 100644 --- a/src/lang/vn.rs +++ b/src/lang/vn.rs @@ -656,5 +656,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Update client clipboard", ""), ("Untagged", ""), ("new-version-of-{}-tip", ""), + ("Accessible devices", ""), ].iter().cloned().collect(); }