From 29c661d919e5ac8ff663bc5178032f946ce9647d Mon Sep 17 00:00:00 2001 From: 21pages Date: Wed, 16 Aug 2023 08:09:03 +0800 Subject: [PATCH 1/4] ab: fix ui not updating immediately Signed-off-by: 21pages --- flutter/lib/models/ab_model.dart | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/flutter/lib/models/ab_model.dart b/flutter/lib/models/ab_model.dart index 3300fa67c..3dd02bb38 100644 --- a/flutter/lib/models/ab_model.dart +++ b/flutter/lib/models/ab_model.dart @@ -204,7 +204,9 @@ class AbModel { try { // avoid double pushes in a row _syncAllFromRecent = true; - syncFromRecent(push: false); + await syncFromRecent(push: false); + //https: //stackoverflow.com/questions/68249333/flutter-getx-updating-item-in-children-list-is-not-reactive + peers.refresh(); final api = "${await bind.mainGetApiServer()}/api/ab"; var authHeaders = getHttpHeaders(); authHeaders['Content-Type'] = "application/json"; @@ -333,15 +335,15 @@ class AbModel { rdpUsername: r.rdpUsername); } - void syncFromRecent({bool push = true}) async { + Future syncFromRecent({bool push = true}) async { if (!_syncFromRecentLock) { _syncFromRecentLock = true; - _syncFromRecentWithoutLock(push: push); + await _syncFromRecentWithoutLock(push: push); _syncFromRecentLock = false; } } - void _syncFromRecentWithoutLock({bool push = true}) async { + Future _syncFromRecentWithoutLock({bool push = true}) async { bool shouldSync(Peer a, Peer b) { return a.hash != b.hash || a.username != b.username || From 553a3798a177bf4cf5b5ece9cf69f1f9d76a3b97 Mon Sep 17 00:00:00 2001 From: 21pages Date: Wed, 16 Aug 2023 08:59:50 +0800 Subject: [PATCH 2/4] ab: sync all recent peers if option enabled Signed-off-by: 21pages --- flutter/lib/common/widgets/peer_card.dart | 7 + flutter/lib/common/widgets/peer_tab_page.dart | 140 ++++++++++-------- flutter/lib/models/ab_model.dart | 63 ++++---- flutter/lib/models/peer_model.dart | 14 ++ libs/hbb_common/src/config.rs | 4 + src/flutter_ffi.rs | 11 +- src/lang/ca.rs | 1 + src/lang/cn.rs | 1 + src/lang/cs.rs | 1 + src/lang/da.rs | 1 + src/lang/de.rs | 1 + src/lang/el.rs | 1 + src/lang/en.rs | 1 + src/lang/eo.rs | 1 + src/lang/es.rs | 1 + src/lang/fa.rs | 1 + src/lang/fr.rs | 1 + src/lang/hu.rs | 1 + src/lang/id.rs | 1 + src/lang/it.rs | 1 + src/lang/ja.rs | 1 + src/lang/ko.rs | 1 + src/lang/kz.rs | 1 + src/lang/lt.rs | 1 + src/lang/nl.rs | 1 + src/lang/pl.rs | 1 + src/lang/pt_PT.rs | 1 + src/lang/ptbr.rs | 1 + src/lang/ro.rs | 1 + src/lang/ru.rs | 1 + src/lang/sk.rs | 1 + src/lang/sl.rs | 1 + src/lang/sq.rs | 1 + src/lang/sr.rs | 1 + src/lang/sv.rs | 1 + src/lang/template.rs | 1 + src/lang/th.rs | 1 + src/lang/tr.rs | 1 + src/lang/tw.rs | 1 + src/lang/ua.rs | 1 + src/lang/vn.rs | 1 + src/ui_interface.rs | 5 + 42 files changed, 183 insertions(+), 96 deletions(-) diff --git a/flutter/lib/common/widgets/peer_card.dart b/flutter/lib/common/widgets/peer_card.dart index 88d44cc7e..f1ab7b1f0 100644 --- a/flutter/lib/common/widgets/peer_card.dart +++ b/flutter/lib/common/widgets/peer_card.dart @@ -1,9 +1,11 @@ import 'dart:io'; +import 'package:bot_toast/bot_toast.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_hbb/common/widgets/dialog.dart'; import 'package:flutter_hbb/consts.dart'; +import 'package:flutter_hbb/models/ab_model.dart'; import 'package:flutter_hbb/models/peer_tab_model.dart'; import 'package:get/get.dart'; import 'package:provider/provider.dart'; @@ -707,6 +709,11 @@ abstract class BasePeerCard extends StatelessWidget { case PeerTabIndex.ab: gFFI.abModel.deletePeer(id); await gFFI.abModel.pushAb(); + if (shouldSyncAb() && await bind.mainPeerExists(id: peer.id)) { + BotToast.showText( + contentColor: Colors.lightBlue, + text: translate('synced_peer_readded_tip')); + } break; case PeerTabIndex.group: break; diff --git a/flutter/lib/common/widgets/peer_tab_page.dart b/flutter/lib/common/widgets/peer_tab_page.dart index 71f620f6b..54853c568 100644 --- a/flutter/lib/common/widgets/peer_tab_page.dart +++ b/flutter/lib/common/widgets/peer_tab_page.dart @@ -1,3 +1,4 @@ +import 'package:bot_toast/bot_toast.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hbb/common/widgets/address_book.dart'; import 'package:flutter_hbb/common/widgets/dialog.dart'; @@ -7,6 +8,7 @@ import 'package:flutter_hbb/common/widgets/peer_card.dart'; import 'package:flutter_hbb/common/widgets/animated_rotation_widget.dart'; import 'package:flutter_hbb/consts.dart'; import 'package:flutter_hbb/desktop/widgets/popup_menu.dart'; +import 'package:flutter_hbb/models/ab_model.dart'; import 'package:flutter_hbb/models/peer_tab_model.dart'; import 'package:get/get.dart'; @@ -122,12 +124,11 @@ class _PeerTabPageState extends State color: Theme.of(context).colorScheme.background, borderRadius: BorderRadius.circular(6)), child: Tooltip( - message: translate('Toggle Tags'), - child: Icon( - Icons.tag_rounded, - size: 18, - )) - )), + message: translate('Toggle Tags'), + child: Icon( + Icons.tag_rounded, + size: 18, + )))), onTap: () async { await bind.mainSetLocalOption( key: "hideAbTagsPanel", @@ -221,13 +222,12 @@ class _PeerTabPageState extends State child: RotatedBox( quarterTurns: 2, child: Tooltip( - message: translate('Refresh'), - child: Icon( - Icons.refresh, - size: 18, - color: textColor, - )) - )), + message: translate('Refresh'), + child: Icon( + Icons.refresh, + size: 18, + color: textColor, + )))), ), ); } @@ -243,29 +243,28 @@ class _PeerTabPageState extends State return Obx( () => Container( - padding: EdgeInsets.all(4.0), - decoration: hover.value ? deco : null, - child: InkWell( - onHover: (value) => hover.value = value, - onTap: () async { - final type = types.elementAt( - peerCardUiType.value == types.elementAt(0) ? 1 : 0); - await bind.setLocalFlutterOption( - k: 'peer-card-ui-type', v: type.index.toString()); - peerCardUiType.value = type; - }, - child: Tooltip( - message: peerCardUiType.value == PeerUiType.grid - ? translate('List View') - : translate('Grid View'), - child: Icon( - peerCardUiType.value == PeerUiType.grid - ? Icons.view_list_rounded - : Icons.grid_view_rounded, - size: 18, - color: textColor, - )) - )), + padding: EdgeInsets.all(4.0), + decoration: hover.value ? deco : null, + child: InkWell( + onHover: (value) => hover.value = value, + onTap: () async { + final type = types.elementAt( + peerCardUiType.value == types.elementAt(0) ? 1 : 0); + await bind.setLocalFlutterOption( + k: 'peer-card-ui-type', v: type.index.toString()); + peerCardUiType.value = type; + }, + child: Tooltip( + message: peerCardUiType.value == PeerUiType.grid + ? translate('List View') + : translate('Grid View'), + child: Icon( + peerCardUiType.value == PeerUiType.grid + ? Icons.view_list_rounded + : Icons.grid_view_rounded, + size: 18, + color: textColor, + )))), ); } @@ -280,12 +279,12 @@ class _PeerTabPageState extends State model.setMultiSelectionMode(true); }, child: Tooltip( - message: translate('Select'), - child: Icon( - IconFont.checkbox, - size: 18, - color: textColor, - )), + message: translate('Select'), + child: Icon( + IconFont.checkbox, + size: 18, + color: textColor, + )), ), ); } @@ -334,8 +333,25 @@ class _PeerTabPageState extends State await bind.mainLoadLanPeers(); break; case 3: - gFFI.abModel.deletePeers(peers.map((p) => p.id).toList()); - await gFFI.abModel.pushAb(); + { + bool hasSynced = false; + if (shouldSyncAb()) { + for (var p in peers) { + if (await bind.mainPeerExists(id: p.id)) { + hasSynced = true; + } + } + } + gFFI.abModel.deletePeers(peers.map((p) => p.id).toList()); + await gFFI.abModel.pushAb(); + if (hasSynced) { + Future.delayed(Duration(seconds: 2), () { + BotToast.showText( + contentColor: Colors.lightBlue, + text: translate('synced_peer_readded_tip')); + }); + } + } break; default: break; @@ -483,8 +499,7 @@ class _PeerSearchBarState extends State { child: Icon( Icons.search_rounded, color: Theme.of(context).hintColor, - )) - ); + ))); } Widget _buildSearchBar() { @@ -543,22 +558,21 @@ class _PeerSearchBarState extends State { ), // Icon(Icons.close), IconButton( - alignment: Alignment.centerRight, - padding: const EdgeInsets.only(right: 2), - onPressed: () { - setState(() { - peerSearchTextController.clear(); - peerSearchText.value = ""; - drawer = false; - }); - }, - icon: Tooltip( - message: translate('Close'), - child: - Icon( - Icons.close, - color: Theme.of(context).hintColor, - )), + alignment: Alignment.centerRight, + padding: const EdgeInsets.only(right: 2), + onPressed: () { + setState(() { + peerSearchTextController.clear(); + peerSearchText.value = ""; + drawer = false; + }); + }, + icon: Tooltip( + message: translate('Close'), + child: Icon( + Icons.close, + color: Theme.of(context).hintColor, + )), ), ], ), @@ -628,7 +642,7 @@ class _PeerSortDropdownState extends State { child: Icon( Icons.sort_rounded, size: 18, - )), + )), onTapDown: (details) { final x = details.globalPosition.dx; final y = details.globalPosition.dy; diff --git a/flutter/lib/models/ab_model.dart b/flutter/lib/models/ab_model.dart index 3dd02bb38..cb4242a6a 100644 --- a/flutter/lib/models/ab_model.dart +++ b/flutter/lib/models/ab_model.dart @@ -344,33 +344,28 @@ class AbModel { } Future _syncFromRecentWithoutLock({bool push = true}) async { - bool shouldSync(Peer a, Peer b) { - return a.hash != b.hash || - a.username != b.username || - a.platform != b.platform || - a.hostname != b.hostname || - a.alias != b.alias; + bool shouldSync(Peer r, Peer p) { + return r.hash != p.hash || + r.username != p.username || + r.platform != p.platform || + r.hostname != p.hostname || + (p.alias.isEmpty && r.alias.isNotEmpty); } Future> getRecentPeers() async { try { - if (peers.isEmpty) []; List filteredPeerIDs; if (_syncAllFromRecent) { _syncAllFromRecent = false; - filteredPeerIDs = peers.map((e) => e.id).toList(); + filteredPeerIDs = []; } else { final new_stored_str = await bind.mainGetNewStoredPeers(); if (new_stored_str.isEmpty) return []; - List new_stores = - (jsonDecode(new_stored_str) as List) - .map((e) => e.toString()) - .toList(); - final abPeerIds = peers.map((e) => e.id).toList(); - filteredPeerIDs = - new_stores.where((e) => abPeerIds.contains(e)).toList(); + filteredPeerIDs = (jsonDecode(new_stored_str) as List) + .map((e) => e.toString()) + .toList(); + if (filteredPeerIDs.isEmpty) return []; } - if (filteredPeerIDs.isEmpty) return []; final loadStr = await bind.mainLoadRecentPeersForAb( filter: jsonEncode(filteredPeerIDs)); if (loadStr.isEmpty) { @@ -392,28 +387,32 @@ class AbModel { try { if (!shouldSyncAb()) return; - final oldPeers = peers.toList(); final recents = await getRecentPeers(); if (recents.isEmpty) return; - for (var i = 0; i < peers.length; i++) { - var p = peers[i]; - var r = recents.firstWhereOrNull((r) => p.id == r.id); - if (r != null) { - peers[i] = merge(r, p); - } - } - bool changed = false; - for (var i = 0; i < peers.length; i++) { - final o = oldPeers[i]; - final p = peers[i]; - if (shouldSync(o, p)) { - changed = true; - break; + bool syncChanged = false; + bool uiChanged = false; + for (var i = 0; i < recents.length; i++) { + var r = recents[i]; + var index = peers.indexWhere((e) => e.id == r.id); + if (index < 0) { + peers.add(r); + syncChanged = true; + uiChanged = true; + } else { + if (!r.equal(peers[index])) { + uiChanged = true; + } + if (shouldSync(r, peers[index])) { + syncChanged = true; + } + peers[index] = merge(r, peers[index]); } } // Be careful with loop calls - if (changed && push) { + if (syncChanged && push) { pushAb(); + } else if (uiChanged) { + peers.refresh(); } } catch (e) { debugPrint('syncFromRecent:$e'); diff --git a/flutter/lib/models/peer_model.dart b/flutter/lib/models/peer_model.dart index 98810b09e..ddd9f8538 100644 --- a/flutter/lib/models/peer_model.dart +++ b/flutter/lib/models/peer_model.dart @@ -1,6 +1,8 @@ import 'dart:convert'; import 'package:flutter/foundation.dart'; import 'platform_model.dart'; +// ignore: depend_on_referenced_packages +import 'package:collection/collection.dart'; class Peer { final String id; @@ -87,6 +89,18 @@ class Peer { rdpPort: '', rdpUsername: '', ); + bool equal(Peer other) { + return id == other.id && + hash == other.hash && + username == other.username && + hostname == other.hostname && + platform == other.platform && + alias == other.alias && + tags.equals(other.tags) && + forceAlwaysRelay == other.forceAlwaysRelay && + rdpPort == other.rdpPort && + rdpUsername == other.rdpUsername; + } } enum UpdateEvent { online, load } diff --git a/libs/hbb_common/src/config.rs b/libs/hbb_common/src/config.rs index b0ec5602f..2cb0072c5 100644 --- a/libs/hbb_common/src/config.rs +++ b/libs/hbb_common/src/config.rs @@ -1079,6 +1079,10 @@ impl PeerConfig { Default::default() } + pub fn exists(id: &str) -> bool { + Self::path(id).exists() + } + serde_field_string!( default_view_style, deserialize_view_style, diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index ab7acc104..f076b4b23 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -841,6 +841,10 @@ pub fn main_peer_has_password(id: String) -> bool { peer_has_password(id) } +pub fn main_peer_exists(id: String) -> bool { + peer_exists(&id) +} + pub fn main_load_recent_peers() { if !config::APP_DIR.read().unwrap().is_empty() { let peers: Vec> = PeerConfig::peers(None) @@ -883,8 +887,13 @@ pub fn main_load_recent_peers_sync() -> SyncReturn { pub fn main_load_recent_peers_for_ab(filter: String) -> String { let id_filters = serde_json::from_str::>(&filter).unwrap_or_default(); + let id_filters = if id_filters.is_empty() { + None + } else { + Some(id_filters) + }; if !config::APP_DIR.read().unwrap().is_empty() { - let peers: Vec> = PeerConfig::peers(Some(id_filters)) + let peers: Vec> = PeerConfig::peers(id_filters) .drain(..) .map(|(id, _, p)| peer_to_map_ab(id, p)) .collect(); diff --git a/src/lang/ca.rs b/src/lang/ca.rs index ef0cd3b86..739d5646b 100644 --- a/src/lang/ca.rs +++ b/src/lang/ca.rs @@ -537,5 +537,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Toggle Tags", ""), ("pull_ab_failed_tip", ""), ("push_ab_failed_tip", ""), + ("synced_peer_readded_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/cn.rs b/src/lang/cn.rs index 88638e0d6..50756df7a 100644 --- a/src/lang/cn.rs +++ b/src/lang/cn.rs @@ -537,5 +537,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Toggle Tags", ""), ("pull_ab_failed_tip", "未成功获取地址簿"), ("push_ab_failed_tip", "未成功上传地址簿"), + ("synced_peer_readded_tip", "最近会话中存在的设备将会被重新添加到地址簿。"), ].iter().cloned().collect(); } diff --git a/src/lang/cs.rs b/src/lang/cs.rs index 69df1b70f..e2028629a 100644 --- a/src/lang/cs.rs +++ b/src/lang/cs.rs @@ -537,5 +537,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Toggle Tags", ""), ("pull_ab_failed_tip", ""), ("push_ab_failed_tip", ""), + ("synced_peer_readded_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/da.rs b/src/lang/da.rs index cf1ad5748..830cb0c45 100644 --- a/src/lang/da.rs +++ b/src/lang/da.rs @@ -537,5 +537,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Toggle Tags", ""), ("pull_ab_failed_tip", ""), ("push_ab_failed_tip", ""), + ("synced_peer_readded_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/de.rs b/src/lang/de.rs index 7ebd725a2..c62309cbf 100644 --- a/src/lang/de.rs +++ b/src/lang/de.rs @@ -537,5 +537,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Toggle Tags", "Tags umschalten"), ("pull_ab_failed_tip", "Aktualisierung des Adressbuchs fehlgeschlagen"), ("push_ab_failed_tip", "Synchronisierung des Adressbuchs mit dem Server fehlgeschlagen"), + ("synced_peer_readded_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/el.rs b/src/lang/el.rs index 2795a0e90..46f816677 100644 --- a/src/lang/el.rs +++ b/src/lang/el.rs @@ -537,5 +537,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Toggle Tags", ""), ("pull_ab_failed_tip", ""), ("push_ab_failed_tip", ""), + ("synced_peer_readded_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/en.rs b/src/lang/en.rs index 695ff923d..dc3dcb605 100644 --- a/src/lang/en.rs +++ b/src/lang/en.rs @@ -76,5 +76,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("exceed_max_devices", "You have reached the maximum number of managed devices."), ("pull_ab_failed_tip", "Failed to refresh address book"), ("push_ab_failed_tip", "Failed to sync address book to server"), + ("synced_peer_readded_tip", "The devices present in the recent sessions will be re-added to the address book."), ].iter().cloned().collect(); } diff --git a/src/lang/eo.rs b/src/lang/eo.rs index 3ac8ea79b..8d7f1c873 100644 --- a/src/lang/eo.rs +++ b/src/lang/eo.rs @@ -537,5 +537,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Toggle Tags", ""), ("pull_ab_failed_tip", ""), ("push_ab_failed_tip", ""), + ("synced_peer_readded_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/es.rs b/src/lang/es.rs index c10f9de16..8fbea74ef 100644 --- a/src/lang/es.rs +++ b/src/lang/es.rs @@ -537,5 +537,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Toggle Tags", "Alternar Etiquetas"), ("pull_ab_failed_tip", ""), ("push_ab_failed_tip", ""), + ("synced_peer_readded_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/fa.rs b/src/lang/fa.rs index 389676bac..eeb36af3b 100644 --- a/src/lang/fa.rs +++ b/src/lang/fa.rs @@ -537,5 +537,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Toggle Tags", ""), ("pull_ab_failed_tip", ""), ("push_ab_failed_tip", ""), + ("synced_peer_readded_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/fr.rs b/src/lang/fr.rs index b1aa90f76..18e1b1ffb 100644 --- a/src/lang/fr.rs +++ b/src/lang/fr.rs @@ -537,5 +537,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Toggle Tags", ""), ("pull_ab_failed_tip", ""), ("push_ab_failed_tip", ""), + ("synced_peer_readded_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/hu.rs b/src/lang/hu.rs index 4e5d34c08..2da6bd72e 100644 --- a/src/lang/hu.rs +++ b/src/lang/hu.rs @@ -537,5 +537,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Toggle Tags", ""), ("pull_ab_failed_tip", ""), ("push_ab_failed_tip", ""), + ("synced_peer_readded_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/id.rs b/src/lang/id.rs index 76fad9ad2..4692b280b 100644 --- a/src/lang/id.rs +++ b/src/lang/id.rs @@ -537,5 +537,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Toggle Tags", ""), ("pull_ab_failed_tip", ""), ("push_ab_failed_tip", ""), + ("synced_peer_readded_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/it.rs b/src/lang/it.rs index 5b6b296b1..f3ec2d941 100644 --- a/src/lang/it.rs +++ b/src/lang/it.rs @@ -537,5 +537,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Toggle Tags", "Attiva/disattiva tag"), ("pull_ab_failed_tip", "Impossibile aggiornare la rubrica"), ("push_ab_failed_tip", "Impossibile sincronizzare la rubrica con il server"), + ("synced_peer_readded_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ja.rs b/src/lang/ja.rs index 9e2bc66df..4c4d6e141 100644 --- a/src/lang/ja.rs +++ b/src/lang/ja.rs @@ -537,5 +537,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Toggle Tags", ""), ("pull_ab_failed_tip", ""), ("push_ab_failed_tip", ""), + ("synced_peer_readded_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ko.rs b/src/lang/ko.rs index 18ee01abd..91e49be3a 100644 --- a/src/lang/ko.rs +++ b/src/lang/ko.rs @@ -537,5 +537,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Toggle Tags", ""), ("pull_ab_failed_tip", ""), ("push_ab_failed_tip", ""), + ("synced_peer_readded_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/kz.rs b/src/lang/kz.rs index 252880ebb..807b74ba4 100644 --- a/src/lang/kz.rs +++ b/src/lang/kz.rs @@ -537,5 +537,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Toggle Tags", ""), ("pull_ab_failed_tip", ""), ("push_ab_failed_tip", ""), + ("synced_peer_readded_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/lt.rs b/src/lang/lt.rs index 6ad41ee7a..0cd258b9e 100644 --- a/src/lang/lt.rs +++ b/src/lang/lt.rs @@ -537,5 +537,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Toggle Tags", ""), ("pull_ab_failed_tip", ""), ("push_ab_failed_tip", ""), + ("synced_peer_readded_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/nl.rs b/src/lang/nl.rs index ef04ed01a..c37e95c89 100644 --- a/src/lang/nl.rs +++ b/src/lang/nl.rs @@ -537,5 +537,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Toggle Tags", ""), ("pull_ab_failed_tip", ""), ("push_ab_failed_tip", ""), + ("synced_peer_readded_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/pl.rs b/src/lang/pl.rs index dc45c4ea9..923dadc22 100644 --- a/src/lang/pl.rs +++ b/src/lang/pl.rs @@ -537,5 +537,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Toggle Tags", ""), ("pull_ab_failed_tip", ""), ("push_ab_failed_tip", ""), + ("synced_peer_readded_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/pt_PT.rs b/src/lang/pt_PT.rs index a3fc256ad..3eb850227 100644 --- a/src/lang/pt_PT.rs +++ b/src/lang/pt_PT.rs @@ -537,5 +537,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Toggle Tags", ""), ("pull_ab_failed_tip", ""), ("push_ab_failed_tip", ""), + ("synced_peer_readded_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ptbr.rs b/src/lang/ptbr.rs index 201ae1138..40360be5a 100644 --- a/src/lang/ptbr.rs +++ b/src/lang/ptbr.rs @@ -537,5 +537,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Toggle Tags", ""), ("pull_ab_failed_tip", ""), ("push_ab_failed_tip", ""), + ("synced_peer_readded_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ro.rs b/src/lang/ro.rs index 54bc2db4c..50ae39902 100644 --- a/src/lang/ro.rs +++ b/src/lang/ro.rs @@ -537,5 +537,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Toggle Tags", ""), ("pull_ab_failed_tip", ""), ("push_ab_failed_tip", ""), + ("synced_peer_readded_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ru.rs b/src/lang/ru.rs index cde7f611e..dec93c575 100644 --- a/src/lang/ru.rs +++ b/src/lang/ru.rs @@ -537,5 +537,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Toggle Tags", "Переключить метки"), ("pull_ab_failed_tip", "Невозможно обновить адресную книгу"), ("push_ab_failed_tip", "Невозможно синхронизировать адресную книгу с сервером"), + ("synced_peer_readded_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sk.rs b/src/lang/sk.rs index 2d8db2678..4b7ced046 100644 --- a/src/lang/sk.rs +++ b/src/lang/sk.rs @@ -537,5 +537,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Toggle Tags", ""), ("pull_ab_failed_tip", ""), ("push_ab_failed_tip", ""), + ("synced_peer_readded_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sl.rs b/src/lang/sl.rs index e16bcfc18..6b356a238 100755 --- a/src/lang/sl.rs +++ b/src/lang/sl.rs @@ -537,5 +537,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Toggle Tags", ""), ("pull_ab_failed_tip", ""), ("push_ab_failed_tip", ""), + ("synced_peer_readded_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sq.rs b/src/lang/sq.rs index ea7a7da5c..5eb5c0fb4 100644 --- a/src/lang/sq.rs +++ b/src/lang/sq.rs @@ -537,5 +537,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Toggle Tags", ""), ("pull_ab_failed_tip", ""), ("push_ab_failed_tip", ""), + ("synced_peer_readded_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sr.rs b/src/lang/sr.rs index 388618bfa..f642bc124 100644 --- a/src/lang/sr.rs +++ b/src/lang/sr.rs @@ -537,5 +537,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Toggle Tags", ""), ("pull_ab_failed_tip", ""), ("push_ab_failed_tip", ""), + ("synced_peer_readded_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sv.rs b/src/lang/sv.rs index a61f4ded9..76ea47cd4 100644 --- a/src/lang/sv.rs +++ b/src/lang/sv.rs @@ -537,5 +537,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Toggle Tags", ""), ("pull_ab_failed_tip", ""), ("push_ab_failed_tip", ""), + ("synced_peer_readded_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/template.rs b/src/lang/template.rs index 4605779c3..06591d147 100644 --- a/src/lang/template.rs +++ b/src/lang/template.rs @@ -537,5 +537,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Toggle Tags", ""), ("pull_ab_failed_tip", ""), ("push_ab_failed_tip", ""), + ("synced_peer_readded_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/th.rs b/src/lang/th.rs index 4b7262021..6e21420fa 100644 --- a/src/lang/th.rs +++ b/src/lang/th.rs @@ -537,5 +537,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Toggle Tags", ""), ("pull_ab_failed_tip", ""), ("push_ab_failed_tip", ""), + ("synced_peer_readded_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/tr.rs b/src/lang/tr.rs index df39aa99b..15aaac5f7 100644 --- a/src/lang/tr.rs +++ b/src/lang/tr.rs @@ -537,5 +537,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Toggle Tags", ""), ("pull_ab_failed_tip", ""), ("push_ab_failed_tip", ""), + ("synced_peer_readded_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/tw.rs b/src/lang/tw.rs index 608a01b4e..837024f2e 100644 --- a/src/lang/tw.rs +++ b/src/lang/tw.rs @@ -537,5 +537,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Toggle Tags", ""), ("pull_ab_failed_tip", "未成功獲取地址簿"), ("push_ab_failed_tip", "未成功上傳地址簿"), + ("synced_peer_readded_tip", "最近會話中存在的設備將會被重新添加到地址簿。"), ].iter().cloned().collect(); } diff --git a/src/lang/ua.rs b/src/lang/ua.rs index edd98640c..acdf8f106 100644 --- a/src/lang/ua.rs +++ b/src/lang/ua.rs @@ -537,5 +537,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Toggle Tags", ""), ("pull_ab_failed_tip", ""), ("push_ab_failed_tip", ""), + ("synced_peer_readded_tip", ""), ].iter().cloned().collect(); } diff --git a/src/lang/vn.rs b/src/lang/vn.rs index 140585e31..8aeafd930 100644 --- a/src/lang/vn.rs +++ b/src/lang/vn.rs @@ -537,5 +537,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Toggle Tags", ""), ("pull_ab_failed_tip", ""), ("push_ab_failed_tip", ""), + ("synced_peer_readded_tip", ""), ].iter().cloned().collect(); } diff --git a/src/ui_interface.rs b/src/ui_interface.rs index f6791de0b..1589a4e2b 100644 --- a/src/ui_interface.rs +++ b/src/ui_interface.rs @@ -648,6 +648,11 @@ pub fn peer_to_map_ab(id: String, p: PeerConfig) -> HashMap<&'static str, String m } +#[cfg(feature = "flutter")] +pub fn peer_exists(id: &str) -> bool { + PeerConfig::exists(id) +} + #[inline] pub fn get_lan_peers() -> Vec> { config::LanPeers::load() From 1e75b172d6e1302254f4e5d32adb0b67d58382b2 Mon Sep 17 00:00:00 2001 From: 21pages Date: Wed, 16 Aug 2023 10:18:29 +0800 Subject: [PATCH 3/4] ab: opt reaction and message hint Signed-off-by: 21pages --- flutter/lib/common/widgets/address_book.dart | 14 ++++- flutter/lib/common/widgets/peer_card.dart | 29 ++++++++-- flutter/lib/common/widgets/peer_tab_page.dart | 18 +++--- flutter/lib/models/ab_model.dart | 56 +++++++++++++------ 4 files changed, 85 insertions(+), 32 deletions(-) diff --git a/flutter/lib/common/widgets/address_book.dart b/flutter/lib/common/widgets/address_book.dart index 9530e851d..3c192c166 100644 --- a/flutter/lib/common/widgets/address_book.dart +++ b/flutter/lib/common/widgets/address_book.dart @@ -48,13 +48,14 @@ class _AddressBookState extends State { return Column( children: [ _buildNotEmptyLoading(), + _buildRetryProgress(), _buildErrorBanner( err: gFFI.abModel.pullError, retry: null, close: () => gFFI.abModel.pullError.value = ''), _buildErrorBanner( err: gFFI.abModel.pushError, - retry: () => gFFI.abModel.pushAb(), + retry: () => gFFI.abModel.pushAb(isRetry: true), close: () => gFFI.abModel.pushError.value = ''), Expanded( child: isDesktop @@ -136,6 +137,13 @@ class _AddressBookState extends State { )); } + Widget _buildRetryProgress() { + return Obx(() => Offstage( + offstage: !gFFI.abModel.retrying.value, + child: LinearProgressIndicator(), + )); + } + Widget _buildAddressBookDesktop() { return Row( children: [ @@ -339,7 +347,7 @@ class _AddressBookState extends State { return; } gFFI.abModel.addId(id, aliasController.text.trim(), selectedTag); - await gFFI.abModel.pushAb(); + gFFI.abModel.pushAb(); this.setState(() {}); // final currentPeers } @@ -448,7 +456,7 @@ class _AddressBookState extends State { for (final tag in tags) { gFFI.abModel.addTag(tag); } - await gFFI.abModel.pushAb(); + gFFI.abModel.pushAb(); // final currentPeers } close(); diff --git a/flutter/lib/common/widgets/peer_card.dart b/flutter/lib/common/widgets/peer_card.dart index f1ab7b1f0..27b485023 100644 --- a/flutter/lib/common/widgets/peer_card.dart +++ b/flutter/lib/common/widgets/peer_card.dart @@ -584,6 +584,7 @@ abstract class BasePeerCard extends StatelessWidget { ), proc: () { bind.mainCreateShortcut(id: id); + showToast(translate('Successful')); }, padding: menuPadding, dismissOnClicked: true, @@ -599,6 +600,7 @@ abstract class BasePeerCard extends StatelessWidget { setter: (bool v) async { await bind.mainSetPeerOption( id: id, key: key, value: bool2option(key, v)); + showToast(translate('Successful')); }, padding: menuPadding, dismissOnClicked: true, @@ -635,6 +637,7 @@ abstract class BasePeerCard extends StatelessWidget { id: id, key: kOptionForceAlwaysRelay, value: bool2option(kOptionForceAlwaysRelay, v)); + showToast(translate('Successful')); }, padding: menuPadding, dismissOnClicked: true, @@ -659,6 +662,7 @@ abstract class BasePeerCard extends StatelessWidget { gFFI.abModel.pushAb(); } else { await bind.mainSetPeerAlias(id: id, alias: newName); + showToast(translate('Successful')); _update(); } } @@ -708,16 +712,25 @@ abstract class BasePeerCard extends StatelessWidget { break; case PeerTabIndex.ab: gFFI.abModel.deletePeer(id); - await gFFI.abModel.pushAb(); + final future = gFFI.abModel.pushAb(); if (shouldSyncAb() && await bind.mainPeerExists(id: peer.id)) { - BotToast.showText( - contentColor: Colors.lightBlue, - text: translate('synced_peer_readded_tip')); + Future.delayed(Duration.zero, () async { + final succ = await future; + if (succ) { + await Future.delayed(Duration(seconds: 2)); // success msg + BotToast.showText( + contentColor: Colors.lightBlue, + text: translate('synced_peer_readded_tip')); + } + }); } break; case PeerTabIndex.group: break; } + if (tab != PeerTabIndex.ab) { + showToast(translate('Successful')); + } } deletePeerConfirmDialog(onSubmit, @@ -737,6 +750,7 @@ abstract class BasePeerCard extends StatelessWidget { ), proc: () { bind.mainForgetPassword(id: id); + showToast(translate('Successful')); }, padding: menuPadding, dismissOnClicked: true, @@ -769,6 +783,7 @@ abstract class BasePeerCard extends StatelessWidget { favs.add(id); await bind.mainStoreFav(favs: favs); } + showToast(translate('Successful')); }(); }, padding: menuPadding, @@ -803,6 +818,7 @@ abstract class BasePeerCard extends StatelessWidget { await bind.mainStoreFav(favs: favs); await reloadFunc(); } + showToast(translate('Successful')); }(); }, padding: menuPadding, @@ -824,7 +840,7 @@ abstract class BasePeerCard extends StatelessWidget { } if (!gFFI.abModel.idContainBy(peer.id)) { gFFI.abModel.addPeer(peer); - await gFFI.abModel.pushAb(); + gFFI.abModel.pushAb(); } }(); }, @@ -1056,7 +1072,7 @@ class AddressBookPeerCard extends BasePeerCard { proc: () { editAbTagDialog(gFFI.abModel.getPeerTags(id), (selectedTag) async { gFFI.abModel.changeTagForPeer(id, selectedTag); - await gFFI.abModel.pushAb(); + gFFI.abModel.pushAb(); }); }, padding: super.menuPadding, @@ -1128,6 +1144,7 @@ void _rdpDialog(String id) async { id: id, key: 'rdp_username', value: username); await bind.mainSetPeerOption( id: id, key: 'rdp_password', value: password); + showToast(translate('Successful')); close(); } diff --git a/flutter/lib/common/widgets/peer_tab_page.dart b/flutter/lib/common/widgets/peer_tab_page.dart index 54853c568..0937f6f5f 100644 --- a/flutter/lib/common/widgets/peer_tab_page.dart +++ b/flutter/lib/common/widgets/peer_tab_page.dart @@ -343,12 +343,17 @@ class _PeerTabPageState extends State } } gFFI.abModel.deletePeers(peers.map((p) => p.id).toList()); - await gFFI.abModel.pushAb(); + final future = gFFI.abModel.pushAb(); if (hasSynced) { - Future.delayed(Duration(seconds: 2), () { - BotToast.showText( - contentColor: Colors.lightBlue, - text: translate('synced_peer_readded_tip')); + Future.delayed(Duration.zero, () async { + final succ = await future; + if (succ) { + await Future.delayed( + Duration(seconds: 2)); // success msg + BotToast.showText( + contentColor: Colors.lightBlue, + text: translate('synced_peer_readded_tip')); + } }); } } @@ -357,7 +362,7 @@ class _PeerTabPageState extends State break; } gFFI.peerTabModel.setMultiSelectionMode(false); - showToast(translate('Successful')); + if (model.currentTab != 3) showToast(translate('Successful')); } deletePeerConfirmDialog(onSubmit, translate('Delete')); @@ -404,7 +409,6 @@ class _PeerTabPageState extends State gFFI.abModel.addPeers(peers); gFFI.abModel.pushAb(); model.setMultiSelectionMode(false); - showToast(translate('Successful')); }, child: Tooltip( message: translate('Add to Address Book'), diff --git a/flutter/lib/models/ab_model.dart b/flutter/lib/models/ab_model.dart index cb4242a6a..9426b0571 100644 --- a/flutter/lib/models/ab_model.dart +++ b/flutter/lib/models/ab_model.dart @@ -30,6 +30,7 @@ class AbModel { final tags = [].obs; final peers = List.empty(growable: true).obs; final sortTags = shouldSortTags().obs; + final retrying = false.obs; bool get emtpy => peers.isEmpty && tags.isEmpty; final selectedTags = List.empty(growable: true).obs; @@ -55,10 +56,11 @@ class AbModel { if (gFFI.userModel.userName.isEmpty) return; if (abLoading.value) return; if (!force && initialized) return; + DateTime startTime = DateTime.now(); if (pushError.isNotEmpty) { try { // push to retry - pushAb(toast: false); + pushAb(toastIfFail: false, toastIfSucc: false); } catch (_) {} } if (!quiet) { @@ -112,14 +114,14 @@ class AbModel { } } } finally { - if (initialized) { - // make loading effect obvious - Future.delayed(Duration(milliseconds: 300), () { - abLoading.value = false; - }); - } else { + var ms = + (Duration(milliseconds: 300) - DateTime.now().difference(startTime)) + .inMilliseconds; + ms = ms > 0 ? ms : 0; + Future.delayed(Duration(milliseconds: ms), () { abLoading.value = false; - } + }); + initialized = true; _syncAllFromRecent = true; _timerCounter = 0; @@ -198,9 +200,16 @@ class AbModel { it.first.alias = alias; } - Future pushAb({bool toast = true}) async { - debugPrint("pushAb"); + Future pushAb( + {bool toastIfFail = true, + bool toastIfSucc = true, + bool isRetry = false}) async { + debugPrint( + "pushAb: toastIfFail:$toastIfFail, toastIfSucc:$toastIfSucc, isRetry:$isRetry"); pushError.value = ''; + if (isRetry) retrying.value = true; + DateTime startTime = DateTime.now(); + bool ret = false; try { // avoid double pushes in a row _syncAllFromRecent = true; @@ -226,12 +235,14 @@ class AbModel { } if (resp.statusCode == 200 && (resp.body.isEmpty || resp.body.toLowerCase() == 'null')) { + ret = true; _saveCache(); } else { Map json = _jsonDecode(resp.body, resp.statusCode); if (json.containsKey('error')) { throw json['error']; } else if (resp.statusCode == 200) { + ret = true; _saveCache(); } else { throw 'HTTP ${resp.statusCode}'; @@ -240,12 +251,25 @@ class AbModel { } catch (e) { pushError.value = '${translate('push_ab_failed_tip')}: ${translate(e.toString())}'; - if (toast && gFFI.peerTabModel.currentTab != PeerTabIndex.ab.index) { - BotToast.showText(contentColor: Colors.red, text: pushError.value); - } - } finally { - _syncAllFromRecent = true; } + _syncAllFromRecent = true; + if (isRetry) { + var ms = + (Duration(milliseconds: 200) - DateTime.now().difference(startTime)) + .inMilliseconds; + ms = ms > 0 ? ms : 0; + Future.delayed(Duration(milliseconds: ms), () { + retrying.value = false; + }); + } + + if (!ret && toastIfFail) { + BotToast.showText(contentColor: Colors.red, text: pushError.value); + } + if (ret && toastIfSucc) { + showToast(translate('Successful')); + } + return ret; } Peer? find(String id) { @@ -410,7 +434,7 @@ class AbModel { } // Be careful with loop calls if (syncChanged && push) { - pushAb(); + pushAb(toastIfSucc: false); } else if (uiChanged) { peers.refresh(); } From 6f8483aecd09fa5d41cc4a48438fab5047dedfef Mon Sep 17 00:00:00 2001 From: 21pages Date: Wed, 16 Aug 2023 13:30:38 +0800 Subject: [PATCH 4/4] ab: full check for adding peers from recent/batch operation Signed-off-by: 21pages --- flutter/lib/common/widgets/peer_tab_page.dart | 10 +++++++++- flutter/lib/models/ab_model.dart | 18 +++++++++++++----- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/flutter/lib/common/widgets/peer_tab_page.dart b/flutter/lib/common/widgets/peer_tab_page.dart index 0937f6f5f..047d95f11 100644 --- a/flutter/lib/common/widgets/peer_tab_page.dart +++ b/flutter/lib/common/widgets/peer_tab_page.dart @@ -405,10 +405,18 @@ class _PeerTabPageState extends State !gFFI.userModel.isLogin || model.currentTab == PeerTabIndex.ab.index, child: InkWell( onTap: () { + if (gFFI.abModel.isFull(true)) { + return; + } final peers = model.selectedPeers; gFFI.abModel.addPeers(peers); - gFFI.abModel.pushAb(); + final future = gFFI.abModel.pushAb(); model.setMultiSelectionMode(false); + Future.delayed(Duration.zero, () async { + await future; + await Future.delayed(Duration(seconds: 2)); // toast + gFFI.abModel.isFull(true); + }); }, child: Tooltip( message: translate('Add to Address Book'), diff --git a/flutter/lib/models/ab_model.dart b/flutter/lib/models/ab_model.dart index 9426b0571..1c07582d4 100644 --- a/flutter/lib/models/ab_model.dart +++ b/flutter/lib/models/ab_model.dart @@ -163,10 +163,16 @@ class AbModel { } } - void addPeers(List ps) { + bool addPeers(List ps) { + bool allAdded = true; for (var p in ps) { - addPeer(p); + if (!isFull(false)) { + addPeer(p); + } else { + allAdded = false; + } } + return allAdded; } void addTag(String tag) async { @@ -419,9 +425,11 @@ class AbModel { var r = recents[i]; var index = peers.indexWhere((e) => e.id == r.id); if (index < 0) { - peers.add(r); - syncChanged = true; - uiChanged = true; + if (!isFull(false)) { + peers.add(r); + syncChanged = true; + uiChanged = true; + } } else { if (!r.equal(peers[index])) { uiChanged = true;