[Lists] Add Timeline List type.

This commit is contained in:
Christian Pauly 2019-06-21 12:18:54 +02:00
parent 372c185228
commit 1b1abf7190
6 changed files with 190 additions and 48 deletions

View File

@ -126,13 +126,16 @@ class Event {
/// Generate a new Event object from a json string, mostly a table row.
static Event fromJson(Map<String, dynamic> jsonObj, Room room) {
Map<String, dynamic> content;
try {
content = json.decode(jsonObj["content_json"]);
} catch (e) {
print("jsonObj decode of event content failed: ${e.toString()}");
content = {};
}
Map<String, dynamic> content = jsonObj["content"];
if (content == null)
try {
content = json.decode(jsonObj["content_json"]);
} catch (e) {
print("jsonObj decode of event content failed: ${e.toString()}");
content = {};
}
return Event(
jsonObj["id"],
User.fromJson(jsonObj, room),

View File

@ -28,6 +28,7 @@ import 'package:famedlysdk/src/responses/ErrorResponse.dart';
import 'package:famedlysdk/src/sync/EventUpdate.dart';
import 'package:famedlysdk/src/Event.dart';
import './User.dart';
import 'Timeline.dart';
/// Represents a Matrix room.
class Room {
@ -83,14 +84,7 @@ class Room {
/// The needed power levels for all actions.
Map<String, int> powerLevels = {};
/// The list of events in this room. If the room is created by the
/// [getRoomList()] of the [Store], this will contain only the last event.
List<Event> events = [];
/// The list of participants in this room. If the room is created by the
/// [getRoomList()] of the [Store], this will contain only the sender of the
/// last event.
List<User> participants = [];
Event lastEvent;
/// Your current client instance.
final Client client;
@ -123,23 +117,22 @@ class Room {
this.historyVisibility,
this.joinRules,
this.powerLevels,
this.events,
this.participants,
this.lastEvent,
this.client,
});
/// The last message sent to this room.
String get lastMessage {
if (events != null && events.length > 0)
return events[0].getBody();
if (lastEvent != null)
return lastEvent.getBody();
else
return "";
}
/// When the last message received.
ChatTime get timeCreated {
if (events?.length > 0)
return events[0].time;
if (lastEvent != null)
return lastEvent.time;
else
return ChatTime.now();
}
@ -165,12 +158,6 @@ class Room {
return res;
}
@Deprecated("Use the client.connection streams instead!")
Stream<List<Event>> get eventsStream {
return Stream<List<Event>>.fromIterable(Iterable<List<Event>>.generate(
this.events.length, (int index) => this.events)).asBroadcastStream();
}
/// Call the Matrix API to send a simple text message.
Future<dynamic> sendText(String message, {String txid = null}) async {
if (txid == null) txid = "txid${DateTime.now().millisecondsSinceEpoch}";
@ -394,9 +381,8 @@ class Room {
"power_event_name": row["power_event_name"],
"power_event_power_levels": row["power_event_power_levels"],
},
lastEvent: Event.fromJson(row, null),
client: matrix,
events: [Event.fromJson(row, null)],
participants: [],
);
}
@ -413,26 +399,25 @@ class Room {
return room;
}
Future<Timeline> getTimeline({onUpdate, onInsert}) async {
List<Event> events = await loadEvents();
return Timeline(
room: this,
events: events,
onUpdate: onUpdate,
onInsert: onInsert,
);
}
/// Load all events for a given room from the store. This includes all
/// senders of those events, who will be added to the participants list.
Future<List<Event>> loadEvents() async {
this.events = await client.store.getEventList(this);
Map<String, bool> participantMap = {};
for (num i = 0; i < events.length; i++) {
if (!participantMap.containsKey(events[i].sender.mxid)) {
participants.add(events[i].sender);
participantMap[events[i].sender.mxid] = true;
}
}
return this.events;
return await client.store.getEventList(this);
}
/// Load all participants for a given room from the store.
Future<List<User>> loadParticipants() async {
this.participants = await client.store.loadParticipants(this);
return this.participants;
return await client.store.loadParticipants(this);
}
/// Request the full list of participants from the server. The local list
@ -454,8 +439,6 @@ class Room {
if (newUser.membership != "leave") participants.add(newUser);
}
this.participants = participants;
return this.participants;
return participants;
}
}

75
lib/src/Timeline.dart Normal file
View File

@ -0,0 +1,75 @@
/*
* Copyright (c) 2019 Zender & Kurtz GbR.
*
* Authors:
* Christian Pauly <krille@famedly.com>
* Marcel Radzio <mtrnord@famedly.com>
*
* This file is part of famedlysdk.
*
* famedlysdk is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* famedlysdk 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with famedlysdk. If not, see <http://www.gnu.org/licenses/>.
*/
import 'dart:async';
import 'Event.dart';
import 'Room.dart';
import 'User.dart';
import 'utils/ChatTime.dart';
import 'sync/EventUpdate.dart';
/// Represents the timeline of a room. The callbacks [onUpdate], [onDelete],
/// [onInsert] and [onResort] will be triggered automatically. The initial
/// event list will be retreived when created by the [room.getTimeline] method.
class Timeline {
final Room room;
List<Event> events = [];
final onUpdateCallback onUpdate;
final onInsertCallback onInsert;
StreamSubscription<EventUpdate> sub;
Timeline({this.room, this.events, this.onUpdate, this.onInsert}) {
sub ??= room.client.connection.onEvent.stream.listen(_handleEventUpdate);
}
void _handleEventUpdate(EventUpdate eventUpdate) async {
try {
if (eventUpdate.roomID != room.id) return;
if (eventUpdate.type == "timeline" || eventUpdate.type == "history") {
if (!eventUpdate.content.containsKey("id"))
eventUpdate.content["id"] = eventUpdate.content["event_id"];
User user = await room.client.store
?.getUser(matrixID: eventUpdate.content["sender"], room: room);
if (user != null) {
eventUpdate.content["displayname"] = user.displayName;
eventUpdate.content["avatar_url"] = user.avatarUrl.mxc;
}
Event newEvent = Event.fromJson(eventUpdate.content, room);
events.insert(0, newEvent);
onInsert(0);
}
onUpdate();
} catch (e) {
print("[WARNING] ${e.toString()}");
sub.cancel();
}
}
}
typedef onUpdateCallback = void Function();
typedef onInsertCallback = void Function(int insertID);

View File

@ -79,7 +79,7 @@ class User {
/// Creates a new User object from a json string like a row from the database.
static User fromJson(Map<String, dynamic> json, Room room) {
return User(json['matrix_id'],
return User(json['matrix_id'] ?? json['sender'],
displayName: json['displayname'],
avatarUrl: MxContent(json['avatar_url']),
membership: json['membership'],

View File

@ -25,7 +25,7 @@
/// already known event.
class EventUpdate {
/// Usually 'timeline', 'state' or whatever.
final String eventType;
final String type;
/// Most events belong to a room. If not, this equals to eventType.
final String roomID;
@ -33,7 +33,7 @@ class EventUpdate {
/// See (Matrix Room Events)[https://matrix.org/docs/spec/client_server/r0.4.0.html#room-events]
/// and (Matrix Events)[https://matrix.org/docs/spec/client_server/r0.4.0.html#id89] for more
/// informations.
final String type;
final String eventType;
// The json payload of the content of this event.
final dynamic content;

81
test/Timeline_test.dart Normal file
View File

@ -0,0 +1,81 @@
/*
* Copyright (c) 2019 Zender & Kurtz GbR.
*
* Authors:
* Christian Pauly <krille@famedly.com>
* Marcel Radzio <mtrnord@famedly.com>
*
* This file is part of famedlysdk.
*
* famedlysdk is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* famedlysdk 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with famedlysdk. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:flutter_test/flutter_test.dart';
import 'package:famedlysdk/src/Client.dart';
import 'package:famedlysdk/src/Room.dart';
import 'package:famedlysdk/src/Timeline.dart';
import 'package:famedlysdk/src/sync/EventUpdate.dart';
import 'package:famedlysdk/src/utils/ChatTime.dart';
void main() {
/// All Tests related to the MxContent
group("Timeline", () {
final String roomID = "!1234:example.com";
final testTimeStamp = ChatTime.now().toTimeStamp();
int updateCount = 0;
List<int> insertList = [];
test("Create", () async {
Client client = Client("testclient");
client.homeserver = "https://testserver.abc";
Room room = Room(id: roomID, client: client);
Timeline timeline = Timeline(
room: room,
events: [],
onUpdate: () {
updateCount++;
},
onInsert: (int insertID) {
insertList.add(insertID);
});
client.connection.onEvent.add(EventUpdate(
type: "timeline",
roomID: roomID,
eventType: "m.room.message",
content: {
"type": "m.room.message",
"content": {"msgtype": "m.text", "body": "Testcase"},
"sender": "@alice:example.com",
"status": 2,
"id": "1",
"origin_server_ts": testTimeStamp
}));
expect(timeline.sub != null, true);
await new Future.delayed(new Duration(milliseconds: 50));
expect(updateCount, 1);
expect(insertList, [0]);
expect(timeline.events.length, 1);
expect(timeline.events[0].id, "1");
expect(timeline.events[0].sender.id, "@alice:example.com");
expect(timeline.events[0].time.toTimeStamp(), testTimeStamp);
expect(timeline.events[0].environment, "m.room.message");
expect(timeline.events[0].getBody(), "Testcase");
});
});
}