diff --git a/lib/matrix.dart b/lib/matrix.dart index bfacf431..12acb3df 100644 --- a/lib/matrix.dart +++ b/lib/matrix.dart @@ -26,6 +26,7 @@ export 'src/database/database_api.dart'; export 'src/database/hive_database.dart'; export 'src/database/fluffybox_database.dart'; export 'src/event.dart'; +export 'src/presence.dart'; export 'src/event_status.dart'; export 'src/voip.dart'; export 'src/voip_content.dart'; diff --git a/lib/src/client.dart b/lib/src/client.dart index 600b317b..9c6f21df 100644 --- a/lib/src/client.dart +++ b/lib/src/client.dart @@ -262,7 +262,7 @@ class Client extends MatrixApi { Map get accountData => _accountData; /// Presences of users by a given matrix ID - Map presences = {}; + Map presences = {}; int _transactionCounter = 0; @@ -889,8 +889,14 @@ class Client extends MatrixApi { StreamController.broadcast(); /// Callback will be called on presences. + @Deprecated( + 'Deprecated, use onPresenceChanged instead which has a timestamp.') final StreamController onPresence = StreamController.broadcast(); + /// Callback will be called on presence updates. + final StreamController onPresenceChanged = + StreamController.broadcast(); + /// Callback will be called on account data updates. final StreamController onAccountData = StreamController.broadcast(); @@ -1467,8 +1473,11 @@ class Client extends MatrixApi { } } for (final newPresence in sync.presence ?? []) { - presences[newPresence.senderId] = newPresence; + final cachedPresence = CachedPresence.fromMatrixEvent(newPresence); + presences[newPresence.senderId] = cachedPresence; + // ignore: deprecated_member_use_from_same_package onPresence.add(newPresence); + onPresenceChanged.add(cachedPresence); } for (final newAccountData in sync.accountData ?? []) { await database?.storeAccountData( diff --git a/lib/src/presence.dart b/lib/src/presence.dart new file mode 100644 index 00000000..c9ea1901 --- /dev/null +++ b/lib/src/presence.dart @@ -0,0 +1,71 @@ +/* + * Famedly Matrix SDK + * Copyright (C) 2019, 2020, 2021 Famedly GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import '../matrix.dart'; + +class CachedPresence { + PresenceType presence; + DateTime? lastActiveTimestamp; + String? statusMsg; + bool? currentlyActive; + String userid; + + CachedPresence(this.presence, int? lastActiveAgo, this.statusMsg, + this.currentlyActive, this.userid) { + if (lastActiveAgo != null) { + lastActiveTimestamp = + DateTime.now().subtract(Duration(milliseconds: lastActiveAgo)); + } + } + + CachedPresence.fromMatrixEvent(Presence event) + : this( + event.presence.presence, + event.presence.lastActiveAgo, + event.presence.statusMsg, + event.presence.currentlyActive, + event.senderId); + + CachedPresence.fromPresenceResponse(GetPresenceResponse event, String userid) + : this(event.presence, event.lastActiveAgo, event.statusMsg, + event.currentlyActive, userid); + + CachedPresence.neverSeen(String userid) + : presence = PresenceType.offline, + userid = userid; + + Presence toPresence() { + final content = { + 'presence': presence.toString(), + }; + if (currentlyActive != null) content['currently_active'] = currentlyActive!; + if (lastActiveTimestamp != null) { + content['last_active_ago'] = + DateTime.now().difference(lastActiveTimestamp!).inMilliseconds; + } + if (statusMsg != null) content['status_msg'] = statusMsg!; + + final json = { + 'content': content, + 'sender': '@example:localhost', + 'type': 'm.presence' + }; + + return Presence.fromJson(json); + } +} diff --git a/lib/src/user.dart b/lib/src/user.dart index ef266880..aa53e3f9 100644 --- a/lib/src/user.dart +++ b/lib/src/user.dart @@ -158,7 +158,23 @@ class User extends Event { ); /// The newest presence of this user if there is any and null if not. - Presence? get presence => room.client.presences[id]; + @Deprecated('Deprecated in favour of currentPresence.') + Presence? get presence => room.client.presences[id]?.toPresence(); + + /// The newest presence of this user if there is any. Fetches it from the server if necessary or returns offline. + Future get currentPresence async { + final cachedPresence = room.client.presences[id]; + if (cachedPresence != null) { + return cachedPresence; + } + + try { + final newPresence = await room.client.getPresence(id); + return CachedPresence.fromPresenceResponse(newPresence, id); + } catch (e) { + return CachedPresence.neverSeen(id); + } + } /// Whether the client is able to ban/unban this user. bool get canBan => room.canBan && powerLevel < room.ownPowerLevel; diff --git a/test/client_test.dart b/test/client_test.dart index 286442e7..4e0ed240 100644 --- a/test/client_test.dart +++ b/test/client_test.dart @@ -71,7 +71,7 @@ void main() { var presenceCounter = 0; var accountDataCounter = 0; - matrix.onPresence.stream.listen((Presence data) { + matrix.onPresenceChanged.stream.listen((CachedPresence data) { presenceCounter++; }); matrix.onAccountData.stream.listen((BasicEvent data) { @@ -150,7 +150,7 @@ void main() { expect(matrix.rooms.length, 2); expect(matrix.rooms[1].canonicalAlias, "#famedlyContactDiscovery:${matrix.userID!.split(":")[1]}"); - expect(matrix.presences['@alice:example.com']?.presence.presence, + expect(matrix.presences['@alice:example.com']?.presence, PresenceType.online); expect(presenceCounter, 1); expect(accountDataCounter, 9); diff --git a/test/user_test.dart b/test/user_test.dart index 2130d524..fee0f421 100644 --- a/test/user_test.dart +++ b/test/user_test.dart @@ -131,7 +131,7 @@ void main() { ] } })); - expect(user1.presence?.presence.presence, PresenceType.online); + expect((await user1.currentPresence).presence, PresenceType.online); }); test('canBan', () async { expect(user1.canBan, false);