diff --git a/lib/matrix_api_lite/model/event_types.dart b/lib/matrix_api_lite/model/event_types.dart index 0e39bb5d..b99282ea 100644 --- a/lib/matrix_api_lite/model/event_types.dart +++ b/lib/matrix_api_lite/model/event_types.dart @@ -61,6 +61,10 @@ abstract class EventTypes { 'org.matrix.call.asserted_identity'; static const String Unknown = 'm.unknown'; + /// An internal event type indicating that the last event in the room for + /// a room list preview is currently being refreshed. + static const String refreshingLastEvent = 'com.famedly.refreshing_last_event'; + // To device event types static const String RoomKey = 'm.room_key'; static const String ForwardedRoomKey = 'm.forwarded_room_key'; diff --git a/lib/src/client.dart b/lib/src/client.dart index a2e493e0..5d0c0453 100644 --- a/lib/src/client.dart +++ b/lib/src/client.dart @@ -2740,13 +2740,25 @@ class Client extends MatrixApi { Logs().d('Skip store LeftRoomUpdate for unknown room', id); continue; } - await database.storeRoomUpdate(id, syncRoomUpdate, room.lastEvent, this); if (syncRoomUpdate is JoinedRoomUpdate && - syncRoomUpdate.timeline?.limited == true && - room.lastEvent == null) { + (room.lastEvent?.type == EventTypes.refreshingLastEvent || + (syncRoomUpdate.timeline?.limited == true && + room.lastEvent == null))) { + room.lastEvent = Event( + originServerTs: + syncRoomUpdate.timeline?.events?.firstOrNull?.originServerTs ?? + DateTime.now(), + type: EventTypes.refreshingLastEvent, + content: {'body': 'Refreshing last event...'}, + room: room, + eventId: generateUniqueTransactionId(), + senderId: userID!, + ); runInRoot(room.refreshLastEvent); } + + await database.storeRoomUpdate(id, syncRoomUpdate, room.lastEvent, this); } } diff --git a/lib/src/room.dart b/lib/src/room.dart index 91cd80e7..dd7c6825 100644 --- a/lib/src/room.dart +++ b/lib/src/room.dart @@ -379,19 +379,37 @@ class Room { /// Fetches the most recent event in the timeline from the server to have /// a valid preview after receiving a limited timeline from the sync. Will - /// be triggered by the sync loop on demand. - Future refreshLastEvent() async { + /// be triggered by the sync loop on demand. Multiple requests will be + /// combined to the same request. + Future refreshLastEvent({ + timeout = const Duration(seconds: 30), + }) async { + final lastEvent = _refreshingLastEvent ??= _refreshLastEvent(); + _refreshingLastEvent = null; + return lastEvent; + } + + Future? _refreshingLastEvent; + + Future _refreshLastEvent({ + timeout = const Duration(seconds: 30), + }) async { if (membership != Membership.join) return null; final filter = StateFilter(types: client.roomPreviewLastEvents.toList()); - final result = await client.getRoomEvents( - id, - Direction.b, - limit: 1, - filter: jsonEncode(filter.toJson()), - ); + final result = await client + .getRoomEvents( + id, + Direction.b, + limit: 1, + filter: jsonEncode(filter.toJson()), + ) + .timeout(timeout); final matrixEvent = result.chunk.firstOrNull; if (matrixEvent == null) { + if (lastEvent?.type == EventTypes.refreshingLastEvent) { + lastEvent = null; + } Logs().d('No last event found for room', id); return null; } @@ -492,8 +510,16 @@ class Room { String get displayname => getLocalizedDisplayname(); /// When was the last event received. - DateTime get latestEventReceivedTime => - lastEvent?.originServerTs ?? DateTime.now(); + DateTime get latestEventReceivedTime { + final lastEventTime = lastEvent?.originServerTs; + if (lastEventTime != null) return lastEventTime; + + if (membership == Membership.invite) return DateTime.now(); + final createEvent = getState(EventTypes.RoomCreate); + if (createEvent is MatrixEvent) return createEvent.originServerTs; + + return DateTime(0); + } /// Call the Matrix API to change the name of this room. Returns the event ID of the /// new m.room.name event. diff --git a/lib/src/utils/event_localizations.dart b/lib/src/utils/event_localizations.dart index a4fb76d8..4f19972e 100644 --- a/lib/src/utils/event_localizations.dart +++ b/lib/src/utils/event_localizations.dart @@ -299,5 +299,6 @@ abstract class EventLocalizations { ?.tryGet('key') ?? body, ), + EventTypes.refreshingLastEvent: (_, i18n, ___) => i18n.refreshingLastEvent, }; } diff --git a/lib/src/utils/matrix_default_localizations.dart b/lib/src/utils/matrix_default_localizations.dart index 9f5efc58..c9076777 100644 --- a/lib/src/utils/matrix_default_localizations.dart +++ b/lib/src/utils/matrix_default_localizations.dart @@ -318,4 +318,7 @@ class MatrixDefaultLocalizations extends MatrixLocalizations { : '${duration.inMinutes.toString().padLeft(2, '0')}:${(duration.inSeconds % 60).toString().padLeft(2, '0')} '; return '$senderName: ${durationString}Voice message'; } + + @override + String get refreshingLastEvent => 'Refreshing last event...'; } diff --git a/lib/src/utils/matrix_localizations.dart b/lib/src/utils/matrix_localizations.dart index 921747f0..37c9672f 100644 --- a/lib/src/utils/matrix_localizations.dart +++ b/lib/src/utils/matrix_localizations.dart @@ -62,6 +62,8 @@ abstract class MatrixLocalizations { String get cancelledSend; + String get refreshingLastEvent; + String youInvitedBy(String senderName); String invitedBy(String senderName); diff --git a/test/client_test.dart b/test/client_test.dart index e38378a9..19773a1e 100644 --- a/test/client_test.dart +++ b/test/client_test.dart @@ -201,26 +201,26 @@ void main() { matrix.getDirectChatFromUserId('@bob:example.com'), '!726s6s6q:example.com', ); - expect(matrix.rooms[2].directChatMatrixID, '@bob:example.com'); + expect(matrix.rooms[1].directChatMatrixID, '@bob:example.com'); expect(matrix.directChats, matrix.accountData['m.direct']?.content); // ignore: deprecated_member_use_from_same_package expect(matrix.presences.length, 1); - expect(matrix.rooms[2].ephemerals.length, 2); - expect(matrix.rooms[2].typingUsers.length, 1); - expect(matrix.rooms[2].typingUsers[0].id, '@alice:example.com'); - expect(matrix.rooms[2].roomAccountData.length, 3); - expect(matrix.rooms[2].encrypted, true); + expect(matrix.rooms[1].ephemerals.length, 2); + expect(matrix.rooms[1].typingUsers.length, 1); + expect(matrix.rooms[1].typingUsers[0].id, '@alice:example.com'); + expect(matrix.rooms[1].roomAccountData.length, 3); + expect(matrix.rooms[1].encrypted, true); expect( - matrix.rooms[2].encryptionAlgorithm, + matrix.rooms[1].encryptionAlgorithm, Client.supportedGroupEncryptionAlgorithms.first, ); expect( matrix - .rooms[2].receiptState.global.otherUsers['@alice:example.com']?.ts, + .rooms[1].receiptState.global.otherUsers['@alice:example.com']?.ts, 1436451550453, ); expect( - matrix.rooms[2].receiptState.global.otherUsers['@alice:example.com'] + matrix.rooms[1].receiptState.global.otherUsers['@alice:example.com'] ?.eventId, '\$7365636s6r6432:example.com', ); @@ -231,7 +231,7 @@ void main() { expect(inviteRoom.states[EventTypes.RoomMember]?.length, 1); expect(matrix.rooms.length, 3); expect( - matrix.rooms[2].canonicalAlias, + matrix.rooms[1].canonicalAlias, "#famedlyContactDiscovery:${matrix.userID!.split(":")[1]}", ); expect(