diff --git a/lib/auxiliary.dart b/lib/auxiliary.dart index 12e4036..7955fe5 100644 --- a/lib/auxiliary.dart +++ b/lib/auxiliary.dart @@ -44,7 +44,8 @@ extension NeosStringExtensions on Uri { } class Aux { - static String neosDbToHttp(String neosdb) { + static String neosDbToHttp(String? neosdb) { + if (neosdb == null || neosdb.isEmpty) return ""; final fullUri = neosdb.replaceFirst("neosdb:///", Config.neosCdnUrl); final lastPeriodIndex = fullUri.lastIndexOf("."); if (lastPeriodIndex != -1 && fullUri.length - lastPeriodIndex < 8) { diff --git a/lib/clients/api_client.dart b/lib/clients/api_client.dart index fe4df9f..18bf91a 100644 --- a/lib/clients/api_client.dart +++ b/lib/clients/api_client.dart @@ -1,6 +1,6 @@ import 'dart:async'; import 'dart:convert'; -import 'package:contacts_plus_plus/clients/neos_hub.dart'; +import 'package:contacts_plus_plus/clients/messaging_client.dart'; import 'package:contacts_plus_plus/clients/settings_client.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -148,7 +148,7 @@ class ApiClient { class ClientHolder extends InheritedWidget { final ApiClient apiClient; final SettingsClient settingsClient; - late final NeosHub hub; + late final MessagingClient messagingClient; ClientHolder({ super.key, @@ -156,7 +156,7 @@ class ClientHolder extends InheritedWidget { required this.settingsClient, required super.child }) : apiClient = ApiClient(authenticationData: authenticationData) { - hub = NeosHub(apiClient: apiClient); + messagingClient = MessagingClient(apiClient: apiClient); } static ClientHolder? maybeOf(BuildContext context) { @@ -173,5 +173,5 @@ class ClientHolder extends InheritedWidget { bool updateShouldNotify(covariant ClientHolder oldWidget) => oldWidget.apiClient != apiClient || oldWidget.settingsClient != settingsClient - || oldWidget.hub != hub; + || oldWidget.messagingClient != messagingClient; } diff --git a/lib/clients/neos_hub.dart b/lib/clients/messaging_client.dart similarity index 90% rename from lib/clients/neos_hub.dart rename to lib/clients/messaging_client.dart index ac887ee..23db9f5 100644 --- a/lib/clients/neos_hub.dart +++ b/lib/clients/messaging_client.dart @@ -2,6 +2,7 @@ import 'dart:convert'; import 'dart:io'; import 'package:contacts_plus_plus/apis/message_api.dart'; import 'package:contacts_plus_plus/models/authentication_data.dart'; +import 'package:contacts_plus_plus/models/friend.dart'; import 'package:http/http.dart' as http; import 'package:contacts_plus_plus/clients/api_client.dart'; @@ -29,12 +30,13 @@ enum EventTarget { } } -class NeosHub { +class MessagingClient { static const String eofChar = ""; 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"; final ApiClient _apiClient; + final Map _friendsCache = {}; final Map _messageCache = {}; final Map _updateListeners = {}; final Logger _logger = Logger("NeosHub"); @@ -42,7 +44,7 @@ class NeosHub { WebSocket? _wsChannel; bool _isConnecting = false; - NeosHub({required ApiClient apiClient}) + MessagingClient({required ApiClient apiClient}) : _apiClient = apiClient { start(); } @@ -52,7 +54,16 @@ class NeosHub { _wsChannel!.add(jsonEncode(data)+eofChar); } - Future getCache(String userId) async { + void updateFriendsCache(List friends) { + _friendsCache.clear(); + for (final friend in friends) { + _friendsCache[friend.id] = friend; + } + } + + Friend? getAsFriend(String userId) => _friendsCache[userId]; + + Future getMessageCache(String userId) async { var cache = _messageCache[userId]; if (cache == null){ cache = MessageCache(apiClient: _apiClient, userId: userId); @@ -168,21 +179,21 @@ class NeosHub { case EventTarget.messageSent: final msg = args[0]; final message = Message.fromMap(msg, withState: MessageState.sent); - final cache = await getCache(message.recipientId); + final cache = await getMessageCache(message.recipientId); cache.addMessage(message); notifyListener(message.recipientId); break; case EventTarget.messageReceived: final msg = args[0]; final message = Message.fromMap(msg); - final cache = await getCache(message.senderId); + final cache = await getMessageCache(message.senderId); cache.addMessage(message); notifyListener(message.senderId); break; case EventTarget.messagesRead: final messageIds = args[0]["ids"] as List; final recipientId = args[0]["recipientId"]; - final cache = await getCache(recipientId ?? ""); + final cache = await getMessageCache(recipientId ?? ""); for (var id in messageIds) { cache.setMessageState(id, MessageState.read); } @@ -201,7 +212,7 @@ class NeosHub { ], }; _sendData(data); - final cache = await getCache(message.recipientId); + final cache = await getMessageCache(message.recipientId); cache.messages.add(message); notifyListener(message.recipientId); } diff --git a/lib/main.dart b/lib/main.dart index 6f0032b..9708fc4 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,7 +1,7 @@ import 'dart:developer'; import 'dart:io' show Platform; -import 'package:contacts_plus_plus/clients/neos_hub.dart'; +import 'package:contacts_plus_plus/clients/messaging_client.dart'; import 'package:contacts_plus_plus/clients/settings_client.dart'; import 'package:contacts_plus_plus/widgets/friends_list.dart'; import 'package:contacts_plus_plus/widgets/login_screen.dart'; @@ -31,8 +31,8 @@ void main() async { void callbackDispatcher() { Workmanager().executeTask((String task, Map? inputData) async { debugPrint("Native called background task: $task"); //simpleTask will be emitted here. - if (task == NeosHub.taskName) { - final unreads = NeosHub.backgroundCheckUnreads(inputData); + if (task == MessagingClient.taskName) { + final unreads = MessagingClient.backgroundCheckUnreads(inputData); } return Future.value(true); }); diff --git a/lib/widgets/friends_list.dart b/lib/widgets/friends_list.dart index cb5a3a2..45e2a20 100644 --- a/lib/widgets/friends_list.dart +++ b/lib/widgets/friends_list.dart @@ -85,6 +85,7 @@ class _FriendsListState extends State { _autoRefresh = Timer(_autoRefreshDuration, () => setState(() => _refreshFriendsList())); _refreshTimeout?.cancel(); _refreshTimeout = Timer(_refreshTimeoutDuration, () {}); + _clientHolder?.messagingClient.updateFriendsCache(friends); return friends; }); } @@ -156,7 +157,7 @@ class _FriendsListState extends State { ids: unread.map((e) => e.id).toList(), readTime: DateTime.now(), ); - _clientHolder!.hub.markMessagesRead(readBatch); + _clientHolder!.messagingClient.markMessagesRead(readBatch); } setState(() { unread.clear(); @@ -171,7 +172,10 @@ class _FriendsListState extends State { return DefaultErrorWidget( message: "${snapshot.error}", onRetry: () { - _refreshFriendsList(); + _refreshTimeout?.cancel(); + setState(() { + _refreshFriendsList(); + }); }, ); } else { diff --git a/lib/widgets/messages_list.dart b/lib/widgets/messages_list.dart index 695fedc..5aef62e 100644 --- a/lib/widgets/messages_list.dart +++ b/lib/widgets/messages_list.dart @@ -47,15 +47,15 @@ class _MessagesListState extends State { void _loadMessages() { _messageCacheFutureComplete = false; - _messageCacheFuture = _clientHolder?.hub.getCache(widget.friend.id) + _messageCacheFuture = _clientHolder?.messagingClient.getMessageCache(widget.friend.id) .whenComplete(() => _messageCacheFutureComplete = true); - _clientHolder?.hub.registerListener( + _clientHolder?.messagingClient.registerListener( widget.friend.id, () => setState(() {})); } @override void dispose() { - _clientHolder?.hub.unregisterListener(widget.friend.id); + _clientHolder?.messagingClient.unregisterListener(widget.friend.id); _messageTextController.dispose(); _sessionListScrollController.dispose(); super.dispose(); @@ -82,7 +82,7 @@ class _MessagesListState extends State { _messageScrollController.position.maxScrollExtent > 0 && _messageCacheFutureComplete) { setState(() { _messageCacheFutureComplete = false; - _messageCacheFuture = _clientHolder?.hub.getCache(widget.friend.id) + _messageCacheFuture = _clientHolder?.messagingClient.getMessageCache(widget.friend.id) .then((value) => value.loadOlderMessages()).whenComplete(() => _messageCacheFutureComplete = true); }); } @@ -273,7 +273,7 @@ class _MessagesListState extends State { sendTime: DateTime.now().toUtc(), ); try { - _clientHolder!.hub.sendMessage(message); + _clientHolder!.messagingClient.sendMessage(message); _messageTextController.clear(); setState(() {}); } catch (e) { diff --git a/lib/widgets/user_list_tile.dart b/lib/widgets/user_list_tile.dart index d74ca71..e1e48e4 100644 --- a/lib/widgets/user_list_tile.dart +++ b/lib/widgets/user_list_tile.dart @@ -1,12 +1,14 @@ +import 'package:contacts_plus_plus/auxiliary.dart'; import 'package:contacts_plus_plus/models/user.dart'; import 'package:contacts_plus_plus/widgets/generic_avatar.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; class UserListTile extends StatefulWidget { - const UserListTile({required this.user, super.key}); + const UserListTile({required this.user, required this.isFriend, super.key}); final User user; + final bool isFriend; @override State createState() => _UserListTileState(); @@ -14,12 +16,12 @@ class UserListTile extends StatefulWidget { class _UserListTileState extends State { final DateFormat _regDateFormat = DateFormat.yMMMMd('en_US'); - late bool _localAdded = widget.user.userProfile != null; + late bool _localAdded = widget.isFriend; @override Widget build(BuildContext context) { return ListTile( - leading: GenericAvatar(imageUri: widget.user.userProfile?.iconUrl ?? "",), + leading: GenericAvatar(imageUri: Aux.neosDbToHttp(widget.user.userProfile?.iconUrl),), title: Text(widget.user.username), subtitle: Text(_regDateFormat.format(widget.user.registrationDate)), trailing: IconButton( diff --git a/lib/widgets/user_search.dart b/lib/widgets/user_search.dart index 058b38f..9146063 100644 --- a/lib/widgets/user_search.dart +++ b/lib/widgets/user_search.dart @@ -68,15 +68,15 @@ class _UserSearchState extends State { builder: (context, snapshot) { if (snapshot.hasData) { final users = (snapshot.data as List); + final mClient = ClientHolder.of(context).messagingClient; return ListView.builder( itemCount: users.length, itemBuilder: (context, index) { - return UserListTile(user: users[index]); + final user = users[index]; + return UserListTile(user: user, isFriend: mClient.getAsFriend(user.id) != null,); }, ); } else if (snapshot.hasError) { - FlutterError.reportError( - FlutterErrorDetails(exception: snapshot.error!, stack: snapshot.stackTrace)); final err = snapshot.error; if (err is SearchError) { return DefaultErrorWidget( @@ -84,6 +84,8 @@ class _UserSearchState extends State { iconOverride: err.icon, ); } else { + FlutterError.reportError( + FlutterErrorDetails(exception: snapshot.error!, stack: snapshot.stackTrace)); return DefaultErrorWidget(title: "${snapshot.error}",); } } else {