refact: optimize, loading recent peers (#10847)

Signed-off-by: fufesou <linlong1266@gmail.com>
This commit is contained in:
fufesou
2025-02-20 11:53:36 +08:00
committed by GitHub
parent 2e89a33210
commit 055b351164
8 changed files with 265 additions and 139 deletions

View File

@@ -6,56 +6,123 @@ import 'package:flutter_hbb/models/peer_model.dart';
import 'package:flutter_hbb/common.dart'; import 'package:flutter_hbb/common.dart';
import 'package:flutter_hbb/common/widgets/peer_card.dart'; import 'package:flutter_hbb/common/widgets/peer_card.dart';
Future<List<Peer>> getAllPeers() async { class AllPeersLoader {
Map<String, dynamic> recentPeers = jsonDecode(bind.mainLoadRecentPeersSync()); List<Peer> peers = [];
Map<String, dynamic> lanPeers = jsonDecode(bind.mainLoadLanPeersSync()); bool hasMoreRecentPeers = false;
Map<String, dynamic> combinedPeers = {};
void mergePeers(Map<String, dynamic> peers) { bool isPeersLoading = false;
if (peers.containsKey("peers")) { bool _isPartialPeersLoaded = false;
dynamic peerData = peers["peers"]; bool _isPeersLoaded = false;
if (peerData is String) { AllPeersLoader();
try {
peerData = jsonDecode(peerData); bool get isLoaded => _isPartialPeersLoaded || _isPeersLoaded;
} catch (e) {
print("Error decoding peers: $e"); void reset() {
return; peers.clear();
} hasMoreRecentPeers = false;
_isPartialPeersLoaded = false;
_isPeersLoaded = false;
}
Future<void> getAllPeers(void Function(VoidCallback) setState) async {
if (isPeersLoading) {
return;
}
reset();
isPeersLoading = true;
final startTime = DateTime.now();
await _getAllPeers(false);
if (!hasMoreRecentPeers) {
final diffTime = DateTime.now().difference(startTime).inMilliseconds;
if (diffTime < 100) {
await Future.delayed(Duration(milliseconds: diffTime));
} }
setState(() {
isPeersLoading = false;
_isPeersLoaded = true;
});
} else {
setState(() {
_isPartialPeersLoaded = true;
});
await _getAllPeers(true);
setState(() {
isPeersLoading = false;
_isPeersLoaded = true;
});
}
}
if (peerData is List) { Future<void> _getAllPeers(bool getAllRecentPeers) async {
for (var peer in peerData) { Map<String, dynamic> recentPeers =
if (peer is Map && peer.containsKey("id")) { jsonDecode(await bind.mainGetRecentPeers(getAll: getAllRecentPeers));
String id = peer["id"]; Map<String, dynamic> lanPeers = jsonDecode(bind.mainLoadLanPeersSync());
if (!combinedPeers.containsKey(id)) { Map<String, dynamic> combinedPeers = {};
combinedPeers[id] = peer;
void mergePeers(Map<String, dynamic> peers) {
if (peers.containsKey("peers")) {
dynamic peerData = peers["peers"];
if (peerData is String) {
try {
peerData = jsonDecode(peerData);
} catch (e) {
print("Error decoding peers: $e");
return;
}
}
if (peerData is List) {
for (var peer in peerData) {
if (peer is Map && peer.containsKey("id")) {
String id = peer["id"];
if (!combinedPeers.containsKey(id)) {
combinedPeers[id] = peer;
}
} }
} }
} }
} }
} }
}
mergePeers(recentPeers); mergePeers(recentPeers);
mergePeers(lanPeers); mergePeers(lanPeers);
for (var p in gFFI.abModel.allPeers()) { for (var p in gFFI.abModel.allPeers()) {
if (!combinedPeers.containsKey(p.id)) { if (!combinedPeers.containsKey(p.id)) {
combinedPeers[p.id] = p.toJson(); combinedPeers[p.id] = p.toJson();
}
} }
} for (var p in gFFI.groupModel.peers.map((e) => Peer.copy(e)).toList()) {
for (var p in gFFI.groupModel.peers.map((e) => Peer.copy(e)).toList()) { if (!combinedPeers.containsKey(p.id)) {
if (!combinedPeers.containsKey(p.id)) { combinedPeers[p.id] = p.toJson();
combinedPeers[p.id] = p.toJson(); }
} }
}
List<Peer> parsedPeers = []; List<Peer> parsedPeers = [];
for (var peer in combinedPeers.values) { for (var peer in combinedPeers.values) {
parsedPeers.add(Peer.fromJson(peer)); parsedPeers.add(Peer.fromJson(peer));
}
try {
final List<dynamic> moreRecentPeerIds =
jsonDecode(recentPeers["ids"] ?? '[]');
hasMoreRecentPeers = false;
for (final id in moreRecentPeerIds) {
final sid = id.toString();
if (!parsedPeers.any((element) => element.id == sid)) {
parsedPeers.add(Peer.fromJson({'id': sid}));
hasMoreRecentPeers = true;
}
}
} catch (e) {
debugPrint("Error parsing more peer ids: $e");
}
peers = parsedPeers;
} }
return parsedPeers;
} }
class AutocompletePeerTile extends StatefulWidget { class AutocompletePeerTile extends StatefulWidget {

View File

@@ -200,18 +200,20 @@ class _ConnectionPageState extends State<ConnectionPage>
final _idController = IDTextEditingController(); final _idController = IDTextEditingController();
final RxBool _idInputFocused = false.obs; final RxBool _idInputFocused = false.obs;
final FocusNode _idFocusNode = FocusNode();
final TextEditingController _idEditingController = TextEditingController();
bool isWindowMinimized = false; bool isWindowMinimized = false;
List<Peer> peers = [];
bool isPeersLoading = false; AllPeersLoader allPeersLoader = AllPeersLoader();
bool isPeersLoaded = false;
// https://github.com/flutter/flutter/issues/157244 // https://github.com/flutter/flutter/issues/157244
Iterable<Peer> _autocompleteOpts = []; Iterable<Peer> _autocompleteOpts = [];
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_idFocusNode.addListener(onFocusChanged);
if (_idController.text.isEmpty) { if (_idController.text.isEmpty) {
WidgetsBinding.instance.addPostFrameCallback((_) async { WidgetsBinding.instance.addPostFrameCallback((_) async {
final lastRemoteId = await bind.mainGetLastRemoteId(); final lastRemoteId = await bind.mainGetLastRemoteId();
@@ -230,6 +232,9 @@ class _ConnectionPageState extends State<ConnectionPage>
void dispose() { void dispose() {
_idController.dispose(); _idController.dispose();
windowManager.removeListener(this); windowManager.removeListener(this);
_idFocusNode.removeListener(onFocusChanged);
_idFocusNode.dispose();
_idEditingController.dispose();
if (Get.isRegistered<IDTextEditingController>()) { if (Get.isRegistered<IDTextEditingController>()) {
Get.delete<IDTextEditingController>(); Get.delete<IDTextEditingController>();
} }
@@ -273,6 +278,13 @@ class _ConnectionPageState extends State<ConnectionPage>
bind.mainOnMainWindowClose(); bind.mainOnMainWindowClose();
} }
void onFocusChanged() {
_idInputFocused.value = _idFocusNode.hasFocus;
if (_idFocusNode.hasFocus && !allPeersLoader.isPeersLoading) {
allPeersLoader.getAllPeers(setState);
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final isOutgoingOnly = bind.isOutgoingOnly(); final isOutgoingOnly = bind.isOutgoingOnly();
@@ -304,18 +316,6 @@ class _ConnectionPageState extends State<ConnectionPage>
connect(context, id, isFileTransfer: isFileTransfer); connect(context, id, isFileTransfer: isFileTransfer);
} }
Future<void> _fetchPeers() async {
setState(() {
isPeersLoading = true;
});
await Future.delayed(Duration(milliseconds: 100));
peers = await getAllPeers();
setState(() {
isPeersLoading = false;
isPeersLoaded = true;
});
}
/// UI for the remote ID TextField. /// UI for the remote ID TextField.
/// Search for a peer. /// Search for a peer.
Widget _buildRemoteIDTextField(BuildContext context) { Widget _buildRemoteIDTextField(BuildContext context) {
@@ -332,11 +332,12 @@ class _ConnectionPageState extends State<ConnectionPage>
Row( Row(
children: [ children: [
Expanded( Expanded(
child: Autocomplete<Peer>( child: RawAutocomplete<Peer>(
optionsBuilder: (TextEditingValue textEditingValue) { optionsBuilder: (TextEditingValue textEditingValue) {
if (textEditingValue.text == '') { if (textEditingValue.text == '') {
_autocompleteOpts = const Iterable<Peer>.empty(); _autocompleteOpts = const Iterable<Peer>.empty();
} else if (peers.isEmpty && !isPeersLoaded) { } else if (allPeersLoader.peers.isEmpty &&
!allPeersLoader.isLoaded) {
Peer emptyPeer = Peer( Peer emptyPeer = Peer(
id: '', id: '',
username: '', username: '',
@@ -363,7 +364,7 @@ class _ConnectionPageState extends State<ConnectionPage>
); );
} }
String textToFind = textEditingValue.text.toLowerCase(); String textToFind = textEditingValue.text.toLowerCase();
_autocompleteOpts = peers _autocompleteOpts = allPeersLoader.peers
.where((peer) => .where((peer) =>
peer.id.toLowerCase().contains(textToFind) || peer.id.toLowerCase().contains(textToFind) ||
peer.username peer.username
@@ -377,6 +378,8 @@ class _ConnectionPageState extends State<ConnectionPage>
} }
return _autocompleteOpts; return _autocompleteOpts;
}, },
focusNode: _idFocusNode,
textEditingController: _idEditingController,
fieldViewBuilder: ( fieldViewBuilder: (
BuildContext context, BuildContext context,
TextEditingController fieldTextEditingController, TextEditingController fieldTextEditingController,
@@ -385,17 +388,17 @@ class _ConnectionPageState extends State<ConnectionPage>
) { ) {
fieldTextEditingController.text = _idController.text; fieldTextEditingController.text = _idController.text;
Get.put<TextEditingController>(fieldTextEditingController); Get.put<TextEditingController>(fieldTextEditingController);
fieldFocusNode.addListener(() async {
_idInputFocused.value = fieldFocusNode.hasFocus; // The listener will be added multiple times when the widget is rebuilt.
if (fieldFocusNode.hasFocus && !isPeersLoading) { // We may need to use the `RawAutocomplete` to get the focus node.
_fetchPeers();
} // Temporarily remove Selection because Selection can cause users to accidentally delete previously entered content during input.
}); // final textLength =
final textLength = // fieldTextEditingController.value.text.length;
fieldTextEditingController.value.text.length; // // Select all to facilitate removing text, just following the behavior of address input of chrome.
// select all to facilitate removing text, just following the behavior of address input of chrome // fieldTextEditingController.selection =
fieldTextEditingController.selection = // TextSelection(baseOffset: 0, extentOffset: textLength);
TextSelection(baseOffset: 0, extentOffset: textLength);
return Obx(() => TextField( return Obx(() => TextField(
autocorrect: false, autocorrect: false,
enableSuggestions: false, enableSuggestions: false,
@@ -468,7 +471,8 @@ class _ConnectionPageState extends State<ConnectionPage>
maxHeight: maxHeight, maxHeight: maxHeight,
maxWidth: 319, maxWidth: 319,
), ),
child: peers.isEmpty && isPeersLoading child: allPeersLoader.peers.isEmpty &&
!allPeersLoader.isLoaded
? Container( ? Container(
height: 80, height: 80,
child: Center( child: Center(

View File

@@ -41,10 +41,11 @@ class _ConnectionPageState extends State<ConnectionPage> {
final _idController = IDTextEditingController(); final _idController = IDTextEditingController();
final RxBool _idEmpty = true.obs; final RxBool _idEmpty = true.obs;
List<Peer> peers = []; final FocusNode _idFocusNode = FocusNode();
final TextEditingController _idEditingController = TextEditingController();
AllPeersLoader allPeersLoader = AllPeersLoader();
bool isPeersLoading = false;
bool isPeersLoaded = false;
StreamSubscription? _uniLinksSubscription; StreamSubscription? _uniLinksSubscription;
// https://github.com/flutter/flutter/issues/157244 // https://github.com/flutter/flutter/issues/157244
@@ -61,6 +62,7 @@ class _ConnectionPageState extends State<ConnectionPage> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_idFocusNode.addListener(onFocusChanged);
if (_idController.text.isEmpty) { if (_idController.text.isEmpty) {
WidgetsBinding.instance.addPostFrameCallback((_) async { WidgetsBinding.instance.addPostFrameCallback((_) async {
final lastRemoteId = await bind.mainGetLastRemoteId(); final lastRemoteId = await bind.mainGetLastRemoteId();
@@ -99,6 +101,13 @@ class _ConnectionPageState extends State<ConnectionPage> {
connect(context, id); connect(context, id);
} }
void onFocusChanged() {
_idEmpty.value = _idEditingController.text.isEmpty;
if (_idFocusNode.hasFocus && !allPeersLoader.isPeersLoading) {
allPeersLoader.getAllPeers(setState);
}
}
/// UI for software update. /// UI for software update.
/// If _updateUrl] is not empty, shows a button to update the software. /// If _updateUrl] is not empty, shows a button to update the software.
Widget _buildUpdateUI(String updateUrl) { Widget _buildUpdateUI(String updateUrl) {
@@ -127,18 +136,6 @@ class _ConnectionPageState extends State<ConnectionPage> {
color: Colors.white, fontWeight: FontWeight.bold)))); color: Colors.white, fontWeight: FontWeight.bold))));
} }
Future<void> _fetchPeers() async {
setState(() {
isPeersLoading = true;
});
await Future.delayed(Duration(milliseconds: 100));
peers = await getAllPeers();
setState(() {
isPeersLoading = false;
isPeersLoaded = true;
});
}
/// UI for the remote ID TextField. /// UI for the remote ID TextField.
/// Search for a peer and connect to it if the id exists. /// Search for a peer and connect to it if the id exists.
Widget _buildRemoteIDTextField() { Widget _buildRemoteIDTextField() {
@@ -156,11 +153,12 @@ class _ConnectionPageState extends State<ConnectionPage> {
Expanded( Expanded(
child: Container( child: Container(
padding: const EdgeInsets.only(left: 16, right: 16), padding: const EdgeInsets.only(left: 16, right: 16),
child: Autocomplete<Peer>( child: RawAutocomplete<Peer>(
optionsBuilder: (TextEditingValue textEditingValue) { optionsBuilder: (TextEditingValue textEditingValue) {
if (textEditingValue.text == '') { if (textEditingValue.text == '') {
_autocompleteOpts = const Iterable<Peer>.empty(); _autocompleteOpts = const Iterable<Peer>.empty();
} else if (peers.isEmpty && !isPeersLoaded) { } else if (allPeersLoader.peers.isEmpty &&
!allPeersLoader.isLoaded) {
Peer emptyPeer = Peer( Peer emptyPeer = Peer(
id: '', id: '',
username: '', username: '',
@@ -188,7 +186,7 @@ class _ConnectionPageState extends State<ConnectionPage> {
} }
String textToFind = textEditingValue.text.toLowerCase(); String textToFind = textEditingValue.text.toLowerCase();
_autocompleteOpts = peers _autocompleteOpts = allPeersLoader.peers
.where((peer) => .where((peer) =>
peer.id.toLowerCase().contains(textToFind) || peer.id.toLowerCase().contains(textToFind) ||
peer.username peer.username
@@ -202,6 +200,8 @@ class _ConnectionPageState extends State<ConnectionPage> {
} }
return _autocompleteOpts; return _autocompleteOpts;
}, },
focusNode: _idFocusNode,
textEditingController: _idEditingController,
fieldViewBuilder: (BuildContext context, fieldViewBuilder: (BuildContext context,
TextEditingController fieldTextEditingController, TextEditingController fieldTextEditingController,
FocusNode fieldFocusNode, FocusNode fieldFocusNode,
@@ -209,18 +209,14 @@ class _ConnectionPageState extends State<ConnectionPage> {
fieldTextEditingController.text = _idController.text; fieldTextEditingController.text = _idController.text;
Get.put<TextEditingController>( Get.put<TextEditingController>(
fieldTextEditingController); fieldTextEditingController);
fieldFocusNode.addListener(() async {
_idEmpty.value = // Temporarily remove Selection because Selection can cause users to accidentally delete previously entered content during input.
fieldTextEditingController.text.isEmpty; // final textLength =
if (fieldFocusNode.hasFocus && !isPeersLoading) { // fieldTextEditingController.value.text.length;
_fetchPeers(); // // select all to facilitate removing text, just following the behavior of address input of chrome
} // fieldTextEditingController.selection = TextSelection(
}); // baseOffset: 0, extentOffset: textLength);
final textLength =
fieldTextEditingController.value.text.length;
// select all to facilitate removing text, just following the behavior of address input of chrome
fieldTextEditingController.selection = TextSelection(
baseOffset: 0, extentOffset: textLength);
return AutoSizeTextField( return AutoSizeTextField(
controller: fieldTextEditingController, controller: fieldTextEditingController,
focusNode: fieldFocusNode, focusNode: fieldFocusNode,
@@ -300,7 +296,8 @@ class _ConnectionPageState extends State<ConnectionPage> {
maxHeight: maxHeight, maxHeight: maxHeight,
maxWidth: 320, maxWidth: 320,
), ),
child: peers.isEmpty && isPeersLoading child: allPeersLoader.peers.isEmpty &&
!allPeersLoader.isLoaded
? Container( ? Container(
height: 80, height: 80,
child: Center( child: Center(
@@ -363,6 +360,9 @@ class _ConnectionPageState extends State<ConnectionPage> {
void dispose() { void dispose() {
_uniLinksSubscription?.cancel(); _uniLinksSubscription?.cancel();
_idController.dispose(); _idController.dispose();
_idFocusNode.removeListener(onFocusChanged);
_idFocusNode.dispose();
_idEditingController.dispose();
if (Get.isRegistered<IDTextEditingController>()) { if (Get.isRegistered<IDTextEditingController>()) {
Get.delete<IDTextEditingController>(); Get.delete<IDTextEditingController>();
} }

View File

@@ -6,12 +6,13 @@ import 'package:flutter_hbb/models/platform_model.dart';
void showPeerSelectionDialog( void showPeerSelectionDialog(
{bool singleSelection = false, {bool singleSelection = false,
required Function(List<String>) onPeersCallback}) { required Function(List<String>) onPeersCallback}) async {
final peers = bind.mainLoadRecentPeersSync(); final peers = await bind.mainGetRecentPeers(getAll: true);
if (peers.isEmpty) { if (peers.isEmpty) {
debugPrint("load recent peers sync failed."); debugPrint("load recent peers failed.");
return; return;
} }
Map<String, dynamic> map = jsonDecode(peers); Map<String, dynamic> map = jsonDecode(peers);
List<dynamic> peersList = map['peers'] ?? []; List<dynamic> peersList = map['peers'] ?? [];
final selected = List<String>.empty(growable: true); final selected = List<String>.empty(growable: true);

View File

@@ -169,6 +169,8 @@ pub fn core_main() -> Option<Vec<String>> {
#[cfg(not(any(target_os = "android", target_os = "ios")))] #[cfg(not(any(target_os = "android", target_os = "ios")))]
init_plugins(&args); init_plugins(&args);
if args.is_empty() || crate::common::is_empty_uni_link(&args[0]) { if args.is_empty() || crate::common::is_empty_uni_link(&args[0]) {
#[cfg(windows)]
hbb_common::config::PeerConfig::preload_peers();
std::thread::spawn(move || crate::start_server(false, no_server)); std::thread::spawn(move || crate::start_server(false, no_server));
} else { } else {
#[cfg(windows)] #[cfg(windows)]

View File

@@ -1101,44 +1101,99 @@ pub fn main_peer_exists(id: String) -> bool {
peer_exists(&id) peer_exists(&id)
} }
fn load_recent_peers(
vec_id_modified_time_path: &Vec<(String, SystemTime, std::path::PathBuf)>,
to_end: bool,
all_peers: &mut Vec<HashMap<&str, String>>,
from: usize,
) -> usize {
let to = if to_end {
Some(vec_id_modified_time_path.len())
} else {
None
};
let mut peers_next = PeerConfig::batch_peers(vec_id_modified_time_path, from, to);
// There may be less peers than the batch size.
// But no need to consider this case, because it is a rare case.
let peers = peers_next.0.drain(..).map(|(id, _, p)| peer_to_map(id, p));
all_peers.extend(peers);
peers_next.1
}
pub fn main_load_recent_peers() { pub fn main_load_recent_peers() {
if !config::APP_DIR.read().unwrap().is_empty() { if !config::APP_DIR.read().unwrap().is_empty() {
let peers: Vec<HashMap<&str, String>> = PeerConfig::peers(None) let vec_id_modified_time_path = PeerConfig::get_vec_id_modified_time_path(&None);
.drain(..) if vec_id_modified_time_path.is_empty() {
.map(|(id, _, p)| peer_to_map(id, p)) return;
.collect(); }
let data = HashMap::from([ let push_to_flutter = |peers| {
("name", "load_recent_peers".to_owned()), let data = HashMap::from([("name", "load_recent_peers".to_owned()), ("peers", peers)]);
( let _res = flutter::push_global_event(
"peers", flutter::APP_TYPE_MAIN,
serde_json::ser::to_string(&peers).unwrap_or("".to_owned()), serde_json::ser::to_string(&data).unwrap_or("".to_owned()),
), );
]); };
let _res = flutter::push_global_event(
flutter::APP_TYPE_MAIN, let load_two_times =
serde_json::ser::to_string(&data).unwrap_or("".to_owned()), vec_id_modified_time_path.len() > PeerConfig::BATCH_LOADING_COUNT && cfg!(target_os = "windows");
); let mut all_peers = vec![];
if load_two_times {
let next_from = load_recent_peers(&vec_id_modified_time_path, false, &mut all_peers, 0);
push_to_flutter(serde_json::ser::to_string(&all_peers).unwrap_or("".to_owned()));
let _ = load_recent_peers(&vec_id_modified_time_path, true, &mut all_peers, next_from);
} else {
let _ = load_recent_peers(&vec_id_modified_time_path, true, &mut all_peers, 0);
}
push_to_flutter(serde_json::ser::to_string(&all_peers).unwrap_or("".to_owned()));
} }
} }
pub fn main_load_recent_peers_sync() -> SyncReturn<String> { fn get_partial_recent_peers(
if !config::APP_DIR.read().unwrap().is_empty() { vec_id_modified_time_path: Vec<(String, SystemTime, std::path::PathBuf)>,
let peers: Vec<HashMap<&str, String>> = PeerConfig::peers(None) count: usize,
.drain(..) ) -> String {
.map(|(id, _, p)| peer_to_map(id, p)) let (peers, next_from) = PeerConfig::batch_peers(
&vec_id_modified_time_path,
0,
Some(count.min(vec_id_modified_time_path.len())),
);
let peer_maps: Vec<_> = peers
.into_iter()
.map(|(id, _, p)| peer_to_map(id, p))
.collect();
let mut data = HashMap::from([(
"peers",
serde_json::ser::to_string(&peer_maps).unwrap_or("[]".to_owned()),
)]);
if next_from < vec_id_modified_time_path.len() {
let ids: Vec<_> = vec_id_modified_time_path[next_from..]
.iter()
.map(|(id, _, _)| id.clone())
.collect(); .collect();
data.insert(
let data = HashMap::from([ "ids",
("name", "load_recent_peers".to_owned()), serde_json::ser::to_string(&ids).unwrap_or("[]".to_owned()),
( );
"peers",
serde_json::ser::to_string(&peers).unwrap_or("".to_owned()),
),
]);
return SyncReturn(serde_json::ser::to_string(&data).unwrap_or("".to_owned()));
} }
SyncReturn("".to_string()) return serde_json::ser::to_string(&data).unwrap_or("".to_owned());
}
pub fn main_get_recent_peers(get_all: bool) -> String {
if !config::APP_DIR.read().unwrap().is_empty() {
let vec_id_modified_time_path = PeerConfig::get_vec_id_modified_time_path(&None);
let load_two_times = !get_all
&& vec_id_modified_time_path.len() > PeerConfig::BATCH_LOADING_COUNT
&& cfg!(target_os = "windows");
let load_count = if load_two_times {
PeerConfig::BATCH_LOADING_COUNT
} else {
vec_id_modified_time_path.len()
};
return get_partial_recent_peers(vec_id_modified_time_path, load_count);
}
"".to_string()
} }
pub fn main_load_lan_peers_sync() -> SyncReturn<String> { pub fn main_load_lan_peers_sync() -> SyncReturn<String> {
@@ -1172,11 +1227,11 @@ pub fn main_load_recent_peers_for_ab(filter: String) -> String {
pub fn main_load_fav_peers() { pub fn main_load_fav_peers() {
if !config::APP_DIR.read().unwrap().is_empty() { if !config::APP_DIR.read().unwrap().is_empty() {
let favs = get_fav(); let favs = get_fav();
let mut recent = PeerConfig::peers(None); let mut recent = PeerConfig::peers(Some(favs.clone()));
let mut lan = config::LanPeers::load() let mut lan = config::LanPeers::load()
.peers .peers
.iter() .iter()
.filter(|d| recent.iter().all(|r| r.0 != d.id)) .filter(|d| favs.contains(&d.id) && recent.iter().all(|r| r.0 != d.id))
.map(|d| { .map(|d| {
( (
d.id.clone(), d.id.clone(),
@@ -1195,13 +1250,7 @@ pub fn main_load_fav_peers() {
recent.append(&mut lan); recent.append(&mut lan);
let peers: Vec<HashMap<&str, String>> = recent let peers: Vec<HashMap<&str, String>> = recent
.into_iter() .into_iter()
.filter_map(|(id, _, p)| { .map(|(id, _, p)| peer_to_map(id, p))
if favs.contains(&id) {
Some(peer_to_map(id, p))
} else {
None
}
})
.collect(); .collect();
let data = HashMap::from([ let data = HashMap::from([

View File

@@ -319,6 +319,9 @@ impl InvokeUiSession for SciterHandler {
ConnType::DEFAULT_CONN => { ConnType::DEFAULT_CONN => {
crate::keyboard::client::start_grab_loop(); crate::keyboard::client::start_grab_loop();
} }
// Left empty code from compilation.
// Please replace the code in the PR.
ConnType::VIEW_CAMERA => {}
} }
} }