Merge pull request #2139 from famedly/krille/auto-fetch-last-event
feat: Auto refresh last event after limited timeline
This commit is contained in:
commit
2d6f043861
|
|
@ -1740,6 +1740,28 @@ class FakeMatrixApi extends BaseClient {
|
||||||
'origin_server_ts': 1432735824653,
|
'origin_server_ts': 1432735824653,
|
||||||
'unsigned': {'age': 1234},
|
'unsigned': {'age': 1234},
|
||||||
},
|
},
|
||||||
|
'/client/v3/rooms/!localpart%3Aserver.abc/messages?dir=b&limit=1&filter=%7B%22types%22%3A%5B%22m.room.message%22%2C%22m.room.encrypted%22%2C%22m.sticker%22%2C%22m.call.invite%22%2C%22m.call.answer%22%2C%22m.call.reject%22%2C%22m.call.hangup%22%2C%22com.famedly.call.member%22%5D%7D':
|
||||||
|
(var req) => {
|
||||||
|
'start': 't47429-4392820_219380_26003_2265',
|
||||||
|
'end': 't47409-4357353_219380_26003_2265',
|
||||||
|
'chunk': [
|
||||||
|
{
|
||||||
|
'content': {
|
||||||
|
'body': 'This is an example text message',
|
||||||
|
'msgtype': 'm.text',
|
||||||
|
'format': 'org.matrix.custom.html',
|
||||||
|
'formatted_body':
|
||||||
|
'<b>This is an example text message</b>',
|
||||||
|
},
|
||||||
|
'type': 'm.room.message',
|
||||||
|
'event_id': '3143273582443PhrSn:example.org',
|
||||||
|
'room_id': '!1234:example.com',
|
||||||
|
'sender': '@example:example.org',
|
||||||
|
'origin_server_ts': 1432735824653,
|
||||||
|
'unsigned': {'age': 1234},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
'/client/v3/rooms/new_room_id/messages?from=emptyHistoryResponse&dir=b&limit=30&filter=%7B%22lazy_load_members%22%3Atrue%7D':
|
'/client/v3/rooms/new_room_id/messages?from=emptyHistoryResponse&dir=b&limit=30&filter=%7B%22lazy_load_members%22%3Atrue%7D':
|
||||||
(var req) => emptyHistoryResponse,
|
(var req) => emptyHistoryResponse,
|
||||||
'/client/v3/rooms/new_room_id/messages?from=1&dir=b&limit=30&filter=%7B%22lazy_load_members%22%3Atrue%7D':
|
'/client/v3/rooms/new_room_id/messages?from=1&dir=b&limit=30&filter=%7B%22lazy_load_members%22%3Atrue%7D':
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,10 @@ abstract class EventTypes {
|
||||||
'org.matrix.call.asserted_identity';
|
'org.matrix.call.asserted_identity';
|
||||||
static const String Unknown = 'm.unknown';
|
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
|
// To device event types
|
||||||
static const String RoomKey = 'm.room_key';
|
static const String RoomKey = 'm.room_key';
|
||||||
static const String ForwardedRoomKey = 'm.forwarded_room_key';
|
static const String ForwardedRoomKey = 'm.forwarded_room_key';
|
||||||
|
|
|
||||||
|
|
@ -2632,13 +2632,15 @@ class Client extends MatrixApi {
|
||||||
final id = entry.key;
|
final id = entry.key;
|
||||||
final syncRoomUpdate = entry.value;
|
final syncRoomUpdate = entry.value;
|
||||||
|
|
||||||
|
final room = await _updateRoomsByRoomUpdate(id, syncRoomUpdate);
|
||||||
|
|
||||||
// Is the timeline limited? Then all previous messages should be
|
// Is the timeline limited? Then all previous messages should be
|
||||||
// removed from the database!
|
// removed from the database!
|
||||||
if (syncRoomUpdate is JoinedRoomUpdate &&
|
if (syncRoomUpdate is JoinedRoomUpdate &&
|
||||||
syncRoomUpdate.timeline?.limited == true) {
|
syncRoomUpdate.timeline?.limited == true) {
|
||||||
await database.deleteTimelineForRoom(id);
|
await database.deleteTimelineForRoom(id);
|
||||||
|
room.lastEvent = null;
|
||||||
}
|
}
|
||||||
final room = await _updateRoomsByRoomUpdate(id, syncRoomUpdate);
|
|
||||||
|
|
||||||
final timelineUpdateType = direction != null
|
final timelineUpdateType = direction != null
|
||||||
? (direction == Direction.b
|
? (direction == Direction.b
|
||||||
|
|
@ -2738,6 +2740,24 @@ class Client extends MatrixApi {
|
||||||
Logs().d('Skip store LeftRoomUpdate for unknown room', id);
|
Logs().d('Skip store LeftRoomUpdate for unknown room', id);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (syncRoomUpdate is JoinedRoomUpdate &&
|
||||||
|
(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);
|
await database.storeRoomUpdate(id, syncRoomUpdate, room.lastEvent, this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3035,13 +3055,6 @@ class Client extends MatrixApi {
|
||||||
}
|
}
|
||||||
if (type != EventUpdateType.timeline) break;
|
if (type != EventUpdateType.timeline) break;
|
||||||
|
|
||||||
// If last event is null or not a valid room preview event anyway,
|
|
||||||
// just use this:
|
|
||||||
if (room.lastEvent == null) {
|
|
||||||
room.lastEvent = event;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Is this event redacting the last event?
|
// Is this event redacting the last event?
|
||||||
if (event.type == EventTypes.Redaction &&
|
if (event.type == EventTypes.Redaction &&
|
||||||
({
|
({
|
||||||
|
|
|
||||||
|
|
@ -377,6 +377,73 @@ class Room {
|
||||||
|
|
||||||
Event? lastEvent;
|
Event? lastEvent;
|
||||||
|
|
||||||
|
/// 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. Multiple requests will be
|
||||||
|
/// combined to the same request.
|
||||||
|
Future<Event?> refreshLastEvent({
|
||||||
|
timeout = const Duration(seconds: 30),
|
||||||
|
}) async {
|
||||||
|
final lastEvent = _refreshingLastEvent ??= _refreshLastEvent();
|
||||||
|
_refreshingLastEvent = null;
|
||||||
|
return lastEvent;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Event?>? _refreshingLastEvent;
|
||||||
|
|
||||||
|
Future<Event?> _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()),
|
||||||
|
)
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
var event = Event.fromMatrixEvent(
|
||||||
|
matrixEvent,
|
||||||
|
this,
|
||||||
|
status: EventStatus.synced,
|
||||||
|
);
|
||||||
|
if (event.type == EventTypes.Encrypted) {
|
||||||
|
final encryption = client.encryption;
|
||||||
|
if (encryption != null) {
|
||||||
|
event = await encryption.decryptRoomEvent(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lastEvent = event;
|
||||||
|
|
||||||
|
Logs().d('Refreshed last event for room', id);
|
||||||
|
|
||||||
|
// Trigger sync handling so that lastEvent gets stored and room list gets
|
||||||
|
// updated.
|
||||||
|
await _handleFakeSync(
|
||||||
|
SyncUpdate(
|
||||||
|
nextBatch: '',
|
||||||
|
rooms: RoomsUpdate(
|
||||||
|
join: {
|
||||||
|
id: JoinedRoomUpdate(timeline: TimelineUpdate(limited: false)),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
void setEphemeral(BasicEvent ephemeral) {
|
void setEphemeral(BasicEvent ephemeral) {
|
||||||
ephemerals[ephemeral.type] = ephemeral;
|
ephemerals[ephemeral.type] = ephemeral;
|
||||||
if (ephemeral.type == 'm.typing') {
|
if (ephemeral.type == 'm.typing') {
|
||||||
|
|
@ -443,8 +510,16 @@ class Room {
|
||||||
String get displayname => getLocalizedDisplayname();
|
String get displayname => getLocalizedDisplayname();
|
||||||
|
|
||||||
/// When was the last event received.
|
/// When was the last event received.
|
||||||
DateTime get latestEventReceivedTime =>
|
DateTime get latestEventReceivedTime {
|
||||||
lastEvent?.originServerTs ?? DateTime.now();
|
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
|
/// Call the Matrix API to change the name of this room. Returns the event ID of the
|
||||||
/// new m.room.name event.
|
/// new m.room.name event.
|
||||||
|
|
|
||||||
|
|
@ -299,5 +299,6 @@ abstract class EventLocalizations {
|
||||||
?.tryGet<String>('key') ??
|
?.tryGet<String>('key') ??
|
||||||
body,
|
body,
|
||||||
),
|
),
|
||||||
|
EventTypes.refreshingLastEvent: (_, i18n, ___) => i18n.refreshingLastEvent,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -318,4 +318,7 @@ class MatrixDefaultLocalizations extends MatrixLocalizations {
|
||||||
: '${duration.inMinutes.toString().padLeft(2, '0')}:${(duration.inSeconds % 60).toString().padLeft(2, '0')} ';
|
: '${duration.inMinutes.toString().padLeft(2, '0')}:${(duration.inSeconds % 60).toString().padLeft(2, '0')} ';
|
||||||
return '$senderName: ${durationString}Voice message';
|
return '$senderName: ${durationString}Voice message';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get refreshingLastEvent => 'Refreshing last event...';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,8 @@ abstract class MatrixLocalizations {
|
||||||
|
|
||||||
String get cancelledSend;
|
String get cancelledSend;
|
||||||
|
|
||||||
|
String get refreshingLastEvent;
|
||||||
|
|
||||||
String youInvitedBy(String senderName);
|
String youInvitedBy(String senderName);
|
||||||
|
|
||||||
String invitedBy(String senderName);
|
String invitedBy(String senderName);
|
||||||
|
|
|
||||||
|
|
@ -201,26 +201,26 @@ void main() {
|
||||||
matrix.getDirectChatFromUserId('@bob:example.com'),
|
matrix.getDirectChatFromUserId('@bob:example.com'),
|
||||||
'!726s6s6q: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);
|
expect(matrix.directChats, matrix.accountData['m.direct']?.content);
|
||||||
// ignore: deprecated_member_use_from_same_package
|
// ignore: deprecated_member_use_from_same_package
|
||||||
expect(matrix.presences.length, 1);
|
expect(matrix.presences.length, 1);
|
||||||
expect(matrix.rooms[2].ephemerals.length, 2);
|
expect(matrix.rooms[1].ephemerals.length, 2);
|
||||||
expect(matrix.rooms[2].typingUsers.length, 1);
|
expect(matrix.rooms[1].typingUsers.length, 1);
|
||||||
expect(matrix.rooms[2].typingUsers[0].id, '@alice:example.com');
|
expect(matrix.rooms[1].typingUsers[0].id, '@alice:example.com');
|
||||||
expect(matrix.rooms[2].roomAccountData.length, 3);
|
expect(matrix.rooms[1].roomAccountData.length, 3);
|
||||||
expect(matrix.rooms[2].encrypted, true);
|
expect(matrix.rooms[1].encrypted, true);
|
||||||
expect(
|
expect(
|
||||||
matrix.rooms[2].encryptionAlgorithm,
|
matrix.rooms[1].encryptionAlgorithm,
|
||||||
Client.supportedGroupEncryptionAlgorithms.first,
|
Client.supportedGroupEncryptionAlgorithms.first,
|
||||||
);
|
);
|
||||||
expect(
|
expect(
|
||||||
matrix
|
matrix
|
||||||
.rooms[2].receiptState.global.otherUsers['@alice:example.com']?.ts,
|
.rooms[1].receiptState.global.otherUsers['@alice:example.com']?.ts,
|
||||||
1436451550453,
|
1436451550453,
|
||||||
);
|
);
|
||||||
expect(
|
expect(
|
||||||
matrix.rooms[2].receiptState.global.otherUsers['@alice:example.com']
|
matrix.rooms[1].receiptState.global.otherUsers['@alice:example.com']
|
||||||
?.eventId,
|
?.eventId,
|
||||||
'\$7365636s6r6432:example.com',
|
'\$7365636s6r6432:example.com',
|
||||||
);
|
);
|
||||||
|
|
@ -231,7 +231,7 @@ void main() {
|
||||||
expect(inviteRoom.states[EventTypes.RoomMember]?.length, 1);
|
expect(inviteRoom.states[EventTypes.RoomMember]?.length, 1);
|
||||||
expect(matrix.rooms.length, 3);
|
expect(matrix.rooms.length, 3);
|
||||||
expect(
|
expect(
|
||||||
matrix.rooms[2].canonicalAlias,
|
matrix.rooms[1].canonicalAlias,
|
||||||
"#famedlyContactDiscovery:${matrix.userID!.split(":")[1]}",
|
"#famedlyContactDiscovery:${matrix.userID!.split(":")[1]}",
|
||||||
);
|
);
|
||||||
expect(
|
expect(
|
||||||
|
|
|
||||||
|
|
@ -883,6 +883,29 @@ void main() {
|
||||||
expect(timeline.events.length, 17);
|
expect(timeline.events.length, 17);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Refresh last event', () async {
|
||||||
|
expect(room.lastEvent?.eventId, '12');
|
||||||
|
final lastEventUpdate =
|
||||||
|
room.client.onSync.stream.firstWhere((u) => u.nextBatch.isEmpty);
|
||||||
|
await room.client.handleSync(
|
||||||
|
SyncUpdate(
|
||||||
|
nextBatch: 'abcd',
|
||||||
|
rooms: RoomsUpdate(
|
||||||
|
join: {
|
||||||
|
room.id: JoinedRoomUpdate(
|
||||||
|
timeline: TimelineUpdate(
|
||||||
|
events: [],
|
||||||
|
limited: true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await lastEventUpdate;
|
||||||
|
expect(room.lastEvent?.eventId, '3143273582443PhrSn:example.org');
|
||||||
|
});
|
||||||
|
|
||||||
test('isFederated', () {
|
test('isFederated', () {
|
||||||
expect(room.isFederated, true);
|
expect(room.isFederated, true);
|
||||||
room.setState(
|
room.setState(
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue