From ece488d177dff87a714beb3662e03c697cd0a1e2 Mon Sep 17 00:00:00 2001 From: Nutcake Date: Tue, 16 May 2023 13:01:42 +0200 Subject: [PATCH] Make friend api calls more efficient and increase update rate --- lib/apis/friend_api.dart | 5 +-- lib/clients/messaging_client.dart | 56 ++++++++++++++++----------- lib/main.dart | 7 +++- lib/models/friend.dart | 5 +++ lib/widgets/friends/friends_list.dart | 5 +-- pubspec.lock | 16 ++++++++ pubspec.yaml | 2 + 7 files changed, 65 insertions(+), 31 deletions(-) diff --git a/lib/apis/friend_api.dart b/lib/apis/friend_api.dart index bf4dd2f..6098cbf 100644 --- a/lib/apis/friend_api.dart +++ b/lib/apis/friend_api.dart @@ -1,12 +1,11 @@ - import 'dart:convert'; import 'package:contacts_plus_plus/clients/api_client.dart'; import 'package:contacts_plus_plus/models/friend.dart'; class FriendApi { - static Future> getFriendsList(ApiClient client) async { - final response = await client.get("/users/${client.userId}/friends"); + static Future> getFriendsList(ApiClient client, {DateTime? lastStatusUpdate}) async { + final response = await client.get("/users/${client.userId}/friends${lastStatusUpdate != null ? "?lastStatusUpdate=${lastStatusUpdate.toUtc().toIso8601String()}" : ""}"); ApiClient.checkResponse(response); final data = jsonDecode(response.body) as List; return data.map((e) => Friend.fromMap(e)).toList(); diff --git a/lib/clients/messaging_client.dart b/lib/clients/messaging_client.dart index 5d0384b..9225d7b 100644 --- a/lib/clients/messaging_client.dart +++ b/lib/clients/messaging_client.dart @@ -8,6 +8,7 @@ import 'package:contacts_plus_plus/clients/notification_client.dart'; import 'package:contacts_plus_plus/models/authentication_data.dart'; import 'package:contacts_plus_plus/models/friend.dart'; import 'package:flutter/widgets.dart'; +import 'package:hive_flutter/hive_flutter.dart'; import 'package:http/http.dart' as http; import 'package:contacts_plus_plus/clients/api_client.dart'; @@ -46,10 +47,11 @@ class MessagingClient extends ChangeNotifier { static const String _negotiationPacket = "{\"protocol\":\"json\", \"version\":1}$eofChar"; static const List _reconnectTimeoutsSeconds = [0, 5, 10, 20, 60]; static const String taskName = "periodic-unread-check"; - static const Duration _autoRefreshDuration = Duration(seconds: 90); - static const Duration _refreshTimeoutDuration = Duration(seconds: 30); + static const Duration _autoRefreshDuration = Duration(seconds: 10); + static const Duration _unreadSafeguardDuration = Duration(seconds: 120); + static const String _messageBoxKey = "message-box"; + static const String _lastUpdateKey = "__last-update-time"; final ApiClient _apiClient; - final Map _friendsCache = {}; final List _sortedFriendsCache = []; // Keep a sorted copy so as to not have to sort during build() final Map _messageCache = {}; final Map> _unreads = {}; @@ -60,6 +62,7 @@ class MessagingClient extends ChangeNotifier { Timer? _notifyOnlineTimer; Timer? _autoRefresh; Timer? _refreshTimeout; + Timer? _unreadSafeguard; int _attempts = 0; WebSocket? _wsChannel; bool _isConnecting = false; @@ -71,7 +74,11 @@ class MessagingClient extends ChangeNotifier { MessagingClient({required ApiClient apiClient, required NotificationClient notificationClient}) : _apiClient = apiClient, _notificationClient = notificationClient { - refreshFriendsListWithErrorHandler(); + Hive.openBox(_messageBoxKey).then((box) async { + box.delete(_lastUpdateKey); + await refreshFriendsListWithErrorHandler(); + await _refreshUnreads(); + }); startWebsocket(); _notifyOnlineTimer = Timer.periodic(const Duration(seconds: 60), (timer) async { // We should probably let the MessagingClient handle the entire state of USerStatus instead of mirroring like this @@ -99,7 +106,16 @@ class MessagingClient extends ChangeNotifier { notifyListeners(); } - void refreshFriendsListWithErrorHandler () async { + Future _refreshUnreads() async { + _unreadSafeguard?.cancel(); + try { + final unreadMessages = await MessageApi.getUserMessages(_apiClient, unreadOnly: true); + updateAllUnreads(unreadMessages.toList()); + } catch (_) {} + _unreadSafeguard = Timer(_unreadSafeguardDuration, _refreshUnreads); + } + + Future refreshFriendsListWithErrorHandler () async { try { await refreshFriendsList(); } catch (e) { @@ -109,24 +125,15 @@ class MessagingClient extends ChangeNotifier { } Future refreshFriendsList() async { - if (_refreshTimeout?.isActive == true) return; - + DateTime? lastUpdateUtc = Hive.box(_messageBoxKey).get(_lastUpdateKey); _autoRefresh?.cancel(); _autoRefresh = Timer(_autoRefreshDuration, () => refreshFriendsList()); - _refreshTimeout?.cancel(); - _refreshTimeout = Timer(_refreshTimeoutDuration, () {}); - final unreadMessages = await MessageApi.getUserMessages(_apiClient, unreadOnly: true); - updateAllUnreads(unreadMessages.toList()); - - final friends = await FriendApi.getFriendsList(_apiClient); - _friendsCache.clear(); + final friends = await FriendApi.getFriendsList(_apiClient, lastStatusUpdate: lastUpdateUtc); for (final friend in friends) { - _friendsCache[friend.id] = friend; + await _updateFriend(friend); } - _sortedFriendsCache.clear(); - _sortedFriendsCache.addAll(friends); - _sortFriendsCache(); + _initStatus = ""; notifyListeners(); } @@ -183,7 +190,7 @@ class MessagingClient extends ChangeNotifier { return _unreads[message.senderId]?.any((element) => element.id == message.id) ?? false; } - Friend? getAsFriend(String userId) => _friendsCache[userId]; + Friend? getAsFriend(String userId) => Friend.fromMapOrNull(Hive.box(_messageBoxKey).get(userId)); List get cachedFriends => _sortedFriendsCache; @@ -196,8 +203,13 @@ class MessagingClient extends ChangeNotifier { notifyListeners(); } - void _updateFriend(Friend friend) { - _friendsCache[friend.id] = friend; + Future _updateFriend(Friend friend) async { + final box = Hive.box(_messageBoxKey); + box.put(friend.id, friend.toMap()); + final lastStatusUpdate = box.get(_lastUpdateKey); + if (lastStatusUpdate == null || friend.userStatus.lastStatusChange.isAfter(lastStatusUpdate)) { + await box.put(_lastUpdateKey, friend.userStatus.lastStatusChange); + } final sIndex = _sortedFriendsCache.indexWhere((element) => element.id == friend.id); if (sIndex == -1) { _sortedFriendsCache.add(friend); @@ -211,7 +223,7 @@ class MessagingClient extends ChangeNotifier { final friend = getAsFriend(userId); if (friend == null) return; final newStatus = await UserApi.getUserStatus(_apiClient, userId: userId); - _updateFriend(friend.copyWith(userStatus: newStatus)); + await _updateFriend(friend.copyWith(userStatus: newStatus)); notifyListeners(); } diff --git a/lib/main.dart b/lib/main.dart index 6dfae85..5119a0e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -12,6 +12,8 @@ import 'package:contacts_plus_plus/widgets/update_notifier.dart'; import 'package:dynamic_color/dynamic_color.dart'; import 'package:flutter/material.dart'; import 'package:flutter_phoenix/flutter_phoenix.dart'; +import 'package:hive_flutter/hive_flutter.dart'; +import 'package:intl/intl.dart'; import 'package:logging/logging.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:provider/provider.dart'; @@ -26,8 +28,9 @@ void main() async { isInDebugMode: true // If enabled it will post a notification whenever the task is running. Handy for debugging tasks ); } - - Logger.root.onRecord.listen((event) => log(event.message, name: event.loggerName, time: event.time)); + await Hive.initFlutter(); + final dateFormat = DateFormat.Hms(); + Logger.root.onRecord.listen((event) => log("${dateFormat.format(event.time)}: ${event.message}", name: event.loggerName, time: event.time)); final settingsClient = SettingsClient(); await settingsClient.loadSettings(); runApp(Phoenix(child: ContactsPlusPlus(settingsClient: settingsClient,))); diff --git a/lib/models/friend.dart b/lib/models/friend.dart index c606a36..cc8c89f 100644 --- a/lib/models/friend.dart +++ b/lib/models/friend.dart @@ -33,6 +33,11 @@ class Friend implements Comparable { ); } + static Friend? fromMapOrNull(Map? map) { + if (map == null) return null; + return Friend.fromMap(map); + } + Friend copyWith({ String? id, String? username, String? ownerId, UserStatus? userStatus, UserProfile? userProfile, FriendStatus? friendStatus, DateTime? latestMessageTime}) { diff --git a/lib/widgets/friends/friends_list.dart b/lib/widgets/friends/friends_list.dart index f27b5d4..b24e74a 100644 --- a/lib/widgets/friends/friends_list.dart +++ b/lib/widgets/friends/friends_list.dart @@ -1,5 +1,4 @@ import 'dart:async'; -import 'dart:ffi'; import 'package:contacts_plus_plus/apis/user_api.dart'; import 'package:contacts_plus_plus/client_holder.dart'; @@ -134,13 +133,11 @@ class _FriendsListState extends State { padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 2) ), onPressed: () { - setState(() { - _userStatusFuture = null; - }); setState(() { _userStatusFuture = UserApi.getUserStatus(clientHolder.apiClient, userId: clientHolder.apiClient .userId); }); + }, icon: const Icon(Icons.warning), label: const Text("Retry"), diff --git a/pubspec.lock b/pubspec.lock index d6bb090..8c30c56 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -272,6 +272,22 @@ packages: description: flutter source: sdk version: "0.0.0" + hive: + dependency: "direct main" + description: + name: hive + sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941" + url: "https://pub.dev" + source: hosted + version: "2.2.3" + hive_flutter: + dependency: "direct main" + description: + name: hive_flutter + sha256: dca1da446b1d808a51689fb5d0c6c9510c0a2ba01e22805d492c73b68e33eecc + url: "https://pub.dev" + source: hosted + version: "1.1.0" html: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 23d94dd..184e65c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -56,6 +56,8 @@ dependencies: photo_view: ^0.14.0 color: ^3.0.0 dynamic_color: ^1.6.5 + hive: ^2.2.3 + hive_flutter: ^1.1.0 dev_dependencies: flutter_test: