famedlysdk/lib/src/RoomList.dart

227 lines
7.9 KiB
Dart

/*
* 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 'dart:core';
import 'package:famedlysdk/famedlysdk.dart';
import 'package:famedlysdk/src/RoomState.dart';
import 'Client.dart';
import 'Room.dart';
import 'User.dart';
import 'sync/EventUpdate.dart';
import 'sync/RoomUpdate.dart';
typedef onRoomListUpdateCallback = void Function();
typedef onRoomListInsertCallback = void Function(int insertID);
typedef onRoomListRemoveCallback = void Function(int insertID);
/// Represents a list of rooms for this client, which will automatically update
/// itself and call the [onUpdate], [onInsert] and [onDelete] callbacks. To get
/// the initial room list, use the store or create a RoomList instance by using
/// [client.getRoomList].
class RoomList {
final Client client;
List<Room> rooms = [];
final bool onlyLeft;
/// Will be called, when the room list has changed. Can be used e.g. to update
/// the state of a StatefulWidget.
final onRoomListUpdateCallback onUpdate;
/// Will be called, when a new room is added to the list.
final onRoomListInsertCallback onInsert;
/// Will be called, when a room has been removed from the list.
final onRoomListRemoveCallback onRemove;
StreamSubscription<EventUpdate> eventSub;
StreamSubscription<RoomUpdate> roomSub;
StreamSubscription<bool> firstSyncSub;
RoomList(
{this.client,
this.rooms,
this.onUpdate,
this.onInsert,
this.onRemove,
this.onlyLeft = false}) {
eventSub ??= client.connection.onEvent.stream.listen(_handleEventUpdate);
roomSub ??= client.connection.onRoomUpdate.stream.listen(_handleRoomUpdate);
firstSyncSub ??=
client.connection.onFirstSync.stream.listen((b) => sortAndUpdate());
sort();
}
RoomList copyWith({
Client client,
List<Room> rooms,
onRoomListUpdateCallback onUpdate,
onRoomListInsertCallback onInsert,
onRoomListRemoveCallback onRemove,
bool onlyLeft,
}) {
return RoomList(
client: client ?? this.client,
rooms: rooms ?? this.rooms,
onUpdate: onUpdate ?? this.onUpdate,
onInsert: onInsert ?? this.onInsert,
onRemove: onRemove ?? this.onRemove,
onlyLeft: onlyLeft ?? this.onlyLeft,
);
}
Room getRoomByAlias(String alias) {
for (int i = 0; i < rooms.length; i++) {
if (rooms[i].canonicalAlias == alias) return rooms[i];
}
return null;
}
Room getRoomById(String id) {
for (int j = 0; j < rooms.length; j++) {
if (rooms[j].id == id) return rooms[j];
}
return null;
}
void _handleRoomUpdate(RoomUpdate chatUpdate) {
// Update the chat list item.
// Search the room in the rooms
num j = 0;
for (j = 0; j < rooms.length; j++) {
if (rooms[j].id == chatUpdate.id) break;
}
final bool found = (j < rooms.length && rooms[j].id == chatUpdate.id);
final bool isLeftRoom = chatUpdate.membership == Membership.leave;
// Does the chat already exist in the list rooms?
if (!found && ((!onlyLeft && !isLeftRoom) || (onlyLeft && isLeftRoom))) {
num position = chatUpdate.membership == Membership.invite ? 0 : j;
// Add the new chat to the list
Room newRoom = Room(
id: chatUpdate.id,
membership: chatUpdate.membership,
prev_batch: chatUpdate.prev_batch,
highlightCount: chatUpdate.highlight_count,
notificationCount: chatUpdate.notification_count,
mHeroes: chatUpdate.summary?.mHeroes,
mJoinedMemberCount: chatUpdate.summary?.mJoinedMemberCount,
mInvitedMemberCount: chatUpdate.summary?.mInvitedMemberCount,
roomAccountData: {},
client: client,
);
rooms.insert(position, newRoom);
if (onInsert != null) onInsert(position);
}
// If the membership is "leave" or not "leave" but onlyLeft=true then remove the item and stop here
else if (found &&
((!onlyLeft && isLeftRoom) || (onlyLeft && !isLeftRoom))) {
rooms.removeAt(j);
if (onRemove != null) onRemove(j);
}
// Update notification, highlight count and/or additional informations
else if (found &&
chatUpdate.membership != Membership.leave &&
(rooms[j].membership != chatUpdate.membership ||
rooms[j].notificationCount != chatUpdate.notification_count ||
rooms[j].highlightCount != chatUpdate.highlight_count ||
chatUpdate.summary != null)) {
rooms[j].membership = chatUpdate.membership;
rooms[j].notificationCount = chatUpdate.notification_count;
rooms[j].highlightCount = chatUpdate.highlight_count;
if (chatUpdate.prev_batch != null)
rooms[j].prev_batch = chatUpdate.prev_batch;
if (chatUpdate.summary != null) {
if (chatUpdate.summary.mHeroes != null)
rooms[j].mHeroes = chatUpdate.summary.mHeroes;
if (chatUpdate.summary.mJoinedMemberCount != null)
rooms[j].mJoinedMemberCount = chatUpdate.summary.mJoinedMemberCount;
if (chatUpdate.summary.mInvitedMemberCount != null)
rooms[j].mInvitedMemberCount = chatUpdate.summary.mInvitedMemberCount;
}
if (rooms[j].onUpdate != null) rooms[j].onUpdate();
}
sortAndUpdate();
}
void _handleEventUpdate(EventUpdate eventUpdate) {
if (eventUpdate.type == "history") return;
// Search the room in the rooms
num j = 0;
for (j = 0; j < rooms.length; j++) {
if (rooms[j].id == eventUpdate.roomID) break;
}
final bool found = (j < rooms.length && rooms[j].id == eventUpdate.roomID);
if (!found) return;
if (eventUpdate.type == "timeline" ||
eventUpdate.type == "state" ||
eventUpdate.type == "invite_state") {
RoomState stateEvent = RoomState.fromJson(eventUpdate.content, rooms[j]);
if (stateEvent.type == EventTypes.Redaction) {
final String redacts = eventUpdate.content["redacts"];
rooms[j].states.states.forEach(
(String key, Map<String, RoomState> states) => states.forEach(
(String key, RoomState state) {
if (state.eventId == redacts) {
state.setRedactionEvent(stateEvent);
}
},
),
);
} else {
RoomState prevState =
rooms[j].getState(stateEvent.typeKey, stateEvent.stateKey);
if (prevState != null && prevState.time > stateEvent.time) return;
rooms[j].setState(stateEvent);
}
} else if (eventUpdate.type == "account_data") {
rooms[j].roomAccountData[eventUpdate.eventType] =
RoomAccountData.fromJson(eventUpdate.content, rooms[j]);
} else if (eventUpdate.type == "ephemeral") {
rooms[j].ephemerals[eventUpdate.eventType] =
RoomAccountData.fromJson(eventUpdate.content, rooms[j]);
}
if (rooms[j].onUpdate != null) rooms[j].onUpdate();
if (eventUpdate.type == "timeline") sortAndUpdate();
}
bool sortLock = false;
sort() {
if (sortLock || rooms.length < 2) return;
sortLock = true;
rooms?.sort((a, b) =>
b.timeCreated.toTimeStamp().compareTo(a.timeCreated.toTimeStamp()));
sortLock = false;
}
sortAndUpdate() {
if (client.prevBatch == null) return;
sort();
if (onUpdate != null) onUpdate();
}
}