Fix contact names before initial chat rendering (1.0.5)
Tests / test (push) Canceled after 0s
Build Android (FCM) / build-android-fcm (push) Canceled after 0s
Build Android / build-android (push) Canceled after 0s
Build iOS / build-ios (push) Canceled after 0s
Build Linux / build-linux (push) Canceled after 0s
Build macOS / build-macos (push) Canceled after 0s
Build Windows / build-windows (push) Canceled after 0s
Native crypto / native-crypto (push) Canceled after 0s
Release (main) / android (oneme) (push) Canceled after 0s
Release (main) / android (qlyra) (push) Canceled after 0s
Release (main) / windows (push) Canceled after 0s
Release (main) / linux (push) Canceled after 0s
Release (main) / macos (push) Canceled after 0s
Release (main) / ios (push) Canceled after 0s
Release (main) / release (push) Canceled after 0s

This commit is contained in:
sevenhill
2026-10-08 05:28:52 +03:00
parent b4c27da560
commit 6942981674
5 changed files with 237 additions and 11 deletions
+19 -1
View File
@@ -42,9 +42,11 @@ class ContactCache {
final name = value['n'];
final avatar = value['a'];
final opts = value['o'];
final phone = value['p'];
if (name is String) _nameCache[id] = name;
if (avatar is String) _avatarCache[id] = avatar;
if (opts is List) _optionsCache[id] = opts.whereType<String>().toSet();
if (phone is int && phone > 0) _phoneCache[id] = phone;
});
} catch (_) {}
}
@@ -67,7 +69,9 @@ class ContactCache {
}
static void putPhone(int id, int phone) {
if (phone > 0) _phoneCache[id] = phone;
if (phone <= 0 || _phoneCache[id] == phone) return;
_phoneCache[id] = phone;
_scheduleSave();
}
static String? get(int id) {
@@ -102,6 +106,17 @@ class ContactCache {
unawaited(_wipePersisted());
}
@visibleForTesting
static void resetMemoryForTesting() {
_nameCache.clear();
_avatarCache.clear();
_optionsCache.clear();
_phoneCache.clear();
_saveTimer?.cancel();
_saveTimer = null;
_loaded = false;
}
static void _scheduleSave() {
_saveTimer?.cancel();
_saveTimer = Timer(const Duration(seconds: 3), () => unawaited(_save()));
@@ -112,6 +127,7 @@ class ContactCache {
..._nameCache.keys,
..._avatarCache.keys,
..._optionsCache.keys,
..._phoneCache.keys,
};
final map = <String, dynamic>{};
for (final id in ids) {
@@ -119,9 +135,11 @@ class ContactCache {
final name = _nameCache[id];
final avatar = _avatarCache[id];
final opts = _optionsCache[id];
final phone = _phoneCache[id];
if (name != null) entry['n'] = name;
if (avatar != null) entry['a'] = avatar;
if (opts != null && opts.isNotEmpty) entry['o'] = opts.toList();
if (phone != null) entry['p'] = phone;
if (entry.isNotEmpty) map['$id'] = entry;
}
final prefs = await SharedPreferences.getInstance();
@@ -0,0 +1,27 @@
/// Prepare local names before the first chat list is rendered.
/// Neither operation requires a server connection or a permission prompt.
Future<void> prepareStartupContactNames({
required Future<int?> accountReady,
required Future<void> settingsReady,
required Future<void> cacheReady,
required Future<void> Function(int accountId) primeContacts,
required Future<void> Function() loadPhonebook,
required void Function(String source, Object error, StackTrace stack) onError,
}) async {
await Future.wait<void>([settingsReady, cacheReady]);
final accountId = await accountReady;
Future<void> attempt(String source, Future<void> Function() load) async {
try {
await load();
} catch (error, stack) {
onError(source, error, stack);
}
}
await Future.wait<void>([
if (accountId != null)
attempt('cached contacts', () => primeContacts(accountId)),
attempt('phonebook', loadPhonebook),
]);
}
+15 -9
View File
@@ -53,6 +53,7 @@ import 'core/config/app_commands.dart';
import 'core/config/app_phonebook_names.dart';
import 'core/contacts/phonebook_contact_sync.dart';
import 'core/contacts/device_contacts_service.dart';
import 'core/contacts/startup_contact_names.dart';
import 'core/config/app_link_preview.dart';
import 'core/config/app_media_cache.dart';
import 'core/config/app_video_note_quality.dart';
@@ -266,6 +267,16 @@ void main(List<String> args) async {
final storiesFuture = AppStories.load();
final commandsFuture = AppCommands.load();
final phonebookNamesFuture = AppPhonebookNames.load();
final contactCacheFuture = ContactCache.load();
final contactNamesReady = prepareStartupContactNames(
accountReady: accountReady,
settingsReady: phonebookNamesFuture.then((_) {}),
cacheReady: contactCacheFuture,
primeContacts: ContactsModule.primeCacheFromDb,
loadPhonebook: DeviceContactsService.loadFromStartup,
onError: (source, error, stack) =>
logger.e('Startup $source failed: $error', stackTrace: stack),
);
final linkPreviewFuture = AppLinkPreview.load();
final cacheLimitFuture = AppMediaCacheLimit.load();
final videoNoteResolutionFuture = AppVideoNoteResolution.load();
@@ -292,7 +303,7 @@ void main(List<String> args) async {
ChatEncryptionStore.instance.load(),
QlyraSettings.load(),
AppLock.instance.load(),
ContactCache.load(),
contactCacheFuture,
]);
E2eeService.instance.attach(messagesModule);
if (QlyraSettings.ghostMode.value) SelfPresence.markOffline();
@@ -352,6 +363,7 @@ void main(List<String> args) async {
await transportReady;
await trafficCaptureFuture;
await debugLogFuture;
await contactNamesReady;
runApp(
QlyraApp(
initialHasAccount: DebugTest.enabled || activeAccountId != null,
@@ -365,11 +377,11 @@ void main(List<String> args) async {
),
);
WidgetsBinding.instance.addPostFrameCallback((_) {
unawaited(_loadOptionalStartupServices(activeAccountId));
unawaited(_loadOptionalStartupServices());
});
}
Future<void> _loadOptionalStartupServices(int? accountId) async {
Future<void> _loadOptionalStartupServices() async {
Future<void> attempt(String name, Future<void> Function() load) async {
try {
await load();
@@ -383,12 +395,6 @@ Future<void> _loadOptionalStartupServices(int? accountId) async {
attempt('push', () => FkmController.instance.init(api)),
attempt('stories', storiesModule.loadCache),
attempt('links', DeepLinkService.instance.init),
attempt('contacts', () async {
if (accountId != null) await ContactsModule.primeCacheFromDb(accountId);
await DeviceContactsService.loadFromStartup();
// Refresh names after the phone book arrives behind the chat list.
ContactsModule.revision.value++;
}),
]);
}