mirror of
https://github.com/rustdesk/rustdesk.git
synced 2025-05-11 18:36:11 +02:00
refact: optimize, loading recent peers (#10847)
Signed-off-by: fufesou <linlong1266@gmail.com>
This commit is contained in:
parent
2e89a33210
commit
055b351164
8 changed files with 265 additions and 139 deletions
|
@ -6,56 +6,123 @@ import 'package:flutter_hbb/models/peer_model.dart';
|
|||
import 'package:flutter_hbb/common.dart';
|
||||
import 'package:flutter_hbb/common/widgets/peer_card.dart';
|
||||
|
||||
Future<List<Peer>> getAllPeers() async {
|
||||
Map<String, dynamic> recentPeers = jsonDecode(bind.mainLoadRecentPeersSync());
|
||||
Map<String, dynamic> lanPeers = jsonDecode(bind.mainLoadLanPeersSync());
|
||||
Map<String, dynamic> combinedPeers = {};
|
||||
class AllPeersLoader {
|
||||
List<Peer> peers = [];
|
||||
bool hasMoreRecentPeers = false;
|
||||
|
||||
void mergePeers(Map<String, dynamic> peers) {
|
||||
if (peers.containsKey("peers")) {
|
||||
dynamic peerData = peers["peers"];
|
||||
bool isPeersLoading = false;
|
||||
bool _isPartialPeersLoaded = false;
|
||||
bool _isPeersLoaded = false;
|
||||
|
||||
if (peerData is String) {
|
||||
try {
|
||||
peerData = jsonDecode(peerData);
|
||||
} catch (e) {
|
||||
print("Error decoding peers: $e");
|
||||
return;
|
||||
}
|
||||
AllPeersLoader();
|
||||
|
||||
bool get isLoaded => _isPartialPeersLoaded || _isPeersLoaded;
|
||||
|
||||
void reset() {
|
||||
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) {
|
||||
for (var peer in peerData) {
|
||||
if (peer is Map && peer.containsKey("id")) {
|
||||
String id = peer["id"];
|
||||
if (!combinedPeers.containsKey(id)) {
|
||||
combinedPeers[id] = peer;
|
||||
Future<void> _getAllPeers(bool getAllRecentPeers) async {
|
||||
Map<String, dynamic> recentPeers =
|
||||
jsonDecode(await bind.mainGetRecentPeers(getAll: getAllRecentPeers));
|
||||
Map<String, dynamic> lanPeers = jsonDecode(bind.mainLoadLanPeersSync());
|
||||
Map<String, dynamic> combinedPeers = {};
|
||||
|
||||
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(lanPeers);
|
||||
for (var p in gFFI.abModel.allPeers()) {
|
||||
if (!combinedPeers.containsKey(p.id)) {
|
||||
combinedPeers[p.id] = p.toJson();
|
||||
mergePeers(recentPeers);
|
||||
mergePeers(lanPeers);
|
||||
for (var p in gFFI.abModel.allPeers()) {
|
||||
if (!combinedPeers.containsKey(p.id)) {
|
||||
combinedPeers[p.id] = p.toJson();
|
||||
}
|
||||
}
|
||||
}
|
||||
for (var p in gFFI.groupModel.peers.map((e) => Peer.copy(e)).toList()) {
|
||||
if (!combinedPeers.containsKey(p.id)) {
|
||||
combinedPeers[p.id] = p.toJson();
|
||||
for (var p in gFFI.groupModel.peers.map((e) => Peer.copy(e)).toList()) {
|
||||
if (!combinedPeers.containsKey(p.id)) {
|
||||
combinedPeers[p.id] = p.toJson();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<Peer> parsedPeers = [];
|
||||
List<Peer> parsedPeers = [];
|
||||
|
||||
for (var peer in combinedPeers.values) {
|
||||
parsedPeers.add(Peer.fromJson(peer));
|
||||
for (var peer in combinedPeers.values) {
|
||||
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 {
|
||||
|
|
|
@ -200,18 +200,20 @@ class _ConnectionPageState extends State<ConnectionPage>
|
|||
final _idController = IDTextEditingController();
|
||||
|
||||
final RxBool _idInputFocused = false.obs;
|
||||
final FocusNode _idFocusNode = FocusNode();
|
||||
final TextEditingController _idEditingController = TextEditingController();
|
||||
|
||||
bool isWindowMinimized = false;
|
||||
List<Peer> peers = [];
|
||||
|
||||
bool isPeersLoading = false;
|
||||
bool isPeersLoaded = false;
|
||||
AllPeersLoader allPeersLoader = AllPeersLoader();
|
||||
|
||||
// https://github.com/flutter/flutter/issues/157244
|
||||
Iterable<Peer> _autocompleteOpts = [];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_idFocusNode.addListener(onFocusChanged);
|
||||
if (_idController.text.isEmpty) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||
final lastRemoteId = await bind.mainGetLastRemoteId();
|
||||
|
@ -230,6 +232,9 @@ class _ConnectionPageState extends State<ConnectionPage>
|
|||
void dispose() {
|
||||
_idController.dispose();
|
||||
windowManager.removeListener(this);
|
||||
_idFocusNode.removeListener(onFocusChanged);
|
||||
_idFocusNode.dispose();
|
||||
_idEditingController.dispose();
|
||||
if (Get.isRegistered<IDTextEditingController>()) {
|
||||
Get.delete<IDTextEditingController>();
|
||||
}
|
||||
|
@ -273,6 +278,13 @@ class _ConnectionPageState extends State<ConnectionPage>
|
|||
bind.mainOnMainWindowClose();
|
||||
}
|
||||
|
||||
void onFocusChanged() {
|
||||
_idInputFocused.value = _idFocusNode.hasFocus;
|
||||
if (_idFocusNode.hasFocus && !allPeersLoader.isPeersLoading) {
|
||||
allPeersLoader.getAllPeers(setState);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isOutgoingOnly = bind.isOutgoingOnly();
|
||||
|
@ -304,18 +316,6 @@ class _ConnectionPageState extends State<ConnectionPage>
|
|||
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.
|
||||
/// Search for a peer.
|
||||
Widget _buildRemoteIDTextField(BuildContext context) {
|
||||
|
@ -332,11 +332,12 @@ class _ConnectionPageState extends State<ConnectionPage>
|
|||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Autocomplete<Peer>(
|
||||
child: RawAutocomplete<Peer>(
|
||||
optionsBuilder: (TextEditingValue textEditingValue) {
|
||||
if (textEditingValue.text == '') {
|
||||
_autocompleteOpts = const Iterable<Peer>.empty();
|
||||
} else if (peers.isEmpty && !isPeersLoaded) {
|
||||
} else if (allPeersLoader.peers.isEmpty &&
|
||||
!allPeersLoader.isLoaded) {
|
||||
Peer emptyPeer = Peer(
|
||||
id: '',
|
||||
username: '',
|
||||
|
@ -363,7 +364,7 @@ class _ConnectionPageState extends State<ConnectionPage>
|
|||
);
|
||||
}
|
||||
String textToFind = textEditingValue.text.toLowerCase();
|
||||
_autocompleteOpts = peers
|
||||
_autocompleteOpts = allPeersLoader.peers
|
||||
.where((peer) =>
|
||||
peer.id.toLowerCase().contains(textToFind) ||
|
||||
peer.username
|
||||
|
@ -377,6 +378,8 @@ class _ConnectionPageState extends State<ConnectionPage>
|
|||
}
|
||||
return _autocompleteOpts;
|
||||
},
|
||||
focusNode: _idFocusNode,
|
||||
textEditingController: _idEditingController,
|
||||
fieldViewBuilder: (
|
||||
BuildContext context,
|
||||
TextEditingController fieldTextEditingController,
|
||||
|
@ -385,17 +388,17 @@ class _ConnectionPageState extends State<ConnectionPage>
|
|||
) {
|
||||
fieldTextEditingController.text = _idController.text;
|
||||
Get.put<TextEditingController>(fieldTextEditingController);
|
||||
fieldFocusNode.addListener(() async {
|
||||
_idInputFocused.value = fieldFocusNode.hasFocus;
|
||||
if (fieldFocusNode.hasFocus && !isPeersLoading) {
|
||||
_fetchPeers();
|
||||
}
|
||||
});
|
||||
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);
|
||||
|
||||
// The listener will be added multiple times when the widget is rebuilt.
|
||||
// We may need to use the `RawAutocomplete` to get the focus node.
|
||||
|
||||
// Temporarily remove Selection because Selection can cause users to accidentally delete previously entered content during input.
|
||||
// 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 Obx(() => TextField(
|
||||
autocorrect: false,
|
||||
enableSuggestions: false,
|
||||
|
@ -468,7 +471,8 @@ class _ConnectionPageState extends State<ConnectionPage>
|
|||
maxHeight: maxHeight,
|
||||
maxWidth: 319,
|
||||
),
|
||||
child: peers.isEmpty && isPeersLoading
|
||||
child: allPeersLoader.peers.isEmpty &&
|
||||
!allPeersLoader.isLoaded
|
||||
? Container(
|
||||
height: 80,
|
||||
child: Center(
|
||||
|
|
|
@ -41,10 +41,11 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
|||
final _idController = IDTextEditingController();
|
||||
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;
|
||||
|
||||
// https://github.com/flutter/flutter/issues/157244
|
||||
|
@ -61,6 +62,7 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
|||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_idFocusNode.addListener(onFocusChanged);
|
||||
if (_idController.text.isEmpty) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||
final lastRemoteId = await bind.mainGetLastRemoteId();
|
||||
|
@ -99,6 +101,13 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
|||
connect(context, id);
|
||||
}
|
||||
|
||||
void onFocusChanged() {
|
||||
_idEmpty.value = _idEditingController.text.isEmpty;
|
||||
if (_idFocusNode.hasFocus && !allPeersLoader.isPeersLoading) {
|
||||
allPeersLoader.getAllPeers(setState);
|
||||
}
|
||||
}
|
||||
|
||||
/// UI for software update.
|
||||
/// If _updateUrl] is not empty, shows a button to update the software.
|
||||
Widget _buildUpdateUI(String updateUrl) {
|
||||
|
@ -127,18 +136,6 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
|||
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.
|
||||
/// Search for a peer and connect to it if the id exists.
|
||||
Widget _buildRemoteIDTextField() {
|
||||
|
@ -156,11 +153,12 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
|||
Expanded(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.only(left: 16, right: 16),
|
||||
child: Autocomplete<Peer>(
|
||||
child: RawAutocomplete<Peer>(
|
||||
optionsBuilder: (TextEditingValue textEditingValue) {
|
||||
if (textEditingValue.text == '') {
|
||||
_autocompleteOpts = const Iterable<Peer>.empty();
|
||||
} else if (peers.isEmpty && !isPeersLoaded) {
|
||||
} else if (allPeersLoader.peers.isEmpty &&
|
||||
!allPeersLoader.isLoaded) {
|
||||
Peer emptyPeer = Peer(
|
||||
id: '',
|
||||
username: '',
|
||||
|
@ -188,7 +186,7 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
|||
}
|
||||
String textToFind = textEditingValue.text.toLowerCase();
|
||||
|
||||
_autocompleteOpts = peers
|
||||
_autocompleteOpts = allPeersLoader.peers
|
||||
.where((peer) =>
|
||||
peer.id.toLowerCase().contains(textToFind) ||
|
||||
peer.username
|
||||
|
@ -202,6 +200,8 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
|||
}
|
||||
return _autocompleteOpts;
|
||||
},
|
||||
focusNode: _idFocusNode,
|
||||
textEditingController: _idEditingController,
|
||||
fieldViewBuilder: (BuildContext context,
|
||||
TextEditingController fieldTextEditingController,
|
||||
FocusNode fieldFocusNode,
|
||||
|
@ -209,18 +209,14 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
|||
fieldTextEditingController.text = _idController.text;
|
||||
Get.put<TextEditingController>(
|
||||
fieldTextEditingController);
|
||||
fieldFocusNode.addListener(() async {
|
||||
_idEmpty.value =
|
||||
fieldTextEditingController.text.isEmpty;
|
||||
if (fieldFocusNode.hasFocus && !isPeersLoading) {
|
||||
_fetchPeers();
|
||||
}
|
||||
});
|
||||
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);
|
||||
|
||||
// Temporarily remove Selection because Selection can cause users to accidentally delete previously entered content during input.
|
||||
// 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(
|
||||
controller: fieldTextEditingController,
|
||||
focusNode: fieldFocusNode,
|
||||
|
@ -300,7 +296,8 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
|||
maxHeight: maxHeight,
|
||||
maxWidth: 320,
|
||||
),
|
||||
child: peers.isEmpty && isPeersLoading
|
||||
child: allPeersLoader.peers.isEmpty &&
|
||||
!allPeersLoader.isLoaded
|
||||
? Container(
|
||||
height: 80,
|
||||
child: Center(
|
||||
|
@ -363,6 +360,9 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
|||
void dispose() {
|
||||
_uniLinksSubscription?.cancel();
|
||||
_idController.dispose();
|
||||
_idFocusNode.removeListener(onFocusChanged);
|
||||
_idFocusNode.dispose();
|
||||
_idEditingController.dispose();
|
||||
if (Get.isRegistered<IDTextEditingController>()) {
|
||||
Get.delete<IDTextEditingController>();
|
||||
}
|
||||
|
|
|
@ -6,12 +6,13 @@ import 'package:flutter_hbb/models/platform_model.dart';
|
|||
|
||||
void showPeerSelectionDialog(
|
||||
{bool singleSelection = false,
|
||||
required Function(List<String>) onPeersCallback}) {
|
||||
final peers = bind.mainLoadRecentPeersSync();
|
||||
required Function(List<String>) onPeersCallback}) async {
|
||||
final peers = await bind.mainGetRecentPeers(getAll: true);
|
||||
if (peers.isEmpty) {
|
||||
debugPrint("load recent peers sync failed.");
|
||||
debugPrint("load recent peers failed.");
|
||||
return;
|
||||
}
|
||||
|
||||
Map<String, dynamic> map = jsonDecode(peers);
|
||||
List<dynamic> peersList = map['peers'] ?? [];
|
||||
final selected = List<String>.empty(growable: true);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue