Merge pull request #3500 from NicKoehler/sort-favorites

peer sorting implementation
This commit is contained in:
RustDesk 2023-03-10 13:05:33 +08:00 committed by GitHub
commit e5da7987b5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 274 additions and 64 deletions

View File

@ -42,6 +42,7 @@ class _PeerCardState extends State<_PeerCard>
with AutomaticKeepAliveClientMixin {
var _menuPos = RelativeRect.fill;
final double _cardRadius = 16;
final double _tileRadius = 5;
final double _borderWidth = 2;
@override
@ -116,27 +117,32 @@ class _PeerCardState extends State<_PeerCard>
Widget _buildDesktop() {
final peer = super.widget.peer;
var deco = Rx<BoxDecoration?>(BoxDecoration(
var deco = Rx<BoxDecoration?>(
BoxDecoration(
border: Border.all(color: Colors.transparent, width: _borderWidth),
borderRadius: peerCardUiType.value == PeerUiType.grid
? BorderRadius.circular(_cardRadius)
: null));
borderRadius: BorderRadius.circular(
peerCardUiType.value == PeerUiType.grid ? _cardRadius : _tileRadius,
),
),
);
return MouseRegion(
onEnter: (evt) {
deco.value = BoxDecoration(
border: Border.all(
color: Theme.of(context).colorScheme.primary,
width: _borderWidth),
borderRadius: peerCardUiType.value == PeerUiType.grid
? BorderRadius.circular(_cardRadius)
: null);
border: Border.all(
color: Theme.of(context).colorScheme.primary,
width: _borderWidth),
borderRadius: BorderRadius.circular(
peerCardUiType.value == PeerUiType.grid ? _cardRadius : _tileRadius,
),
);
},
onExit: (evt) {
deco.value = BoxDecoration(
border: Border.all(color: Colors.transparent, width: _borderWidth),
borderRadius: peerCardUiType.value == PeerUiType.grid
? BorderRadius.circular(_cardRadius)
: null);
border: Border.all(color: Colors.transparent, width: _borderWidth),
borderRadius: BorderRadius.circular(
peerCardUiType.value == PeerUiType.grid ? _cardRadius : _tileRadius,
),
);
},
child: GestureDetector(
onDoubleTap: () => widget.connect(context, peer.id),
@ -163,6 +169,10 @@ class _PeerCardState extends State<_PeerCard>
Container(
decoration: BoxDecoration(
color: str2color('${peer.id}${peer.platform}', 0x7f),
borderRadius: BorderRadius.only(
topLeft: Radius.circular(_tileRadius),
bottomLeft: Radius.circular(_tileRadius),
),
),
alignment: Alignment.center,
width: 42,
@ -171,7 +181,12 @@ class _PeerCardState extends State<_PeerCard>
Expanded(
child: Container(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.background),
color: Theme.of(context).colorScheme.background,
borderRadius: BorderRadius.only(
topRight: Radius.circular(_tileRadius),
bottomRight: Radius.circular(_tileRadius),
),
),
child: Row(
children: [
Expanded(

View File

@ -17,6 +17,7 @@ import 'package:get/get.dart';
import 'package:get/get_rx/src/rx_workers/utils/debouncer.dart';
import 'package:provider/provider.dart';
import 'package:visibility_detector/visibility_detector.dart';
import 'package:dropdown_button2/dropdown_button2.dart';
import '../../common.dart';
import '../../models/platform_model.dart';
@ -39,6 +40,8 @@ EdgeInsets? _menuPadding() {
class _PeerTabPageState extends State<PeerTabPage>
with SingleTickerProviderStateMixin {
bool _hideSort = bind.getLocalFlutterConfig(k: 'peer-tab-index') == '0';
final List<_TabEntry> entries = [
_TabEntry(
RecentPeersView(
@ -83,6 +86,7 @@ class _PeerTabPageState extends State<PeerTabPage>
if (tabIndex < entries.length) {
gFFI.peerTabModel.setCurrentTab(tabIndex);
entries[tabIndex].load();
_hideSort = tabIndex == 0;
}
}
@ -95,22 +99,27 @@ class _PeerTabPageState extends State<PeerTabPage>
SizedBox(
height: 28,
child: Container(
padding: isDesktop ? null : EdgeInsets.symmetric(horizontal: 2),
constraints: isDesktop ? null : kMobilePageConstraints,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: visibleContextMenuListener(
_createSwitchBar(context))),
buildScrollJumper(),
const PeerSearchBar(),
Offstage(
offstage: !isDesktop,
child: _createPeerViewTypeSwitch(context)
.marginOnly(left: 13)),
],
)),
padding: isDesktop ? null : EdgeInsets.symmetric(horizontal: 2),
constraints: isDesktop ? null : kMobilePageConstraints,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child:
visibleContextMenuListener(_createSwitchBar(context))),
buildScrollJumper(),
const PeerSearchBar(),
Offstage(
offstage: !isDesktop,
child: _createPeerViewTypeSwitch(context)
.marginOnly(left: 13)),
Offstage(
offstage: _hideSort,
child: PeerSortDropdown().marginOnly(left: 8),
),
],
),
),
),
_createPeersView(),
],
@ -231,32 +240,32 @@ class _PeerTabPageState extends State<PeerTabPage>
Widget _createPeerViewTypeSwitch(BuildContext context) {
final textColor = Theme.of(context).textTheme.titleLarge?.color;
final activeDeco =
BoxDecoration(color: Theme.of(context).colorScheme.background);
return Row(
children: [PeerUiType.grid, PeerUiType.list]
.map((type) => Obx(
() => Container(
padding: EdgeInsets.all(4.0),
decoration: peerCardUiType.value == type ? activeDeco : null,
child: InkWell(
onTap: () async {
await bind.setLocalFlutterConfig(
k: 'peer-card-ui-type', v: type.index.toString());
peerCardUiType.value = type;
},
child: Icon(
type == PeerUiType.grid
? Icons.grid_view_rounded
: Icons.list,
size: 18,
color:
peerCardUiType.value == type ? textColor : textColor
?..withOpacity(0.5),
)),
),
))
.toList(),
final deco = BoxDecoration(
color: Theme.of(context).colorScheme.background,
borderRadius: BorderRadius.circular(5),
);
final types = [PeerUiType.grid, PeerUiType.list];
return Obx(
() => Container(
padding: EdgeInsets.all(4.0),
decoration: deco,
child: InkWell(
onTap: () async {
final type = types.elementAt(
peerCardUiType.value == types.elementAt(0) ? 1 : 0);
await bind.setLocalFlutterConfig(
k: 'peer-card-ui-type', v: type.index.toString());
peerCardUiType.value = type;
},
child: Icon(
peerCardUiType.value == PeerUiType.grid
? Icons.list_rounded
: Icons.grid_view_rounded,
size: 18,
color: textColor,
)),
),
);
}
@ -417,3 +426,90 @@ class _PeerSearchBarState extends State<PeerSearchBar> {
);
}
}
class PeerSortDropdown extends StatefulWidget {
const PeerSortDropdown({super.key});
@override
State<PeerSortDropdown> createState() => _PeerSortDropdownState();
}
class _PeerSortDropdownState extends State<PeerSortDropdown> {
@override
void initState() {
if (!PeerSortType.values.contains(peerSort.value)) {
peerSort.value = PeerSortType.remoteId;
bind.setLocalFlutterConfig(
k: "peer-sorting",
v: peerSort.value,
);
}
super.initState();
}
@override
Widget build(BuildContext context) {
final deco = BoxDecoration(
color: Theme.of(context).colorScheme.background,
borderRadius: BorderRadius.circular(5),
);
return Container(
padding: EdgeInsets.all(4.0),
decoration: deco,
child: DropdownButtonHideUnderline(
child: DropdownButton2<String>(
onChanged: (v) async {
if (v != null) {
setState(() => peerSort.value = v);
await bind.setLocalFlutterConfig(
k: "peer-sorting",
v: peerSort.value,
);
}
},
customButton: Icon(
Icons.sort,
size: 18,
),
dropdownStyleData: DropdownStyleData(
decoration: BoxDecoration(
color: Theme.of(context).cardColor,
borderRadius: BorderRadius.circular(10),
),
width: 160,
),
items: [
DropdownMenuItem<String>(
alignment: Alignment.center,
child: Text(
translate("Sort by"),
style: TextStyle(fontWeight: FontWeight.bold),
),
enabled: false,
),
...PeerSortType.values
.map<DropdownMenuItem<String>>(
(String value) => DropdownMenuItem<String>(
value: value,
child: Row(
children: [
Icon(
value == peerSort.value
? Icons.radio_button_checked_rounded
: Icons.radio_button_off_rounded,
size: 18,
).paddingOnly(right: 12),
Text(
translate(value),
overflow: TextOverflow.ellipsis,
),
],
),
),
)
.toList(),
]),
),
);
}
}

View File

@ -16,8 +16,31 @@ import 'peer_card.dart';
typedef PeerFilter = bool Function(Peer peer);
typedef PeerCardBuilder = Widget Function(Peer peer);
class PeerSortType {
static const String remoteId = 'Remote ID';
static const String remoteHost = 'Remote Host';
static const String alias = 'Alias';
static const String username = 'Username';
static const String status = 'Status';
static List<String> values = [
PeerSortType.remoteId,
PeerSortType.remoteHost,
PeerSortType.alias,
PeerSortType.username,
PeerSortType.status
];
}
/// for peer search text, global obs value
final peerSearchText = "".obs;
/// for peer sort, global obs value
final peerSort = bind.getLocalFlutterConfig(k: 'peer-sorting').obs;
// list for listener
final obslist = [peerSearchText, peerSort].obs;
final peerSearchTextController =
TextEditingController(text: peerSearchText.value);
@ -114,7 +137,7 @@ class _PeersViewState extends State<_PeersView> with WindowListener {
String _peerId(String cardId) => cardId.replaceAll(widget.peers.name, '');
Widget _buildPeersView(Peers peers) {
final body = ObxValue<RxString>((searchText) {
final body = ObxValue<RxList>((filters) {
return FutureBuilder<List<Peer>>(
builder: (context, snapshot) {
if (snapshot.hasData) {
@ -144,9 +167,9 @@ class _PeersViewState extends State<_PeersView> with WindowListener {
);
}
},
future: matchPeers(searchText.value, peers.peers),
future: matchPeers(filters[0].value, filters[1].value, peers.peers),
);
}, peerSearchText);
}, obslist);
return body;
}
@ -185,11 +208,44 @@ class _PeersViewState extends State<_PeersView> with WindowListener {
}();
}
Future<List<Peer>>? matchPeers(String searchText, List<Peer> peers) async {
Future<List<Peer>>? matchPeers(
String searchText, String sortedBy, List<Peer> peers) async {
if (widget.peerFilter != null) {
peers = peers.where((peer) => widget.peerFilter!(peer)).toList();
}
// fallback to id sorting
if (!PeerSortType.values.contains(sortedBy)) {
sortedBy = PeerSortType.remoteId;
bind.setLocalFlutterConfig(
k: "peer-sorting",
v: sortedBy,
);
}
if (widget.peers.loadEvent != 'load_recent_peers') {
switch (sortedBy) {
case PeerSortType.remoteId:
peers.sort((p1, p2) => p1.id.compareTo(p2.id));
break;
case PeerSortType.remoteHost:
peers.sort((p1, p2) =>
p1.hostname.toLowerCase().compareTo(p2.hostname.toLowerCase()));
break;
case PeerSortType.alias:
peers.sort((p1, p2) =>
p1.alias.toLowerCase().compareTo(p2.alias.toLowerCase()));
break;
case PeerSortType.username:
peers.sort((p1, p2) =>
p1.username.toLowerCase().compareTo(p2.username.toLowerCase()));
break;
case PeerSortType.status:
peers.sort((p1, p2) => p1.online ? -1 : 1);
break;
}
}
searchText = searchText.trim();
if (searchText.isEmpty) {
return peers;
@ -203,6 +259,7 @@ class _PeersViewState extends State<_PeersView> with WindowListener {
filteredList.add(peers[i]);
}
}
return filteredList;
}
}

View File

@ -386,6 +386,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.0.2"
dropdown_button2:
dependency: "direct main"
description:
name: dropdown_button2
sha256: "4458d81bfd24207f3d58f66f78097064e02f810f94cf1bc80bf20fe7685ebc80"
url: "https://pub.dev"
source: hosted
version: "2.0.0"
event_bus:
dependency: transitive
description:

View File

@ -94,6 +94,7 @@ dependencies:
flutter_keyboard_visibility: ^5.4.0
texture_rgba_renderer: ^0.0.12
percent_indicator: ^4.2.2
dropdown_button2: ^2.0.0
dev_dependencies:
icons_launcher: ^2.0.4

View File

@ -464,5 +464,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("idd_driver_tip", ""),
("confirm_idd_driver_tip", ""),
("RDP Settings", ""),
("Sort by", ""),
].iter().cloned().collect();
}

View File

@ -464,5 +464,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("idd_driver_tip", "安装虚拟显示器驱动,以便在没有连接显示器的情况下启动虚拟显示器进行控制。"),
("confirm_idd_driver_tip", "安装虚拟显示器驱动的选项已勾选。请注意测试证书将被安装以信任虚拟显示器驱动。测试证书仅会用于信任Rustdesk的驱动。"),
("RDP Settings", ""),
("Sort by", ""),
].iter().cloned().collect();
}

View File

@ -464,5 +464,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("idd_driver_tip", ""),
("confirm_idd_driver_tip", ""),
("RDP Settings", ""),
("Sort by", ""),
].iter().cloned().collect();
}

View File

@ -464,5 +464,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("idd_driver_tip", ""),
("confirm_idd_driver_tip", ""),
("RDP Settings", ""),
("Sort by", ""),
].iter().cloned().collect();
}

View File

@ -464,5 +464,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("idd_driver_tip", "Installieren Sie den virtuellen Anzeigetreiber, der verwendet wird, wenn Sie keine physischen Anzeigen haben."),
("confirm_idd_driver_tip", "Die Option zur Installation des virtuellen Anzeigetreibers ist aktiviert. Beachten Sie, dass ein Testzertifikat installiert wird, um dem virtuellen Anzeigetreiber zu vertrauen. Dieses Testzertifikat wird nur verwendet, um Rustdesk-Treibern zu vertrauen."),
("RDP Settings", "RDP-Einstellungen"),
("Sort by", ""),
].iter().cloned().collect();
}

View File

@ -464,5 +464,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("idd_driver_tip", "Εγκαταστήστε το πρόγραμμα οδήγησης εικονικής οθόνης που χρησιμοποιείται όταν δεν έχετε φυσικές οθόνες."),
("confirm_idd_driver_tip", "Είναι ενεργοποιημένη η επιλογή εγκατάστασης του προγράμματος οδήγησης εικονικής οθόνης. Λάβετε υπόψη ότι θα εγκατασταθεί ένα δοκιμαστικό πιστοποιητικό για το πρόγραμμα οδήγησης εικονικής οθόνης. Αυτό το πιστοποιητικό θα χρησιμοποιηθεί μόνο για την πιστοποίηση των προγραμμάτων οδήγησης του Rustdesk."),
("RDP Settings", "Ρυθμίσεις RDP"),
("Sort by", ""),
].iter().cloned().collect();
}

View File

@ -464,5 +464,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("idd_driver_tip", ""),
("confirm_idd_driver_tip", ""),
("RDP Settings", ""),
("Sort by", ""),
].iter().cloned().collect();
}

View File

@ -464,5 +464,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("idd_driver_tip", "Instalar controlador virtual de pantalla a usar cuando no hay pantalla física."),
("confirm_idd_driver_tip", "La opción de instalar el controlador de pantalla virtual está marcada. Hay que tener en cuenta que se instalará un certificado de prueba para confirar en el controlador de pantalla. Este certificado solo se usará para confiar en controladores Rustdesk."),
("RDP Settings", "Ajustes RDP"),
("Sort by", ""),
].iter().cloned().collect();
}

View File

@ -464,5 +464,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("idd_driver_tip", ""),
("confirm_idd_driver_tip", ""),
("RDP Settings", ""),
("Sort by", ""),
].iter().cloned().collect();
}

View File

@ -464,5 +464,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("idd_driver_tip", ""),
("confirm_idd_driver_tip", ""),
("RDP Settings", ""),
("Sort by", ""),
].iter().cloned().collect();
}

View File

@ -464,5 +464,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("idd_driver_tip", ""),
("confirm_idd_driver_tip", ""),
("RDP Settings", ""),
("Sort by", ""),
].iter().cloned().collect();
}

View File

@ -464,5 +464,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("idd_driver_tip", ""),
("confirm_idd_driver_tip", ""),
("RDP Settings", ""),
("Sort by", ""),
].iter().cloned().collect();
}

View File

@ -208,7 +208,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("x11 expected", "x11 necessario"),
("Port", "Porta"),
("Settings", "Impostazioni"),
("Username", " Nome utente"),
("Username", "Nome utente"),
("Invalid port", "Numero di porta non valido"),
("Closed manually by the peer", "Chiuso manualmente dal peer"),
("Enable remote configuration modification", "Abilita la modifica remota della configurazione"),
@ -461,8 +461,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Resolution", "Risoluzione"),
("No transfers in progress", "Nessun trasferimento in corso"),
("Set one-time password length", "Imposta la lunghezza della password monouso"),
("idd_driver_tip", ""),
("confirm_idd_driver_tip", ""),
("idd_driver_tip", "Installa il driver per lo schermo virtuale che sarà utilizzato quando non si dispone di schermi fisici."),
("confirm_idd_driver_tip", "L'opzione per installare il driver per lo schermo virtuale è selezionata. Nota che un certificato di test sarà installato per l'attendibilità del driver dello schermo virtuale. Questo certificato di test verrà utilizzato solo per l'attendibilità dei driver di RustDesk."),
("RDP Settings", "Impostazioni RDP"),
("Sort by", "Ordina per"),
].iter().cloned().collect();
}

View File

@ -464,5 +464,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("idd_driver_tip", ""),
("confirm_idd_driver_tip", ""),
("RDP Settings", ""),
("Sort by", ""),
].iter().cloned().collect();
}

View File

@ -464,5 +464,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("idd_driver_tip", ""),
("confirm_idd_driver_tip", ""),
("RDP Settings", ""),
("Sort by", ""),
].iter().cloned().collect();
}

View File

@ -464,5 +464,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("idd_driver_tip", ""),
("confirm_idd_driver_tip", ""),
("RDP Settings", ""),
("Sort by", ""),
].iter().cloned().collect();
}

View File

@ -464,5 +464,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("idd_driver_tip", ""),
("confirm_idd_driver_tip", ""),
("RDP Settings", ""),
("Sort by", ""),
].iter().cloned().collect();
}

View File

@ -464,5 +464,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("idd_driver_tip", ""),
("confirm_idd_driver_tip", ""),
("RDP Settings", ""),
("Sort by", ""),
].iter().cloned().collect();
}

View File

@ -464,5 +464,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("idd_driver_tip", ""),
("confirm_idd_driver_tip", ""),
("RDP Settings", ""),
("Sort by", ""),
].iter().cloned().collect();
}

View File

@ -464,5 +464,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("idd_driver_tip", ""),
("confirm_idd_driver_tip", ""),
("RDP Settings", ""),
("Sort by", ""),
].iter().cloned().collect();
}

View File

@ -464,5 +464,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("idd_driver_tip", ""),
("confirm_idd_driver_tip", ""),
("RDP Settings", ""),
("Sort by", ""),
].iter().cloned().collect();
}

View File

@ -464,5 +464,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("idd_driver_tip", "Установите драйвер виртуального дисплея, который используется при отсутствии физических дисплеев."),
("confirm_idd_driver_tip", "Включена функция установки драйвера виртуального дисплея. Обратите внимание, что для доверия к драйверу будет установлен тестовый сертификат. Этот сертификат будет использоваться только для подтверждения доверия драйверам Rustdesk."),
("RDP Settings", "Настройки RDP"),
("Sort by", ""),
].iter().cloned().collect();
}

View File

@ -464,5 +464,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("idd_driver_tip", ""),
("confirm_idd_driver_tip", ""),
("RDP Settings", ""),
("Sort by", ""),
].iter().cloned().collect();
}

View File

@ -464,5 +464,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("idd_driver_tip", ""),
("confirm_idd_driver_tip", ""),
("RDP Settings", ""),
("Sort by", ""),
].iter().cloned().collect();
}

View File

@ -464,5 +464,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("idd_driver_tip", ""),
("confirm_idd_driver_tip", ""),
("RDP Settings", ""),
("Sort by", ""),
].iter().cloned().collect();
}

View File

@ -464,5 +464,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("idd_driver_tip", ""),
("confirm_idd_driver_tip", ""),
("RDP Settings", ""),
("Sort by", ""),
].iter().cloned().collect();
}

View File

@ -464,5 +464,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("idd_driver_tip", ""),
("confirm_idd_driver_tip", ""),
("RDP Settings", ""),
("Sort by", ""),
].iter().cloned().collect();
}

View File

@ -464,5 +464,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("idd_driver_tip", ""),
("confirm_idd_driver_tip", ""),
("RDP Settings", ""),
("Sort by", ""),
].iter().cloned().collect();
}

View File

@ -464,5 +464,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("idd_driver_tip", ""),
("confirm_idd_driver_tip", ""),
("RDP Settings", ""),
("Sort by", ""),
].iter().cloned().collect();
}

View File

@ -464,5 +464,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("idd_driver_tip", ""),
("confirm_idd_driver_tip", ""),
("RDP Settings", ""),
("Sort by", ""),
].iter().cloned().collect();
}

View File

@ -464,5 +464,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("idd_driver_tip", ""),
("confirm_idd_driver_tip", ""),
("RDP Settings", ""),
("Sort by", ""),
].iter().cloned().collect();
}

View File

@ -464,5 +464,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("idd_driver_tip", ""),
("confirm_idd_driver_tip", ""),
("RDP Settings", ""),
("Sort by", ""),
].iter().cloned().collect();
}

View File

@ -464,5 +464,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("idd_driver_tip", ""),
("confirm_idd_driver_tip", ""),
("RDP Settings", ""),
("Sort by", ""),
].iter().cloned().collect();
}