Merge branch 'store-enhance-usestatestore' into 'master'
[Store] Switch to a state focused store See merge request famedly/famedlysdk!67
This commit is contained in:
commit
902df33d50
|
@ -35,6 +35,7 @@ export 'package:famedlysdk/src/Connection.dart';
|
||||||
export 'package:famedlysdk/src/Event.dart';
|
export 'package:famedlysdk/src/Event.dart';
|
||||||
export 'package:famedlysdk/src/Room.dart';
|
export 'package:famedlysdk/src/Room.dart';
|
||||||
export 'package:famedlysdk/src/RoomList.dart';
|
export 'package:famedlysdk/src/RoomList.dart';
|
||||||
|
export 'package:famedlysdk/src/RoomState.dart';
|
||||||
export 'package:famedlysdk/src/Store.dart';
|
export 'package:famedlysdk/src/Store.dart';
|
||||||
export 'package:famedlysdk/src/Timeline.dart';
|
export 'package:famedlysdk/src/Timeline.dart';
|
||||||
export 'package:famedlysdk/src/User.dart';
|
export 'package:famedlysdk/src/User.dart';
|
||||||
|
|
41
lib/src/AccountData.dart
Normal file
41
lib/src/AccountData.dart
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
/*
|
||||||
|
* 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:famedlysdk/src/RoomState.dart';
|
||||||
|
|
||||||
|
class AccountData {
|
||||||
|
/// The json payload of the content. The content highly depends on the type.
|
||||||
|
final Map<String, dynamic> content;
|
||||||
|
|
||||||
|
/// The type String of this event. For example 'm.room.message'.
|
||||||
|
final String typeKey;
|
||||||
|
|
||||||
|
AccountData({this.content, this.typeKey});
|
||||||
|
|
||||||
|
/// Get a State event from a table row or from the event stream.
|
||||||
|
factory AccountData.fromJson(Map<String, dynamic> jsonPayload) {
|
||||||
|
final Map<String, dynamic> content =
|
||||||
|
RoomState.getMapFromPayload(jsonPayload['content']);
|
||||||
|
return AccountData(content: content, typeKey: jsonPayload['type']);
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,6 +24,10 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:core';
|
import 'dart:core';
|
||||||
|
|
||||||
|
import 'package:famedlysdk/src/AccountData.dart';
|
||||||
|
import 'package:famedlysdk/src/Presence.dart';
|
||||||
|
import 'package:famedlysdk/src/sync/UserUpdate.dart';
|
||||||
|
|
||||||
import 'Connection.dart';
|
import 'Connection.dart';
|
||||||
import 'Room.dart';
|
import 'Room.dart';
|
||||||
import 'RoomList.dart';
|
import 'RoomList.dart';
|
||||||
|
@ -33,6 +37,9 @@ import 'requests/SetPushersRequest.dart';
|
||||||
import 'responses/ErrorResponse.dart';
|
import 'responses/ErrorResponse.dart';
|
||||||
import 'responses/PushrulesResponse.dart';
|
import 'responses/PushrulesResponse.dart';
|
||||||
|
|
||||||
|
typedef AccountDataEventCB = void Function(AccountData accountData);
|
||||||
|
typedef PresenceCB = void Function(Presence presence);
|
||||||
|
|
||||||
/// Represents a Matrix client to communicate with a
|
/// Represents a Matrix client to communicate with a
|
||||||
/// [Matrix](https://matrix.org) homeserver and is the entry point for this
|
/// [Matrix](https://matrix.org) homeserver and is the entry point for this
|
||||||
/// SDK.
|
/// SDK.
|
||||||
|
@ -86,6 +93,56 @@ class Client {
|
||||||
/// Returns the current login state.
|
/// Returns the current login state.
|
||||||
bool isLogged() => accessToken != null;
|
bool isLogged() => accessToken != null;
|
||||||
|
|
||||||
|
/// A list of all rooms the user is participating or invited.
|
||||||
|
RoomList roomList;
|
||||||
|
|
||||||
|
/// Key/Value store of account data.
|
||||||
|
Map<String, AccountData> accountData = {};
|
||||||
|
|
||||||
|
/// Presences of users by a given matrix ID
|
||||||
|
Map<String, Presence> presences = {};
|
||||||
|
|
||||||
|
/// Callback will be called on account data updates.
|
||||||
|
AccountDataEventCB onAccountData;
|
||||||
|
|
||||||
|
/// Callback will be called on presences.
|
||||||
|
PresenceCB onPresence;
|
||||||
|
|
||||||
|
void handleUserUpdate(UserUpdate userUpdate) {
|
||||||
|
if (userUpdate.type == "account_data") {
|
||||||
|
AccountData newAccountData = AccountData.fromJson(userUpdate.content);
|
||||||
|
accountData[newAccountData.typeKey] = newAccountData;
|
||||||
|
if (onAccountData != null) onAccountData(newAccountData);
|
||||||
|
}
|
||||||
|
if (userUpdate.type == "presence") {
|
||||||
|
Presence newPresence = Presence.fromJson(userUpdate.content);
|
||||||
|
presences[newPresence.sender] = newPresence;
|
||||||
|
if (onPresence != null) onPresence(newPresence);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> get directChats =>
|
||||||
|
accountData["m.direct"] != null ? accountData["m.direct"].content : {};
|
||||||
|
|
||||||
|
/// Returns the (first) room ID from the store which is a private chat with the user [userId].
|
||||||
|
/// Returns null if there is none.
|
||||||
|
String getDirectChatFromUserId(String userId) {
|
||||||
|
if (accountData["m.direct"] != null &&
|
||||||
|
accountData["m.direct"].content[userId] is List<dynamic> &&
|
||||||
|
accountData["m.direct"].content[userId].length > 0) {
|
||||||
|
if (roomList.getRoomById(accountData["m.direct"].content[userId][0]) !=
|
||||||
|
null) return accountData["m.direct"].content[userId][0];
|
||||||
|
(accountData["m.direct"].content[userId] as List<dynamic>)
|
||||||
|
.remove(accountData["m.direct"].content[userId][0]);
|
||||||
|
connection.jsonRequest(
|
||||||
|
type: HTTPType.PUT,
|
||||||
|
action: "/client/r0/user/${userID}/account_data/m.direct",
|
||||||
|
data: directChats);
|
||||||
|
return getDirectChatFromUserId(userId);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/// Checks the supported versions of the Matrix protocol and the supported
|
/// Checks the supported versions of the Matrix protocol and the supported
|
||||||
/// login types. Returns false if the server is not compatible with the
|
/// login types. Returns false if the server is not compatible with the
|
||||||
/// client. Automatically sets [matrixVersions] and [lazyLoadMembers].
|
/// client. Automatically sets [matrixVersions] and [lazyLoadMembers].
|
||||||
|
@ -225,12 +282,12 @@ class Client {
|
||||||
/// defined by the autojoin room feature in Synapse.
|
/// defined by the autojoin room feature in Synapse.
|
||||||
Future<List<User>> loadFamedlyContacts() async {
|
Future<List<User>> loadFamedlyContacts() async {
|
||||||
List<User> contacts = [];
|
List<User> contacts = [];
|
||||||
Room contactDiscoveryRoom = await store
|
Room contactDiscoveryRoom = roomList
|
||||||
.getRoomByAlias("#famedlyContactDiscovery:${userID.split(":")[1]}");
|
.getRoomByAlias("#famedlyContactDiscovery:${userID.split(":")[1]}");
|
||||||
if (contactDiscoveryRoom != null)
|
if (contactDiscoveryRoom != null)
|
||||||
contacts = await contactDiscoveryRoom.requestParticipants();
|
contacts = await contactDiscoveryRoom.requestParticipants();
|
||||||
else
|
else
|
||||||
contacts = await store.loadContacts();
|
contacts = await store?.loadContacts();
|
||||||
return contacts;
|
return contacts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,8 @@ import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:core';
|
import 'dart:core';
|
||||||
|
|
||||||
|
import 'package:famedlysdk/src/Room.dart';
|
||||||
|
import 'package:famedlysdk/src/RoomList.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
|
|
||||||
|
@ -50,9 +52,9 @@ class Connection {
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
String get _syncFilters => '{"room":{"state":{"lazy_load_members":true}}}';
|
static String syncFilters = '{"room":{"state":{"lazy_load_members":true}}}';
|
||||||
|
|
||||||
String get _firstSyncFilters =>
|
static String firstSyncFilters =
|
||||||
'{"room":{"include_leave":true,"state":{"lazy_load_members":true}}}';
|
'{"room":{"include_leave":true,"state":{"lazy_load_members":true}}}';
|
||||||
|
|
||||||
/// Handles the connection to the Matrix Homeserver. You can change this to a
|
/// Handles the connection to the Matrix Homeserver. You can change this to a
|
||||||
|
@ -147,13 +149,34 @@ class Connection {
|
||||||
client.lazyLoadMembers = newLazyLoadMembers;
|
client.lazyLoadMembers = newLazyLoadMembers;
|
||||||
client.prevBatch = newPrevBatch;
|
client.prevBatch = newPrevBatch;
|
||||||
|
|
||||||
client.store?.storeClient();
|
List<Room> rooms = [];
|
||||||
|
if (client.store != null) {
|
||||||
|
client.store.storeClient();
|
||||||
|
rooms = await client.store
|
||||||
|
.getRoomList(onlyLeft: false, onlyGroups: false, onlyDirect: false);
|
||||||
|
client.accountData = await client.store.getAccountData();
|
||||||
|
client.presences = await client.store.getPresences();
|
||||||
|
}
|
||||||
|
|
||||||
|
client.roomList = RoomList(
|
||||||
|
client: client,
|
||||||
|
onlyLeft: false,
|
||||||
|
onlyDirect: false,
|
||||||
|
onlyGroups: false,
|
||||||
|
onUpdate: null,
|
||||||
|
onInsert: null,
|
||||||
|
onRemove: null,
|
||||||
|
rooms: rooms);
|
||||||
|
|
||||||
|
_userEventSub ??= onUserEvent.stream.listen(client.handleUserUpdate);
|
||||||
|
|
||||||
onLoginStateChanged.add(LoginState.logged);
|
onLoginStateChanged.add(LoginState.logged);
|
||||||
|
|
||||||
_sync();
|
_sync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
StreamSubscription _userEventSub;
|
||||||
|
|
||||||
/// Resets all settings and stops the synchronisation.
|
/// Resets all settings and stops the synchronisation.
|
||||||
void clear() {
|
void clear() {
|
||||||
client.store?.clear();
|
client.store?.clear();
|
||||||
|
@ -261,10 +284,10 @@ class Connection {
|
||||||
Future<void> _sync() async {
|
Future<void> _sync() async {
|
||||||
if (client.isLogged() == false) return;
|
if (client.isLogged() == false) return;
|
||||||
|
|
||||||
String action = "/client/r0/sync?filter=$_firstSyncFilters";
|
String action = "/client/r0/sync?filter=$firstSyncFilters";
|
||||||
|
|
||||||
if (client.prevBatch != null) {
|
if (client.prevBatch != null) {
|
||||||
action = "/client/r0/sync?filter=$_syncFilters";
|
action = "/client/r0/sync?filter=$syncFilters";
|
||||||
action += "&timeout=30000";
|
action += "&timeout=30000";
|
||||||
action += "&since=${client.prevBatch}";
|
action += "&since=${client.prevBatch}";
|
||||||
}
|
}
|
||||||
|
@ -450,6 +473,8 @@ class Connection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
typedef _FutureVoidCallback = Future<void> Function();
|
||||||
|
|
||||||
class _LifecycleEventHandler extends WidgetsBindingObserver {
|
class _LifecycleEventHandler extends WidgetsBindingObserver {
|
||||||
_LifecycleEventHandler({this.resumeCallBack, this.suspendingCallBack});
|
_LifecycleEventHandler({this.resumeCallBack, this.suspendingCallBack});
|
||||||
|
|
||||||
|
@ -471,6 +496,4 @@ class _LifecycleEventHandler extends WidgetsBindingObserver {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef _FutureVoidCallback = Future<void> Function();
|
|
||||||
|
|
||||||
enum LoginState { logged, loggedOut }
|
enum LoginState { logged, loggedOut }
|
||||||
|
|
|
@ -21,37 +21,14 @@
|
||||||
* along with famedlysdk. If not, see <http://www.gnu.org/licenses/>.
|
* along with famedlysdk. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import 'dart:convert';
|
import 'package:famedlysdk/src/RoomState.dart';
|
||||||
|
|
||||||
import 'package:famedlysdk/src/Client.dart';
|
|
||||||
import 'package:famedlysdk/src/sync/EventUpdate.dart';
|
import 'package:famedlysdk/src/sync/EventUpdate.dart';
|
||||||
import 'package:famedlysdk/src/utils/ChatTime.dart';
|
import 'package:famedlysdk/src/utils/ChatTime.dart';
|
||||||
|
|
||||||
import './Room.dart';
|
import './Room.dart';
|
||||||
import './User.dart';
|
|
||||||
|
|
||||||
/// A single Matrix event, e.g. a message in a chat.
|
|
||||||
class Event {
|
|
||||||
/// The Matrix ID for this event in the format '$localpart:server.abc'.
|
|
||||||
final String id;
|
|
||||||
|
|
||||||
/// The room this event belongs to.
|
|
||||||
final Room room;
|
|
||||||
|
|
||||||
/// The time this event has received at the server.
|
|
||||||
final ChatTime time;
|
|
||||||
|
|
||||||
/// The user who has sent this event.
|
|
||||||
final User sender;
|
|
||||||
|
|
||||||
/// The user who is the target of this event e.g. for a m.room.member event.
|
|
||||||
final User stateKey;
|
|
||||||
|
|
||||||
/// The type of this event. Mostly this is 'timeline'.
|
|
||||||
final String environment;
|
|
||||||
|
|
||||||
Event replyEvent;
|
|
||||||
|
|
||||||
|
/// Defines a timeline event for a room.
|
||||||
|
class Event extends RoomState {
|
||||||
/// The status of this event.
|
/// The status of this event.
|
||||||
/// -1=ERROR
|
/// -1=ERROR
|
||||||
/// 0=SENDING
|
/// 0=SENDING
|
||||||
|
@ -59,20 +36,53 @@ class Event {
|
||||||
/// 2=RECEIVED
|
/// 2=RECEIVED
|
||||||
int status;
|
int status;
|
||||||
|
|
||||||
/// The json payload of the content. The content highly depends on the type.
|
static const int defaultStatus = 2;
|
||||||
final Map<String, dynamic> content;
|
|
||||||
|
|
||||||
Event(
|
Event(
|
||||||
this.id,
|
{this.status = defaultStatus,
|
||||||
this.sender,
|
dynamic content,
|
||||||
this.time, {
|
String typeKey,
|
||||||
this.room,
|
String eventId,
|
||||||
this.stateKey,
|
String roomId,
|
||||||
this.status = 2,
|
String senderId,
|
||||||
this.environment,
|
ChatTime time,
|
||||||
this.content,
|
dynamic unsigned,
|
||||||
this.replyEvent,
|
dynamic prevContent,
|
||||||
});
|
String stateKey,
|
||||||
|
Room room})
|
||||||
|
: super(
|
||||||
|
content: content,
|
||||||
|
typeKey: typeKey,
|
||||||
|
eventId: eventId,
|
||||||
|
roomId: roomId,
|
||||||
|
senderId: senderId,
|
||||||
|
time: time,
|
||||||
|
unsigned: unsigned,
|
||||||
|
prevContent: prevContent,
|
||||||
|
stateKey: stateKey,
|
||||||
|
room: room);
|
||||||
|
|
||||||
|
/// Get a State event from a table row or from the event stream.
|
||||||
|
factory Event.fromJson(Map<String, dynamic> jsonPayload, Room room) {
|
||||||
|
final Map<String, dynamic> content =
|
||||||
|
RoomState.getMapFromPayload(jsonPayload['content']);
|
||||||
|
final Map<String, dynamic> unsigned =
|
||||||
|
RoomState.getMapFromPayload(jsonPayload['unsigned']);
|
||||||
|
final Map<String, dynamic> prevContent =
|
||||||
|
RoomState.getMapFromPayload(jsonPayload['prev_content']);
|
||||||
|
return Event(
|
||||||
|
status: jsonPayload['status'] ?? defaultStatus,
|
||||||
|
content: content,
|
||||||
|
typeKey: jsonPayload['type'],
|
||||||
|
eventId: jsonPayload['event_id'],
|
||||||
|
roomId: jsonPayload['room_id'],
|
||||||
|
senderId: jsonPayload['sender'],
|
||||||
|
time: ChatTime(jsonPayload['origin_server_ts']),
|
||||||
|
unsigned: unsigned,
|
||||||
|
prevContent: prevContent,
|
||||||
|
stateKey: jsonPayload['state_key'],
|
||||||
|
room: room);
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the body of this event if it has a body.
|
/// Returns the body of this event if it has a body.
|
||||||
String get text => content["body"] ?? "";
|
String get text => content["body"] ?? "";
|
||||||
|
@ -84,89 +94,7 @@ class Event {
|
||||||
String getBody() {
|
String getBody() {
|
||||||
if (text != "") return text;
|
if (text != "") return text;
|
||||||
if (formattedText != "") return formattedText;
|
if (formattedText != "") return formattedText;
|
||||||
return "*** Unable to parse Content ***";
|
return "$type";
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the real type.
|
|
||||||
EventTypes get type {
|
|
||||||
switch (environment) {
|
|
||||||
case "m.room.avatar":
|
|
||||||
return EventTypes.RoomAvatar;
|
|
||||||
case "m.room.name":
|
|
||||||
return EventTypes.RoomName;
|
|
||||||
case "m.room.topic":
|
|
||||||
return EventTypes.RoomTopic;
|
|
||||||
case "m.room.Aliases":
|
|
||||||
return EventTypes.RoomAliases;
|
|
||||||
case "m.room.canonical_alias":
|
|
||||||
return EventTypes.RoomCanonicalAlias;
|
|
||||||
case "m.room.create":
|
|
||||||
return EventTypes.RoomCreate;
|
|
||||||
case "m.room.join_rules":
|
|
||||||
return EventTypes.RoomJoinRules;
|
|
||||||
case "m.room.member":
|
|
||||||
return EventTypes.RoomMember;
|
|
||||||
case "m.room.power_levels":
|
|
||||||
return EventTypes.RoomPowerLevels;
|
|
||||||
case "m.room.guest_access":
|
|
||||||
return EventTypes.GuestAccess;
|
|
||||||
case "m.room.history_visibility":
|
|
||||||
return EventTypes.HistoryVisibility;
|
|
||||||
case "m.room.message":
|
|
||||||
switch (content["msgtype"] ?? "m.text") {
|
|
||||||
case "m.text":
|
|
||||||
if (content.containsKey("m.relates_to")) {
|
|
||||||
return EventTypes.Reply;
|
|
||||||
}
|
|
||||||
return EventTypes.Text;
|
|
||||||
case "m.notice":
|
|
||||||
return EventTypes.Notice;
|
|
||||||
case "m.emote":
|
|
||||||
return EventTypes.Emote;
|
|
||||||
case "m.image":
|
|
||||||
return EventTypes.Image;
|
|
||||||
case "m.video":
|
|
||||||
return EventTypes.Video;
|
|
||||||
case "m.audio":
|
|
||||||
return EventTypes.Audio;
|
|
||||||
case "m.file":
|
|
||||||
return EventTypes.File;
|
|
||||||
case "m.location":
|
|
||||||
return EventTypes.Location;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return EventTypes.Unknown;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Generate a new Event object from a json string, mostly a table row.
|
|
||||||
static Event fromJson(Map<String, dynamic> jsonObj, Room room,
|
|
||||||
{User senderUser, User stateKeyUser}) {
|
|
||||||
Map<String, dynamic> content = jsonObj["content"];
|
|
||||||
|
|
||||||
if (content == null && jsonObj["content_json"] != null)
|
|
||||||
try {
|
|
||||||
content = json.decode(jsonObj["content_json"]);
|
|
||||||
} catch (e) {
|
|
||||||
if (room.client.debug) {
|
|
||||||
print("jsonObj decode of event content failed: ${e.toString()}");
|
|
||||||
}
|
|
||||||
content = {};
|
|
||||||
}
|
|
||||||
else if (content == null) content = {};
|
|
||||||
|
|
||||||
if (senderUser == null) senderUser = User.fromJson(jsonObj, room);
|
|
||||||
if (stateKeyUser == null) stateKeyUser = User(jsonObj["state_key"]);
|
|
||||||
|
|
||||||
return Event(
|
|
||||||
jsonObj["event_id"] ?? jsonObj["id"],
|
|
||||||
senderUser,
|
|
||||||
ChatTime(jsonObj["origin_server_ts"]),
|
|
||||||
stateKey: stateKeyUser,
|
|
||||||
environment: jsonObj["type"],
|
|
||||||
status: jsonObj["status"] ?? 2,
|
|
||||||
content: content,
|
|
||||||
room: room,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Removes this event if the status is < 1. This event will just be removed
|
/// Removes this event if the status is < 1. This event will just be removed
|
||||||
|
@ -175,14 +103,14 @@ class Event {
|
||||||
if (status < 1) {
|
if (status < 1) {
|
||||||
if (room.client.store != null)
|
if (room.client.store != null)
|
||||||
await room.client.store.db
|
await room.client.store.db
|
||||||
.rawDelete("DELETE FROM Events WHERE id=?", [id]);
|
.rawDelete("DELETE FROM Events WHERE id=?", [eventId]);
|
||||||
|
|
||||||
room.client.connection.onEvent.add(EventUpdate(
|
room.client.connection.onEvent.add(EventUpdate(
|
||||||
roomID: room.id,
|
roomID: room.id,
|
||||||
type: "timeline",
|
type: "timeline",
|
||||||
eventType: environment,
|
eventType: typeKey,
|
||||||
content: {
|
content: {
|
||||||
"event_id": id,
|
"event_id": eventId,
|
||||||
"status": -2,
|
"status": -2,
|
||||||
"content": {"body": "Removed..."}
|
"content": {"body": "Removed..."}
|
||||||
}));
|
}));
|
||||||
|
@ -198,42 +126,4 @@ class Event {
|
||||||
final String eventID = await room.sendTextEvent(text, txid: txid);
|
final String eventID = await room.sendTextEvent(text, txid: txid);
|
||||||
return eventID;
|
return eventID;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated("Use [client.store.getEventList(Room room)] instead!")
|
|
||||||
static Future<List<Event>> getEventList(Client matrix, Room room) async {
|
|
||||||
List<Event> eventList = await matrix.store.getEventList(room);
|
|
||||||
return eventList;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
enum EventTypes {
|
|
||||||
Text,
|
|
||||||
Emote,
|
|
||||||
Notice,
|
|
||||||
Image,
|
|
||||||
Video,
|
|
||||||
Audio,
|
|
||||||
File,
|
|
||||||
Location,
|
|
||||||
Reply,
|
|
||||||
RoomAliases,
|
|
||||||
RoomCanonicalAlias,
|
|
||||||
RoomCreate,
|
|
||||||
RoomJoinRules,
|
|
||||||
RoomMember,
|
|
||||||
RoomPowerLevels,
|
|
||||||
RoomName,
|
|
||||||
RoomTopic,
|
|
||||||
RoomAvatar,
|
|
||||||
GuestAccess,
|
|
||||||
HistoryVisibility,
|
|
||||||
Unknown,
|
|
||||||
}
|
|
||||||
|
|
||||||
final Map<String, int> StatusTypes = {
|
|
||||||
"REMOVE": -2,
|
|
||||||
"ERROR": -1,
|
|
||||||
"SENDING": 0,
|
|
||||||
"SENT": 1,
|
|
||||||
"RECEIVED": 2,
|
|
||||||
};
|
|
||||||
|
|
43
lib/src/Presence.dart
Normal file
43
lib/src/Presence.dart
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
/*
|
||||||
|
* 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:famedlysdk/src/AccountData.dart';
|
||||||
|
import 'package:famedlysdk/src/RoomState.dart';
|
||||||
|
|
||||||
|
class Presence extends AccountData {
|
||||||
|
/// The user who has sent this event if it is not a global account data event.
|
||||||
|
final String sender;
|
||||||
|
|
||||||
|
Presence({this.sender, Map<String, dynamic> content, String typeKey})
|
||||||
|
: super(content: content, typeKey: typeKey);
|
||||||
|
|
||||||
|
/// Get a State event from a table row or from the event stream.
|
||||||
|
factory Presence.fromJson(Map<String, dynamic> jsonPayload) {
|
||||||
|
final Map<String, dynamic> content =
|
||||||
|
RoomState.getMapFromPayload(jsonPayload['content']);
|
||||||
|
return Presence(
|
||||||
|
content: content,
|
||||||
|
typeKey: jsonPayload['type'],
|
||||||
|
sender: jsonPayload['sender']);
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,6 +23,8 @@
|
||||||
|
|
||||||
import 'package:famedlysdk/src/Client.dart';
|
import 'package:famedlysdk/src/Client.dart';
|
||||||
import 'package:famedlysdk/src/Event.dart';
|
import 'package:famedlysdk/src/Event.dart';
|
||||||
|
import 'package:famedlysdk/src/RoomAccountData.dart';
|
||||||
|
import 'package:famedlysdk/src/RoomState.dart';
|
||||||
import 'package:famedlysdk/src/responses/ErrorResponse.dart';
|
import 'package:famedlysdk/src/responses/ErrorResponse.dart';
|
||||||
import 'package:famedlysdk/src/sync/EventUpdate.dart';
|
import 'package:famedlysdk/src/sync/EventUpdate.dart';
|
||||||
import 'package:famedlysdk/src/utils/ChatTime.dart';
|
import 'package:famedlysdk/src/utils/ChatTime.dart';
|
||||||
|
@ -40,15 +42,6 @@ class Room {
|
||||||
/// Membership status of the user for this room.
|
/// Membership status of the user for this room.
|
||||||
Membership membership;
|
Membership membership;
|
||||||
|
|
||||||
/// The name of the room if set by a participant.
|
|
||||||
String name;
|
|
||||||
|
|
||||||
/// The topic of the room if set by a participant.
|
|
||||||
String topic;
|
|
||||||
|
|
||||||
/// The avatar of the room if set by a participant.
|
|
||||||
MxContent avatar = MxContent("");
|
|
||||||
|
|
||||||
/// The count of unread notifications.
|
/// The count of unread notifications.
|
||||||
int notificationCount;
|
int notificationCount;
|
||||||
|
|
||||||
|
@ -57,7 +50,13 @@ class Room {
|
||||||
|
|
||||||
String prev_batch;
|
String prev_batch;
|
||||||
|
|
||||||
String draft;
|
List<String> mHeroes = [];
|
||||||
|
int mJoinedMemberCount;
|
||||||
|
int mInvitedMemberCount;
|
||||||
|
|
||||||
|
Map<String, RoomState> states = {};
|
||||||
|
|
||||||
|
Map<String, RoomAccountData> roomAccountData = {};
|
||||||
|
|
||||||
/// Time when the user has last read the chat.
|
/// Time when the user has last read the chat.
|
||||||
ChatTime unread;
|
ChatTime unread;
|
||||||
|
@ -65,69 +64,97 @@ class Room {
|
||||||
/// ID of the fully read marker event.
|
/// ID of the fully read marker event.
|
||||||
String fullyRead;
|
String fullyRead;
|
||||||
|
|
||||||
/// The address in the format: #roomname:homeserver.org.
|
/// The name of the room if set by a participant.
|
||||||
String canonicalAlias;
|
String get name {
|
||||||
|
if (states["m.room.name"] != null &&
|
||||||
|
!states["m.room.name"].content["name"].isEmpty)
|
||||||
|
return states["m.room.name"].content["name"];
|
||||||
|
if (canonicalAlias != null && !canonicalAlias.isEmpty)
|
||||||
|
return canonicalAlias.substring(1, canonicalAlias.length).split(":")[0];
|
||||||
|
if (mHeroes != null && mHeroes.length > 0) {
|
||||||
|
String displayname = "";
|
||||||
|
for (int i = 0; i < mHeroes.length; i++) {
|
||||||
|
User hero = states[mHeroes[i]] != null
|
||||||
|
? states[mHeroes[i]].asUser
|
||||||
|
: User(mHeroes[i]);
|
||||||
|
displayname += hero.calcDisplayname() + ", ";
|
||||||
|
}
|
||||||
|
return displayname.substring(0, displayname.length - 2);
|
||||||
|
}
|
||||||
|
return "Empty chat";
|
||||||
|
}
|
||||||
|
|
||||||
/// If this room is a direct chat, this is the matrix ID of the user
|
/// The topic of the room if set by a participant.
|
||||||
String directChatMatrixID;
|
String get topic => states["m.room.topic"] != null
|
||||||
|
? states["m.room.topic"].content["topic"]
|
||||||
|
: "";
|
||||||
|
|
||||||
|
/// The avatar of the room if set by a participant.
|
||||||
|
MxContent get avatar {
|
||||||
|
if (states["m.room.avatar"] != null)
|
||||||
|
return MxContent(states["m.room.avatar"].content["url"]);
|
||||||
|
if (mHeroes != null && mHeroes.length == 1 && states[mHeroes[0]] != null)
|
||||||
|
return states[mHeroes[0]].asUser.avatarUrl;
|
||||||
|
return MxContent("");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The address in the format: #roomname:homeserver.org.
|
||||||
|
String get canonicalAlias => states["m.room.canonical_alias"] != null
|
||||||
|
? states["m.room.canonical_alias"].content["alias"]
|
||||||
|
: "";
|
||||||
|
|
||||||
|
/// If this room is a direct chat, this is the matrix ID of the user.
|
||||||
|
/// Returns null otherwise.
|
||||||
|
String get directChatMatrixID {
|
||||||
|
String returnUserId = null;
|
||||||
|
if (client.directChats is Map<String, dynamic>) {
|
||||||
|
client.directChats.forEach((String userId, dynamic roomIds) {
|
||||||
|
if (roomIds is List<dynamic>) {
|
||||||
|
for (int i = 0; i < roomIds.length; i++)
|
||||||
|
if (roomIds[i] == this.id) {
|
||||||
|
returnUserId = userId;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return returnUserId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wheither this is a direct chat or not
|
||||||
|
bool get isDirectChat => directChatMatrixID != null;
|
||||||
|
|
||||||
/// Must be one of [all, mention]
|
/// Must be one of [all, mention]
|
||||||
String notificationSettings;
|
String notificationSettings;
|
||||||
|
|
||||||
/// Are guest users allowed?
|
Event get lastEvent {
|
||||||
String guestAccess;
|
ChatTime lastTime = ChatTime(0);
|
||||||
|
Event lastEvent = null;
|
||||||
/// Who can see the history of this room?
|
states.forEach((String key, RoomState state) {
|
||||||
String historyVisibility;
|
if (state.time != null && state.time > lastTime) {
|
||||||
|
lastTime = state.time;
|
||||||
/// Who is allowed to join this room?
|
lastEvent = state.timelineEvent;
|
||||||
String joinRules;
|
}
|
||||||
|
});
|
||||||
/// The needed power levels for all actions.
|
return lastEvent;
|
||||||
Map<String, int> powerLevels = {};
|
}
|
||||||
|
|
||||||
List<String> mHeroes;
|
|
||||||
int mJoinedMemberCount;
|
|
||||||
int mInvitedMemberCount;
|
|
||||||
|
|
||||||
Event lastEvent;
|
|
||||||
|
|
||||||
/// Your current client instance.
|
/// Your current client instance.
|
||||||
final Client client;
|
final Client client;
|
||||||
|
|
||||||
@Deprecated("Rooms.roomID is deprecated! Use Rooms.id instead!")
|
|
||||||
String get roomID => this.id;
|
|
||||||
|
|
||||||
@Deprecated("Rooms.matrix is deprecated! Use Rooms.client instead!")
|
|
||||||
Client get matrix => this.client;
|
|
||||||
|
|
||||||
@Deprecated("Rooms.status is deprecated! Use Rooms.membership instead!")
|
|
||||||
String get status => this.membership.toString().split('.').last;
|
|
||||||
|
|
||||||
Room({
|
Room({
|
||||||
this.id,
|
this.id,
|
||||||
this.membership,
|
this.membership = Membership.join,
|
||||||
this.name,
|
this.notificationCount = 0,
|
||||||
this.topic,
|
this.highlightCount = 0,
|
||||||
this.avatar,
|
|
||||||
this.notificationCount,
|
|
||||||
this.highlightCount,
|
|
||||||
this.prev_batch = "",
|
this.prev_batch = "",
|
||||||
this.draft,
|
|
||||||
this.unread,
|
|
||||||
this.fullyRead,
|
|
||||||
this.canonicalAlias,
|
|
||||||
this.directChatMatrixID,
|
|
||||||
this.notificationSettings,
|
|
||||||
this.guestAccess,
|
|
||||||
this.historyVisibility,
|
|
||||||
this.joinRules,
|
|
||||||
this.powerLevels,
|
|
||||||
this.lastEvent,
|
|
||||||
this.client,
|
this.client,
|
||||||
this.mHeroes,
|
this.notificationSettings,
|
||||||
this.mInvitedMemberCount,
|
this.mHeroes = const [],
|
||||||
this.mJoinedMemberCount,
|
this.mInvitedMemberCount = 0,
|
||||||
|
this.mJoinedMemberCount = 0,
|
||||||
|
this.states = const {},
|
||||||
|
this.roomAccountData = const {},
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Calculates the displayname. First checks if there is a name, then checks for a canonical alias and
|
/// Calculates the displayname. First checks if there is a name, then checks for a canonical alias and
|
||||||
|
@ -297,9 +324,10 @@ class Room {
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Call the Matrix API to unban a banned user from this room.
|
/// Set the power level of the user with the [userID] to the value [power].
|
||||||
Future<dynamic> setPower(String userID, int power) async {
|
Future<dynamic> setPower(String userID, int power) async {
|
||||||
Map<String, int> powerMap = await client.store.getPowerLevels(id);
|
if (states["m.room.power_levels"] == null) return null;
|
||||||
|
Map<String, int> powerMap = states["m.room.power_levels"].content["users"];
|
||||||
powerMap[userID] = power;
|
powerMap[userID] = power;
|
||||||
|
|
||||||
dynamic res = await client.connection.jsonRequest(
|
dynamic res = await client.connection.jsonRequest(
|
||||||
|
@ -325,7 +353,7 @@ class Room {
|
||||||
final dynamic resp = await client.connection.jsonRequest(
|
final dynamic resp = await client.connection.jsonRequest(
|
||||||
type: HTTPType.GET,
|
type: HTTPType.GET,
|
||||||
action:
|
action:
|
||||||
"/client/r0/rooms/$id/messages?from=${prev_batch}&dir=b&limit=$historyCount");
|
"/client/r0/rooms/$id/messages?from=${prev_batch}&dir=b&limit=$historyCount&filter=${Connection.syncFilters}");
|
||||||
|
|
||||||
if (resp is ErrorResponse) return;
|
if (resp is ErrorResponse) return;
|
||||||
|
|
||||||
|
@ -336,6 +364,33 @@ class Room {
|
||||||
resp["chunk"].length > 0 &&
|
resp["chunk"].length > 0 &&
|
||||||
resp["end"] is String)) return;
|
resp["end"] is String)) return;
|
||||||
|
|
||||||
|
if (resp["state"] is List<dynamic>) {
|
||||||
|
client.store?.transaction(() {
|
||||||
|
for (int i = 0; i < resp["state"].length; i++) {
|
||||||
|
EventUpdate eventUpdate = EventUpdate(
|
||||||
|
type: "state",
|
||||||
|
roomID: id,
|
||||||
|
eventType: resp["state"][i]["type"],
|
||||||
|
content: resp["state"][i],
|
||||||
|
);
|
||||||
|
client.connection.onEvent.add(eventUpdate);
|
||||||
|
client.store.storeEventUpdate(eventUpdate);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
});
|
||||||
|
if (client.store == null) {
|
||||||
|
for (int i = 0; i < resp["state"].length; i++) {
|
||||||
|
EventUpdate eventUpdate = EventUpdate(
|
||||||
|
type: "state",
|
||||||
|
roomID: id,
|
||||||
|
eventType: resp["state"][i]["type"],
|
||||||
|
content: resp["state"][i],
|
||||||
|
);
|
||||||
|
client.connection.onEvent.add(eventUpdate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
List<dynamic> history = resp["chunk"];
|
List<dynamic> history = resp["chunk"];
|
||||||
client.store?.transaction(() {
|
client.store?.transaction(() {
|
||||||
for (int i = 0; i < history.length; i++) {
|
for (int i = 0; i < history.length; i++) {
|
||||||
|
@ -348,7 +403,7 @@ class Room {
|
||||||
client.connection.onEvent.add(eventUpdate);
|
client.connection.onEvent.add(eventUpdate);
|
||||||
client.store.storeEventUpdate(eventUpdate);
|
client.store.storeEventUpdate(eventUpdate);
|
||||||
client.store.txn.rawUpdate(
|
client.store.txn.rawUpdate(
|
||||||
"UPDATE Rooms SET prev_batch=? WHERE id=?", [resp["end"], id]);
|
"UPDATE Rooms SET prev_batch=? WHERE room_id=?", [resp["end"], id]);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
});
|
});
|
||||||
|
@ -367,8 +422,7 @@ class Room {
|
||||||
|
|
||||||
/// Sets this room as a direct chat for this user.
|
/// Sets this room as a direct chat for this user.
|
||||||
Future<dynamic> addToDirectChat(String userID) async {
|
Future<dynamic> addToDirectChat(String userID) async {
|
||||||
Map<String, List<String>> directChats =
|
Map<String, dynamic> directChats = client.directChats;
|
||||||
await client.store.getAccountDataDirectChats();
|
|
||||||
if (directChats.containsKey(userID)) if (!directChats[userID].contains(id))
|
if (directChats.containsKey(userID)) if (!directChats[userID].contains(id))
|
||||||
directChats[userID].add(id);
|
directChats[userID].add(id);
|
||||||
else
|
else
|
||||||
|
@ -395,73 +449,58 @@ class Room {
|
||||||
return resp;
|
return resp;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a Room from a json String which comes normally from the store.
|
/// Returns a Room from a json String which comes normally from the store. If the
|
||||||
|
/// state are also given, the method will await them.
|
||||||
static Future<Room> getRoomFromTableRow(
|
static Future<Room> getRoomFromTableRow(
|
||||||
Map<String, dynamic> row, Client matrix) async {
|
Map<String, dynamic> row, Client matrix,
|
||||||
String avatarUrl = row["avatar_url"];
|
{Future<List<Map<String, dynamic>>> states,
|
||||||
if (avatarUrl == "")
|
Future<List<Map<String, dynamic>>> roomAccountData}) async {
|
||||||
avatarUrl = await matrix.store?.getAvatarFromSingleChat(row["id"]) ?? "";
|
Room newRoom = Room(
|
||||||
|
id: row["room_id"],
|
||||||
return Room(
|
|
||||||
id: row["id"],
|
|
||||||
name: row["topic"],
|
|
||||||
membership: Membership.values
|
membership: Membership.values
|
||||||
.firstWhere((e) => e.toString() == 'Membership.' + row["membership"]),
|
.firstWhere((e) => e.toString() == 'Membership.' + row["membership"]),
|
||||||
topic: row["description"],
|
|
||||||
avatar: MxContent(avatarUrl),
|
|
||||||
notificationCount: row["notification_count"],
|
notificationCount: row["notification_count"],
|
||||||
highlightCount: row["highlight_count"],
|
highlightCount: row["highlight_count"],
|
||||||
unread: ChatTime(row["unread"]),
|
|
||||||
fullyRead: row["fully_read"],
|
|
||||||
notificationSettings: row["notification_settings"],
|
notificationSettings: row["notification_settings"],
|
||||||
directChatMatrixID: row["direct_chat_matrix_id"],
|
|
||||||
draft: row["draft"],
|
|
||||||
prev_batch: row["prev_batch"],
|
prev_batch: row["prev_batch"],
|
||||||
guestAccess: row["guest_access"],
|
|
||||||
historyVisibility: row["history_visibility"],
|
|
||||||
joinRules: row["join_rules"],
|
|
||||||
canonicalAlias: row["canonical_alias"],
|
|
||||||
mInvitedMemberCount: row["invited_member_count"],
|
mInvitedMemberCount: row["invited_member_count"],
|
||||||
mJoinedMemberCount: row["joined_member_count"],
|
mJoinedMemberCount: row["joined_member_count"],
|
||||||
mHeroes: row["heroes"]?.split(",") ?? [],
|
mHeroes: row["heroes"]?.split(",") ?? [],
|
||||||
powerLevels: {
|
|
||||||
"power_events_default": row["power_events_default"],
|
|
||||||
"power_state_default": row["power_state_default"],
|
|
||||||
"power_redact": row["power_redact"],
|
|
||||||
"power_invite": row["power_invite"],
|
|
||||||
"power_ban": row["power_ban"],
|
|
||||||
"power_kick": row["power_kick"],
|
|
||||||
"power_user_default": row["power_user_default"],
|
|
||||||
"power_event_avatar": row["power_event_avatar"],
|
|
||||||
"power_event_history_visibility": row["power_event_history_visibility"],
|
|
||||||
"power_event_canonical_alias": row["power_event_canonical_alias"],
|
|
||||||
"power_event_aliases": row["power_event_aliases"],
|
|
||||||
"power_event_name": row["power_event_name"],
|
|
||||||
"power_event_power_levels": row["power_event_power_levels"],
|
|
||||||
},
|
|
||||||
lastEvent: Event.fromJson(row, null),
|
|
||||||
client: matrix,
|
client: matrix,
|
||||||
|
states: {},
|
||||||
|
roomAccountData: {},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Map<String, RoomState> newStates = {};
|
||||||
|
if (states != null) {
|
||||||
|
List<Map<String, dynamic>> rawStates = await states;
|
||||||
|
for (int i = 0; i < rawStates.length; i++) {
|
||||||
|
RoomState newState = RoomState.fromJson(rawStates[i], newRoom);
|
||||||
|
newStates[newState.key] = newState;
|
||||||
|
}
|
||||||
|
newRoom.states = newStates;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated("Use client.store.getRoomById(String id) instead!")
|
Map<String, RoomAccountData> newRoomAccountData = {};
|
||||||
static Future<Room> getRoomById(String id, Client matrix) async {
|
if (roomAccountData != null) {
|
||||||
Room room = await matrix.store.getRoomById(id);
|
List<Map<String, dynamic>> rawRoomAccountData = await roomAccountData;
|
||||||
return room;
|
for (int i = 0; i < rawRoomAccountData.length; i++) {
|
||||||
|
RoomAccountData newData =
|
||||||
|
RoomAccountData.fromJson(rawRoomAccountData[i], newRoom);
|
||||||
|
newRoomAccountData[newData.typeKey] = newData;
|
||||||
|
}
|
||||||
|
newRoom.roomAccountData = newRoomAccountData;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Load a room from the store including all room events.
|
return newRoom;
|
||||||
static Future<Room> loadRoomEvents(String id, Client matrix) async {
|
|
||||||
Room room = await matrix.store.getRoomById(id);
|
|
||||||
await room.loadEvents();
|
|
||||||
return room;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a timeline from the store. Returns a [Timeline] object.
|
/// Creates a timeline from the store. Returns a [Timeline] object.
|
||||||
Future<Timeline> getTimeline(
|
Future<Timeline> getTimeline(
|
||||||
{onTimelineUpdateCallback onUpdate,
|
{onTimelineUpdateCallback onUpdate,
|
||||||
onTimelineInsertCallback onInsert}) async {
|
onTimelineInsertCallback onInsert}) async {
|
||||||
List<Event> events = await loadEvents();
|
List<Event> events = [];
|
||||||
|
if (client.store != null) events = await client.store.getEventList(this);
|
||||||
return Timeline(
|
return Timeline(
|
||||||
room: this,
|
room: this,
|
||||||
events: events,
|
events: events,
|
||||||
|
@ -470,17 +509,23 @@ class Room {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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 {
|
|
||||||
return await client.store.getEventList(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Load all participants for a given room from the store.
|
/// Load all participants for a given room from the store.
|
||||||
|
@deprecated
|
||||||
Future<List<User>> loadParticipants() async {
|
Future<List<User>> loadParticipants() async {
|
||||||
return await client.store.loadParticipants(this);
|
return await client.store.loadParticipants(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns all participants for this room. With lazy loading this
|
||||||
|
/// list may not be complete. User [requestParticipants] in this
|
||||||
|
/// case.
|
||||||
|
List<User> getParticipants() {
|
||||||
|
List<User> userList = [];
|
||||||
|
for (var entry in states.entries)
|
||||||
|
if (entry.value.type == EventTypes.RoomMember)
|
||||||
|
userList.add(entry.value.asUser);
|
||||||
|
return userList;
|
||||||
|
}
|
||||||
|
|
||||||
/// Request the full list of participants from the server. The local list
|
/// Request the full list of participants from the server. The local list
|
||||||
/// from the store is not complete if the client uses lazy loading.
|
/// from the store is not complete if the client uses lazy loading.
|
||||||
Future<List<User>> requestParticipants() async {
|
Future<List<User>> requestParticipants() async {
|
||||||
|
@ -492,14 +537,7 @@ class Room {
|
||||||
return participants;
|
return participants;
|
||||||
|
|
||||||
for (num i = 0; i < res["chunk"].length; i++) {
|
for (num i = 0; i < res["chunk"].length; i++) {
|
||||||
User newUser = User(res["chunk"][i]["state_key"],
|
User newUser = RoomState.fromJson(res["chunk"][i], this).asUser;
|
||||||
displayName: res["chunk"][i]["content"]["displayname"] ?? "",
|
|
||||||
membership: Membership.values.firstWhere((e) =>
|
|
||||||
e.toString() ==
|
|
||||||
'Membership.' + res["chunk"][i]["content"]["membership"] ??
|
|
||||||
""),
|
|
||||||
avatarUrl: MxContent(res["chunk"][i]["content"]["avatar_url"] ?? ""),
|
|
||||||
room: this);
|
|
||||||
if (newUser.membership != Membership.leave) participants.add(newUser);
|
if (newUser.membership != Membership.leave) participants.add(newUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -507,18 +545,14 @@ class Room {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<User> getUserByMXID(String mxID) async {
|
Future<User> getUserByMXID(String mxID) async {
|
||||||
if (client.store != null) {
|
if (states[mxID] != null) return states[mxID].asUser;
|
||||||
final User storeEvent =
|
|
||||||
await client.store.getUser(matrixID: mxID, room: this);
|
|
||||||
if (storeEvent != null) return storeEvent;
|
|
||||||
}
|
|
||||||
final dynamic resp = await client.connection.jsonRequest(
|
final dynamic resp = await client.connection.jsonRequest(
|
||||||
type: HTTPType.GET,
|
type: HTTPType.GET,
|
||||||
action: "/client/r0/rooms/$id/state/m.room.member/$mxID");
|
action: "/client/r0/rooms/$id/state/m.room.member/$mxID");
|
||||||
if (resp is ErrorResponse) return null;
|
if (resp is ErrorResponse) return null;
|
||||||
// Somehow we miss the mxid in the response and only get the content of the event.
|
// Somehow we miss the mxid in the response and only get the content of the event.
|
||||||
resp["matrix_id"] = mxID;
|
resp["matrix_id"] = mxID;
|
||||||
return User.fromJson(resp, this);
|
return RoomState.fromJson(resp, this).asUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Searches for the event in the store. If it isn't found, try to request it
|
/// Searches for the event in the store. If it isn't found, try to request it
|
||||||
|
@ -531,7 +565,30 @@ class Room {
|
||||||
final dynamic resp = await client.connection.jsonRequest(
|
final dynamic resp = await client.connection.jsonRequest(
|
||||||
type: HTTPType.GET, action: "/client/r0/rooms/$id/event/$eventID");
|
type: HTTPType.GET, action: "/client/r0/rooms/$id/event/$eventID");
|
||||||
if (resp is ErrorResponse) return null;
|
if (resp is ErrorResponse) return null;
|
||||||
return Event.fromJson(resp, this,
|
return Event.fromJson(resp, this);
|
||||||
senderUser: (await getUserByMXID(resp["sender"])));
|
}
|
||||||
|
|
||||||
|
/// Returns the user's own power level.
|
||||||
|
int getPowerLevelByUserId(String userId) {
|
||||||
|
int powerLevel = 0;
|
||||||
|
RoomState powerLevelState = states["m.room.power_levels"];
|
||||||
|
if (powerLevelState == null) return powerLevel;
|
||||||
|
if (powerLevelState.content["users_default"] is int)
|
||||||
|
powerLevel = powerLevelState.content["users_default"];
|
||||||
|
if (powerLevelState.content["users"] is Map<String, dynamic> &&
|
||||||
|
powerLevelState.content["users"][userId] != null)
|
||||||
|
powerLevel = powerLevelState.content["users"][userId];
|
||||||
|
return powerLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the user's own power level.
|
||||||
|
int get ownPowerLevel => getPowerLevelByUserId(client.userID);
|
||||||
|
|
||||||
|
/// Returns the power levels from all users for this room or null if not given.
|
||||||
|
Map<String, int> get powerLevels {
|
||||||
|
RoomState powerLevelState = states["m.room.power_levels"];
|
||||||
|
if (powerLevelState.content["users"] is Map<String, int>)
|
||||||
|
return powerLevelState.content["users"];
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
49
lib/src/RoomAccountData.dart
Normal file
49
lib/src/RoomAccountData.dart
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
/*
|
||||||
|
* 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:famedlysdk/famedlysdk.dart';
|
||||||
|
import 'package:famedlysdk/src/AccountData.dart';
|
||||||
|
import 'package:famedlysdk/src/RoomState.dart';
|
||||||
|
|
||||||
|
class RoomAccountData extends AccountData {
|
||||||
|
/// The user who has sent this event if it is not a global account data event.
|
||||||
|
final String roomId;
|
||||||
|
|
||||||
|
final Room room;
|
||||||
|
|
||||||
|
RoomAccountData(
|
||||||
|
{this.roomId, this.room, Map<String, dynamic> content, String typeKey})
|
||||||
|
: super(content: content, typeKey: typeKey);
|
||||||
|
|
||||||
|
/// Get a State event from a table row or from the event stream.
|
||||||
|
factory RoomAccountData.fromJson(
|
||||||
|
Map<String, dynamic> jsonPayload, Room room) {
|
||||||
|
final Map<String, dynamic> content =
|
||||||
|
RoomState.getMapFromPayload(jsonPayload['content']);
|
||||||
|
return RoomAccountData(
|
||||||
|
content: content,
|
||||||
|
typeKey: jsonPayload['type'],
|
||||||
|
roomId: jsonPayload['room_id'],
|
||||||
|
room: room);
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,14 +24,17 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:core';
|
import 'dart:core';
|
||||||
|
|
||||||
|
import 'package:famedlysdk/src/RoomState.dart';
|
||||||
|
|
||||||
import 'Client.dart';
|
import 'Client.dart';
|
||||||
import 'Event.dart';
|
|
||||||
import 'Room.dart';
|
import 'Room.dart';
|
||||||
import 'User.dart';
|
import 'User.dart';
|
||||||
import 'sync/EventUpdate.dart';
|
import 'sync/EventUpdate.dart';
|
||||||
import 'sync/RoomUpdate.dart';
|
import 'sync/RoomUpdate.dart';
|
||||||
import 'utils/ChatTime.dart';
|
|
||||||
import 'utils/MxContent.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
|
/// Represents a list of rooms for this client, which will automatically update
|
||||||
/// itself and call the [onUpdate], [onInsert] and [onDelete] callbacks. To get
|
/// itself and call the [onUpdate], [onInsert] and [onDelete] callbacks. To get
|
||||||
|
@ -69,6 +72,21 @@ class RoomList {
|
||||||
this.onlyGroups = false}) {
|
this.onlyGroups = false}) {
|
||||||
eventSub ??= client.connection.onEvent.stream.listen(_handleEventUpdate);
|
eventSub ??= client.connection.onEvent.stream.listen(_handleEventUpdate);
|
||||||
roomSub ??= client.connection.onRoomUpdate.stream.listen(_handleRoomUpdate);
|
roomSub ??= client.connection.onRoomUpdate.stream.listen(_handleRoomUpdate);
|
||||||
|
sort();
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
void _handleRoomUpdate(RoomUpdate chatUpdate) {
|
||||||
|
@ -87,7 +105,6 @@ class RoomList {
|
||||||
// Add the new chat to the list
|
// Add the new chat to the list
|
||||||
Room newRoom = Room(
|
Room newRoom = Room(
|
||||||
id: chatUpdate.id,
|
id: chatUpdate.id,
|
||||||
name: "",
|
|
||||||
membership: chatUpdate.membership,
|
membership: chatUpdate.membership,
|
||||||
prev_batch: chatUpdate.prev_batch,
|
prev_batch: chatUpdate.prev_batch,
|
||||||
highlightCount: chatUpdate.highlight_count,
|
highlightCount: chatUpdate.highlight_count,
|
||||||
|
@ -95,6 +112,9 @@ class RoomList {
|
||||||
mHeroes: chatUpdate.summary?.mHeroes,
|
mHeroes: chatUpdate.summary?.mHeroes,
|
||||||
mJoinedMemberCount: chatUpdate.summary?.mJoinedMemberCount,
|
mJoinedMemberCount: chatUpdate.summary?.mJoinedMemberCount,
|
||||||
mInvitedMemberCount: chatUpdate.summary?.mInvitedMemberCount,
|
mInvitedMemberCount: chatUpdate.summary?.mInvitedMemberCount,
|
||||||
|
states: {},
|
||||||
|
roomAccountData: {},
|
||||||
|
client: client,
|
||||||
);
|
);
|
||||||
rooms.insert(position, newRoom);
|
rooms.insert(position, newRoom);
|
||||||
if (onInsert != null) onInsert(position);
|
if (onInsert != null) onInsert(position);
|
||||||
|
@ -125,11 +145,7 @@ class RoomList {
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleEventUpdate(EventUpdate eventUpdate) {
|
void _handleEventUpdate(EventUpdate eventUpdate) {
|
||||||
// Is the event necessary for the chat list? If not, then return
|
if (eventUpdate.type != "timeline" && eventUpdate.type != "state") return;
|
||||||
if (!(eventUpdate.type == "timeline" ||
|
|
||||||
eventUpdate.eventType == "m.room.avatar" ||
|
|
||||||
eventUpdate.eventType == "m.room.name")) return;
|
|
||||||
|
|
||||||
// Search the room in the rooms
|
// Search the room in the rooms
|
||||||
num j = 0;
|
num j = 0;
|
||||||
for (j = 0; j < rooms.length; j++) {
|
for (j = 0; j < rooms.length; j++) {
|
||||||
|
@ -138,44 +154,20 @@ class RoomList {
|
||||||
final bool found = (j < rooms.length && rooms[j].id == eventUpdate.roomID);
|
final bool found = (j < rooms.length && rooms[j].id == eventUpdate.roomID);
|
||||||
if (!found) return;
|
if (!found) return;
|
||||||
|
|
||||||
// Is this an old timeline event? Then stop here...
|
RoomState stateEvent = RoomState.fromJson(eventUpdate.content, rooms[j]);
|
||||||
/*if (eventUpdate.type == "timeline" &&
|
if (rooms[j].states[stateEvent.key] != null &&
|
||||||
ChatTime(eventUpdate.content["origin_server_ts"]) <=
|
rooms[j].states[stateEvent.key].time > stateEvent.time) return;
|
||||||
rooms[j].timeCreated) return;*/
|
rooms[j].states[stateEvent.key] = stateEvent;
|
||||||
|
|
||||||
if (eventUpdate.type == "timeline") {
|
|
||||||
User stateKey = null;
|
|
||||||
if (eventUpdate.content["state_key"] is String)
|
|
||||||
stateKey = User(eventUpdate.content["state_key"]);
|
|
||||||
// Update the last message preview
|
|
||||||
rooms[j].lastEvent = Event(
|
|
||||||
eventUpdate.content["id"],
|
|
||||||
User(eventUpdate.content["sender"]),
|
|
||||||
ChatTime(eventUpdate.content["origin_server_ts"]),
|
|
||||||
room: rooms[j],
|
|
||||||
stateKey: stateKey,
|
|
||||||
content: eventUpdate.content["content"],
|
|
||||||
environment: eventUpdate.eventType,
|
|
||||||
status: 2,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (eventUpdate.eventType == "m.room.name") {
|
|
||||||
// Update the room name
|
|
||||||
rooms[j].name = eventUpdate.content["content"]["name"];
|
|
||||||
} else if (eventUpdate.eventType == "m.room.avatar") {
|
|
||||||
// Update the room avatar
|
|
||||||
rooms[j].avatar = MxContent(eventUpdate.content["content"]["url"]);
|
|
||||||
}
|
|
||||||
sortAndUpdate();
|
sortAndUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
sortAndUpdate() {
|
sort() {
|
||||||
rooms?.sort((a, b) =>
|
rooms?.sort((a, b) =>
|
||||||
b.timeCreated.toTimeStamp().compareTo(a.timeCreated.toTimeStamp()));
|
b.timeCreated.toTimeStamp().compareTo(a.timeCreated.toTimeStamp()));
|
||||||
|
}
|
||||||
|
|
||||||
|
sortAndUpdate() {
|
||||||
|
sort();
|
||||||
if (onUpdate != null) onUpdate();
|
if (onUpdate != null) onUpdate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef onRoomListUpdateCallback = void Function();
|
|
||||||
typedef onRoomListInsertCallback = void Function(int insertID);
|
|
||||||
typedef onRoomListRemoveCallback = void Function(int insertID);
|
|
||||||
|
|
215
lib/src/RoomState.dart
Normal file
215
lib/src/RoomState.dart
Normal file
|
@ -0,0 +1,215 @@
|
||||||
|
/*
|
||||||
|
* 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:convert';
|
||||||
|
import 'package:famedlysdk/famedlysdk.dart';
|
||||||
|
import 'package:famedlysdk/src/utils/ChatTime.dart';
|
||||||
|
import './Room.dart';
|
||||||
|
|
||||||
|
class RoomState {
|
||||||
|
/// The Matrix ID for this event in the format '$localpart:server.abc'. Please not
|
||||||
|
/// that account data, presence and other events may not have an eventId.
|
||||||
|
final String eventId;
|
||||||
|
|
||||||
|
/// The json payload of the content. The content highly depends on the type.
|
||||||
|
final Map<String, dynamic> content;
|
||||||
|
|
||||||
|
/// The type String of this event. For example 'm.room.message'.
|
||||||
|
final String typeKey;
|
||||||
|
|
||||||
|
/// The ID of the room this event belongs to.
|
||||||
|
final String roomId;
|
||||||
|
|
||||||
|
/// The user who has sent this event if it is not a global account data event.
|
||||||
|
final String senderId;
|
||||||
|
|
||||||
|
User get sender => room.states[senderId]?.asUser ?? User(senderId);
|
||||||
|
|
||||||
|
/// The time this event has received at the server. May be null for events like
|
||||||
|
/// account data.
|
||||||
|
final ChatTime time;
|
||||||
|
|
||||||
|
/// Optional additional content for this event.
|
||||||
|
final Map<String, dynamic> unsigned;
|
||||||
|
|
||||||
|
/// The room this event belongs to. May be null.
|
||||||
|
final Room room;
|
||||||
|
|
||||||
|
/// Optional. The previous content for this state.
|
||||||
|
/// This will be present only for state events appearing in the timeline.
|
||||||
|
/// If this is not a state event, or there is no previous content, this key will be null.
|
||||||
|
final Map<String, dynamic> prevContent;
|
||||||
|
|
||||||
|
/// Optional. This key will only be present for state events. A unique key which defines
|
||||||
|
/// the overwriting semantics for this piece of room state.
|
||||||
|
final String stateKey;
|
||||||
|
|
||||||
|
User get stateKeyUser => room.states[stateKey]?.asUser ?? User(stateKey);
|
||||||
|
|
||||||
|
RoomState(
|
||||||
|
{this.content,
|
||||||
|
this.typeKey,
|
||||||
|
this.eventId,
|
||||||
|
this.roomId,
|
||||||
|
this.senderId,
|
||||||
|
this.time,
|
||||||
|
this.unsigned,
|
||||||
|
this.prevContent,
|
||||||
|
this.stateKey,
|
||||||
|
this.room});
|
||||||
|
|
||||||
|
static Map<String, dynamic> getMapFromPayload(dynamic payload) {
|
||||||
|
if (payload is String)
|
||||||
|
try {
|
||||||
|
return json.decode(payload);
|
||||||
|
} catch (e) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
if (payload is Map<String, dynamic>) return payload;
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a State event from a table row or from the event stream.
|
||||||
|
factory RoomState.fromJson(Map<String, dynamic> jsonPayload, Room room) {
|
||||||
|
final Map<String, dynamic> content =
|
||||||
|
RoomState.getMapFromPayload(jsonPayload['content']);
|
||||||
|
final Map<String, dynamic> unsigned =
|
||||||
|
RoomState.getMapFromPayload(jsonPayload['unsigned']);
|
||||||
|
final Map<String, dynamic> prevContent =
|
||||||
|
RoomState.getMapFromPayload(jsonPayload['prev_content']);
|
||||||
|
return RoomState(
|
||||||
|
stateKey: jsonPayload['state_key'],
|
||||||
|
prevContent: prevContent,
|
||||||
|
content: content,
|
||||||
|
typeKey: jsonPayload['type'],
|
||||||
|
eventId: jsonPayload['event_id'],
|
||||||
|
roomId: jsonPayload['room_id'],
|
||||||
|
senderId: jsonPayload['sender'],
|
||||||
|
time: ChatTime(jsonPayload['origin_server_ts']),
|
||||||
|
unsigned: unsigned,
|
||||||
|
room: room);
|
||||||
|
}
|
||||||
|
|
||||||
|
Event get timelineEvent => Event(
|
||||||
|
content: content,
|
||||||
|
typeKey: typeKey,
|
||||||
|
eventId: eventId,
|
||||||
|
room: room,
|
||||||
|
roomId: roomId,
|
||||||
|
senderId: senderId,
|
||||||
|
time: time,
|
||||||
|
unsigned: unsigned,
|
||||||
|
status: 1,
|
||||||
|
);
|
||||||
|
|
||||||
|
/// The unique key of this event. For events with a [stateKey], it will be the
|
||||||
|
/// stateKey. Otherwise it will be the [type] as a string.
|
||||||
|
String get key => stateKey == null || stateKey.isEmpty ? typeKey : stateKey;
|
||||||
|
|
||||||
|
User get asUser => User.fromState(
|
||||||
|
stateKey: stateKey,
|
||||||
|
prevContent: prevContent,
|
||||||
|
content: content,
|
||||||
|
typeKey: typeKey,
|
||||||
|
eventId: eventId,
|
||||||
|
roomId: roomId,
|
||||||
|
senderId: senderId,
|
||||||
|
time: time,
|
||||||
|
unsigned: unsigned,
|
||||||
|
room: room);
|
||||||
|
|
||||||
|
/// Get the real type.
|
||||||
|
EventTypes get type {
|
||||||
|
switch (typeKey) {
|
||||||
|
case "m.room.avatar":
|
||||||
|
return EventTypes.RoomAvatar;
|
||||||
|
case "m.room.name":
|
||||||
|
return EventTypes.RoomName;
|
||||||
|
case "m.room.topic":
|
||||||
|
return EventTypes.RoomTopic;
|
||||||
|
case "m.room.Aliases":
|
||||||
|
return EventTypes.RoomAliases;
|
||||||
|
case "m.room.canonical_alias":
|
||||||
|
return EventTypes.RoomCanonicalAlias;
|
||||||
|
case "m.room.create":
|
||||||
|
return EventTypes.RoomCreate;
|
||||||
|
case "m.room.join_rules":
|
||||||
|
return EventTypes.RoomJoinRules;
|
||||||
|
case "m.room.member":
|
||||||
|
return EventTypes.RoomMember;
|
||||||
|
case "m.room.power_levels":
|
||||||
|
return EventTypes.RoomPowerLevels;
|
||||||
|
case "m.room.guest_access":
|
||||||
|
return EventTypes.GuestAccess;
|
||||||
|
case "m.room.history_visibility":
|
||||||
|
return EventTypes.HistoryVisibility;
|
||||||
|
case "m.room.message":
|
||||||
|
switch (content["msgtype"] ?? "m.text") {
|
||||||
|
case "m.text":
|
||||||
|
if (content.containsKey("m.relates_to")) {
|
||||||
|
return EventTypes.Reply;
|
||||||
|
}
|
||||||
|
return EventTypes.Text;
|
||||||
|
case "m.notice":
|
||||||
|
return EventTypes.Notice;
|
||||||
|
case "m.emote":
|
||||||
|
return EventTypes.Emote;
|
||||||
|
case "m.image":
|
||||||
|
return EventTypes.Image;
|
||||||
|
case "m.video":
|
||||||
|
return EventTypes.Video;
|
||||||
|
case "m.audio":
|
||||||
|
return EventTypes.Audio;
|
||||||
|
case "m.file":
|
||||||
|
return EventTypes.File;
|
||||||
|
case "m.location":
|
||||||
|
return EventTypes.Location;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return EventTypes.Unknown;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum EventTypes {
|
||||||
|
Text,
|
||||||
|
Emote,
|
||||||
|
Notice,
|
||||||
|
Image,
|
||||||
|
Video,
|
||||||
|
Audio,
|
||||||
|
File,
|
||||||
|
Location,
|
||||||
|
Reply,
|
||||||
|
RoomAliases,
|
||||||
|
RoomCanonicalAlias,
|
||||||
|
RoomCreate,
|
||||||
|
RoomJoinRules,
|
||||||
|
RoomMember,
|
||||||
|
RoomPowerLevels,
|
||||||
|
RoomName,
|
||||||
|
RoomTopic,
|
||||||
|
RoomAvatar,
|
||||||
|
GuestAccess,
|
||||||
|
HistoryVisibility,
|
||||||
|
Unknown,
|
||||||
|
}
|
|
@ -25,6 +25,9 @@ import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:core';
|
import 'dart:core';
|
||||||
|
|
||||||
|
import 'package:famedlysdk/src/AccountData.dart';
|
||||||
|
import 'package:famedlysdk/src/Presence.dart';
|
||||||
|
import 'package:famedlysdk/src/RoomState.dart';
|
||||||
import 'package:path/path.dart' as p;
|
import 'package:path/path.dart' as p;
|
||||||
import 'package:sqflite/sqflite.dart';
|
import 'package:sqflite/sqflite.dart';
|
||||||
|
|
||||||
|
@ -55,7 +58,7 @@ class Store {
|
||||||
_init() async {
|
_init() async {
|
||||||
var databasePath = await getDatabasesPath();
|
var databasePath = await getDatabasesPath();
|
||||||
String path = p.join(databasePath, "FluffyMatrix.db");
|
String path = p.join(databasePath, "FluffyMatrix.db");
|
||||||
_db = await openDatabase(path, version: 12,
|
_db = await openDatabase(path, version: 14,
|
||||||
onCreate: (Database db, int version) async {
|
onCreate: (Database db, int version) async {
|
||||||
await createTables(db);
|
await createTables(db);
|
||||||
}, onUpgrade: (Database db, int oldVersion, int newVersion) async {
|
}, onUpgrade: (Database db, int oldVersion, int newVersion) async {
|
||||||
|
@ -153,8 +156,8 @@ class Store {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> storeRoomPrevBatch(Room room) async {
|
Future<void> storeRoomPrevBatch(Room room) async {
|
||||||
await _db.rawUpdate(
|
await _db.rawUpdate("UPDATE Rooms SET prev_batch=? WHERE room_id=?",
|
||||||
"UPDATE Rooms SET prev_batch=? WHERE id=?", [room.prev_batch, room.id]);
|
[room.prev_batch, room.id]);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -163,8 +166,7 @@ class Store {
|
||||||
Future<void> storeRoomUpdate(RoomUpdate roomUpdate) {
|
Future<void> storeRoomUpdate(RoomUpdate roomUpdate) {
|
||||||
// Insert the chat into the database if not exists
|
// Insert the chat into the database if not exists
|
||||||
txn.rawInsert(
|
txn.rawInsert(
|
||||||
"INSERT OR IGNORE INTO Rooms " +
|
"INSERT OR IGNORE INTO Rooms " + "VALUES(?, ?, 0, 0, '', 0, 0, '') ",
|
||||||
"VALUES(?, ?, '', 0, 0, 0, 0, '', '', '', '', 0, '', '', '', '', '', '', '', '', 0, 50, 50, 0, 50, 50, 0, 50, 100, 50, 50, 50, 100) ",
|
|
||||||
[roomUpdate.id, roomUpdate.membership.toString().split('.').last]);
|
[roomUpdate.id, roomUpdate.membership.toString().split('.').last]);
|
||||||
|
|
||||||
// Update the notification counts and the limited timeline boolean and the summary
|
// Update the notification counts and the limited timeline boolean and the summary
|
||||||
|
@ -187,15 +189,15 @@ class Store {
|
||||||
updateQuery += ", heroes=?";
|
updateQuery += ", heroes=?";
|
||||||
updateArgs.add(roomUpdate.summary.mHeroes.join(","));
|
updateArgs.add(roomUpdate.summary.mHeroes.join(","));
|
||||||
}
|
}
|
||||||
updateQuery += " WHERE id=?";
|
updateQuery += " WHERE room_id=?";
|
||||||
updateArgs.add(roomUpdate.id);
|
updateArgs.add(roomUpdate.id);
|
||||||
txn.rawUpdate(updateQuery, updateArgs);
|
txn.rawUpdate(updateQuery, updateArgs);
|
||||||
|
|
||||||
// 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 (roomUpdate.limitedTimeline) {
|
if (roomUpdate.limitedTimeline) {
|
||||||
txn.rawDelete("DELETE FROM Events WHERE chat_id=?", [roomUpdate.id]);
|
txn.rawDelete("DELETE FROM Events WHERE room_id=?", [roomUpdate.id]);
|
||||||
txn.rawUpdate("UPDATE Rooms SET prev_batch=? WHERE id=?",
|
txn.rawUpdate("UPDATE Rooms SET prev_batch=? WHERE room_id=?",
|
||||||
[roomUpdate.prev_batch, roomUpdate.id]);
|
[roomUpdate.prev_batch, roomUpdate.id]);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
@ -204,21 +206,17 @@ class Store {
|
||||||
/// Stores an UserUpdate object in the database. Must be called inside of
|
/// Stores an UserUpdate object in the database. Must be called inside of
|
||||||
/// [transaction].
|
/// [transaction].
|
||||||
Future<void> storeUserEventUpdate(UserUpdate userUpdate) {
|
Future<void> storeUserEventUpdate(UserUpdate userUpdate) {
|
||||||
switch (userUpdate.eventType) {
|
if (userUpdate.type == "account_data")
|
||||||
case "m.direct":
|
txn.rawInsert("INSERT OR REPLACE INTO AccountData VALUES(?, ?)", [
|
||||||
if (userUpdate.content["content"] is Map<String, dynamic>) {
|
userUpdate.eventType,
|
||||||
final Map<String, dynamic> directMap = userUpdate.content["content"];
|
json.encode(userUpdate.content["content"]),
|
||||||
directMap.forEach((String key, dynamic value) {
|
]);
|
||||||
if (value is List<dynamic> && value.length > 0)
|
else if (userUpdate.type == "presence")
|
||||||
for (int i = 0; i < value.length; i++) {
|
txn.rawInsert("INSERT OR REPLACE INTO Presences VALUES(?, ?, ?)", [
|
||||||
txn.rawUpdate(
|
userUpdate.eventType,
|
||||||
"UPDATE Rooms SET direct_chat_matrix_id=? WHERE id=?",
|
userUpdate.content["sender"],
|
||||||
[key, value[i]]);
|
json.encode(userUpdate.content["content"]),
|
||||||
}
|
]);
|
||||||
});
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -229,46 +227,40 @@ class Store {
|
||||||
String type = eventUpdate.type;
|
String type = eventUpdate.type;
|
||||||
String chat_id = eventUpdate.roomID;
|
String chat_id = eventUpdate.roomID;
|
||||||
|
|
||||||
if (type == "timeline" || type == "history") {
|
|
||||||
// calculate the status
|
|
||||||
num status = 2;
|
|
||||||
if (eventContent["status"] is num) status = eventContent["status"];
|
|
||||||
|
|
||||||
// Make unsigned part of the content
|
|
||||||
if (eventContent.containsKey("unsigned")) {
|
|
||||||
Map<String, dynamic> newContent = {
|
|
||||||
"unsigned": eventContent["unsigned"]
|
|
||||||
};
|
|
||||||
eventContent["content"].forEach((key, val) => newContent[key] = val);
|
|
||||||
eventContent["content"] = newContent;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the state_key for m.room.member events
|
// Get the state_key for m.room.member events
|
||||||
String state_key = "";
|
String state_key = "";
|
||||||
if (eventContent["state_key"] is String) {
|
if (eventContent["state_key"] is String) {
|
||||||
state_key = eventContent["state_key"];
|
state_key = eventContent["state_key"];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (type == "timeline" || type == "history") {
|
||||||
|
// calculate the status
|
||||||
|
num status = 2;
|
||||||
|
if (eventContent["status"] is num) status = eventContent["status"];
|
||||||
|
|
||||||
// Save the event in the database
|
// Save the event in the database
|
||||||
if ((status == 1 || status == -1) &&
|
if ((status == 1 || status == -1) &&
|
||||||
eventContent["unsigned"] is Map<String, dynamic> &&
|
eventContent["unsigned"] is Map<String, dynamic> &&
|
||||||
eventContent["unsigned"]["transaction_id"] is String)
|
eventContent["unsigned"]["transaction_id"] is String)
|
||||||
txn.rawUpdate("UPDATE Events SET status=?, id=? WHERE id=?", [
|
txn.rawUpdate(
|
||||||
|
"UPDATE Events SET status=?, event_id=? WHERE event_id=?", [
|
||||||
status,
|
status,
|
||||||
eventContent["event_id"],
|
eventContent["event_id"],
|
||||||
eventContent["unsigned"]["transaction_id"]
|
eventContent["unsigned"]["transaction_id"]
|
||||||
]);
|
]);
|
||||||
else
|
else
|
||||||
txn.rawInsert(
|
txn.rawInsert(
|
||||||
"INSERT OR REPLACE INTO Events VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?)", [
|
"INSERT OR REPLACE INTO Events VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||||
|
[
|
||||||
eventContent["event_id"],
|
eventContent["event_id"],
|
||||||
chat_id,
|
chat_id,
|
||||||
eventContent["origin_server_ts"],
|
eventContent["origin_server_ts"],
|
||||||
eventContent["sender"],
|
eventContent["sender"],
|
||||||
state_key,
|
|
||||||
eventContent["content"]["body"],
|
|
||||||
eventContent["type"],
|
eventContent["type"],
|
||||||
|
json.encode(eventContent["unsigned"] ?? ""),
|
||||||
json.encode(eventContent["content"]),
|
json.encode(eventContent["content"]),
|
||||||
|
json.encode(eventContent["prevContent"]),
|
||||||
|
eventContent["state_key"],
|
||||||
status
|
status
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
@ -276,205 +268,55 @@ class Store {
|
||||||
if (status != -1 &&
|
if (status != -1 &&
|
||||||
eventUpdate.content.containsKey("unsigned") &&
|
eventUpdate.content.containsKey("unsigned") &&
|
||||||
eventUpdate.content["unsigned"]["transaction_id"] is String)
|
eventUpdate.content["unsigned"]["transaction_id"] is String)
|
||||||
txn.rawDelete("DELETE FROM Events WHERE id=?",
|
txn.rawDelete("DELETE FROM Events WHERE event_id=?",
|
||||||
[eventUpdate.content["unsigned"]["transaction_id"]]);
|
[eventUpdate.content["unsigned"]["transaction_id"]]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type == "history") return null;
|
if (type == "history") return null;
|
||||||
|
|
||||||
switch (eventUpdate.eventType) {
|
if (eventUpdate.content["event_id"] != null) {
|
||||||
case "m.receipt":
|
|
||||||
if (eventContent["user"] == client.userID) {
|
|
||||||
txn.rawUpdate("UPDATE Rooms SET unread=? WHERE id=?",
|
|
||||||
[eventContent["ts"], chat_id]);
|
|
||||||
} else {
|
|
||||||
// Mark all previous received messages as seen
|
|
||||||
txn.rawUpdate(
|
|
||||||
"UPDATE Events SET status=3 WHERE origin_server_ts<=? AND chat_id=? AND status=2",
|
|
||||||
[eventContent["ts"], chat_id]);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
// This event means, that the name of a room has been changed, so
|
|
||||||
// it has to be changed in the database.
|
|
||||||
case "m.room.name":
|
|
||||||
txn.rawUpdate("UPDATE Rooms SET topic=? WHERE id=?",
|
|
||||||
[eventContent["content"]["name"], chat_id]);
|
|
||||||
break;
|
|
||||||
// This event means, that the topic of a room has been changed, so
|
|
||||||
// it has to be changed in the database
|
|
||||||
case "m.room.topic":
|
|
||||||
txn.rawUpdate("UPDATE Rooms SET description=? WHERE id=?",
|
|
||||||
[eventContent["content"]["topic"], chat_id]);
|
|
||||||
break;
|
|
||||||
// This event means, that the topic of a room has been changed, so
|
|
||||||
// it has to be changed in the database
|
|
||||||
case "m.room.history_visibility":
|
|
||||||
txn.rawUpdate("UPDATE Rooms SET history_visibility=? WHERE id=?",
|
|
||||||
[eventContent["content"]["history_visibility"], chat_id]);
|
|
||||||
break;
|
|
||||||
// This event means, that the topic of a room has been changed, so
|
|
||||||
// it has to be changed in the database
|
|
||||||
case "m.room.redaction":
|
|
||||||
txn.rawDelete(
|
|
||||||
"DELETE FROM Events WHERE id=?", [eventContent["redacts"]]);
|
|
||||||
break;
|
|
||||||
// This event means, that the topic of a room has been changed, so
|
|
||||||
// it has to be changed in the database
|
|
||||||
case "m.room.guest_access":
|
|
||||||
txn.rawUpdate("UPDATE Rooms SET guest_access=? WHERE id=?",
|
|
||||||
[eventContent["content"]["guest_access"], chat_id]);
|
|
||||||
break;
|
|
||||||
// This event means, that the canonical alias of a room has been changed, so
|
|
||||||
// it has to be changed in the database
|
|
||||||
case "m.room.canonical_alias":
|
|
||||||
txn.rawUpdate("UPDATE Rooms SET canonical_alias=? WHERE id=?",
|
|
||||||
[eventContent["content"]["alias"], chat_id]);
|
|
||||||
break;
|
|
||||||
// This event means, that the topic of a room has been changed, so
|
|
||||||
// it has to be changed in the database
|
|
||||||
case "m.room.join_rules":
|
|
||||||
txn.rawUpdate("UPDATE Rooms SET join_rules=? WHERE id=?",
|
|
||||||
[eventContent["content"]["join_rule"], chat_id]);
|
|
||||||
break;
|
|
||||||
// This event means, that the avatar of a room has been changed, so
|
|
||||||
// it has to be changed in the database
|
|
||||||
case "m.room.avatar":
|
|
||||||
txn.rawUpdate("UPDATE Rooms SET avatar_url=? WHERE id=?",
|
|
||||||
[eventContent["content"]["url"], chat_id]);
|
|
||||||
break;
|
|
||||||
// This event means, that the aliases of a room has been changed, so
|
|
||||||
// it has to be changed in the database
|
|
||||||
case "m.fully_read":
|
|
||||||
txn.rawUpdate("UPDATE Rooms SET fully_read=? WHERE id=?",
|
|
||||||
[eventContent["content"]["event_id"], chat_id]);
|
|
||||||
break;
|
|
||||||
// This event means, that someone joined the room, has left the room
|
|
||||||
// or has changed his nickname
|
|
||||||
case "m.room.member":
|
|
||||||
String membership = eventContent["content"]["membership"];
|
|
||||||
String state_key = eventContent["state_key"];
|
|
||||||
String insertDisplayname = "";
|
|
||||||
String insertAvatarUrl = "";
|
|
||||||
if (eventContent["content"]["displayname"] is String) {
|
|
||||||
insertDisplayname = eventContent["content"]["displayname"];
|
|
||||||
}
|
|
||||||
if (eventContent["content"]["avatar_url"] is String) {
|
|
||||||
insertAvatarUrl = eventContent["content"]["avatar_url"];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update membership table
|
|
||||||
txn.rawInsert("INSERT OR IGNORE INTO Users VALUES(?,?,?,?,?,0)", [
|
|
||||||
chat_id,
|
|
||||||
state_key,
|
|
||||||
insertDisplayname,
|
|
||||||
insertAvatarUrl,
|
|
||||||
membership
|
|
||||||
]);
|
|
||||||
String queryStr = "UPDATE Users SET membership=?";
|
|
||||||
List<String> queryArgs = [membership];
|
|
||||||
|
|
||||||
if (eventContent["content"]["displayname"] is String) {
|
|
||||||
queryStr += " , displayname=?";
|
|
||||||
queryArgs.add(eventContent["content"]["displayname"]);
|
|
||||||
}
|
|
||||||
if (eventContent["content"]["avatar_url"] is String) {
|
|
||||||
queryStr += " , avatar_url=?";
|
|
||||||
queryArgs.add(eventContent["content"]["avatar_url"]);
|
|
||||||
}
|
|
||||||
|
|
||||||
queryStr += " WHERE matrix_id=? AND chat_id=?";
|
|
||||||
queryArgs.add(state_key);
|
|
||||||
queryArgs.add(chat_id);
|
|
||||||
txn.rawUpdate(queryStr, queryArgs);
|
|
||||||
break;
|
|
||||||
// This event changes the permissions of the users and the power levels
|
|
||||||
case "m.room.power_levels":
|
|
||||||
String query = "UPDATE Rooms SET ";
|
|
||||||
if (eventContent["content"]["ban"] is num)
|
|
||||||
query += ", power_ban=" + eventContent["content"]["ban"].toString();
|
|
||||||
if (eventContent["content"]["events_default"] is num)
|
|
||||||
query += ", power_events_default=" +
|
|
||||||
eventContent["content"]["events_default"].toString();
|
|
||||||
if (eventContent["content"]["state_default"] is num)
|
|
||||||
query += ", power_state_default=" +
|
|
||||||
eventContent["content"]["state_default"].toString();
|
|
||||||
if (eventContent["content"]["redact"] is num)
|
|
||||||
query +=
|
|
||||||
", power_redact=" + eventContent["content"]["redact"].toString();
|
|
||||||
if (eventContent["content"]["invite"] is num)
|
|
||||||
query +=
|
|
||||||
", power_invite=" + eventContent["content"]["invite"].toString();
|
|
||||||
if (eventContent["content"]["kick"] is num)
|
|
||||||
query += ", power_kick=" + eventContent["content"]["kick"].toString();
|
|
||||||
if (eventContent["content"]["user_default"] is num)
|
|
||||||
query += ", power_user_default=" +
|
|
||||||
eventContent["content"]["user_default"].toString();
|
|
||||||
if (eventContent["content"]["events"] is Map<String, dynamic>) {
|
|
||||||
if (eventContent["content"]["events"]["m.room.avatar"] is num)
|
|
||||||
query += ", power_event_avatar=" +
|
|
||||||
eventContent["content"]["events"]["m.room.avatar"].toString();
|
|
||||||
if (eventContent["content"]["events"]["m.room.history_visibility"]
|
|
||||||
is num)
|
|
||||||
query += ", power_event_history_visibility=" +
|
|
||||||
eventContent["content"]["events"]["m.room.history_visibility"]
|
|
||||||
.toString();
|
|
||||||
if (eventContent["content"]["events"]["m.room.canonical_alias"]
|
|
||||||
is num)
|
|
||||||
query += ", power_event_canonical_alias=" +
|
|
||||||
eventContent["content"]["events"]["m.room.canonical_alias"]
|
|
||||||
.toString();
|
|
||||||
if (eventContent["content"]["events"]["m.room.aliases"] is num)
|
|
||||||
query += ", power_event_aliases=" +
|
|
||||||
eventContent["content"]["events"]["m.room.aliases"].toString();
|
|
||||||
if (eventContent["content"]["events"]["m.room.name"] is num)
|
|
||||||
query += ", power_event_name=" +
|
|
||||||
eventContent["content"]["events"]["m.room.name"].toString();
|
|
||||||
if (eventContent["content"]["events"]["m.room.power_levels"] is num)
|
|
||||||
query += ", power_event_power_levels=" +
|
|
||||||
eventContent["content"]["events"]["m.room.power_levels"]
|
|
||||||
.toString();
|
|
||||||
}
|
|
||||||
if (query != "UPDATE Rooms SET ") {
|
|
||||||
query = query.replaceFirst(",", "");
|
|
||||||
txn.rawUpdate(query + " WHERE id=?", [chat_id]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the users power levels:
|
|
||||||
if (eventContent["content"]["users"] is Map<String, dynamic>) {
|
|
||||||
eventContent["content"]["users"]
|
|
||||||
.forEach((String user, dynamic value) async {
|
|
||||||
num power_level = eventContent["content"]["users"][user];
|
|
||||||
txn.rawUpdate(
|
|
||||||
"UPDATE Users SET power_level=? WHERE matrix_id=? AND chat_id=?",
|
|
||||||
[power_level, user, chat_id]);
|
|
||||||
txn.rawInsert(
|
txn.rawInsert(
|
||||||
"INSERT OR IGNORE INTO Users VALUES(?, ?, '', '', ?, ?)",
|
"INSERT OR REPLACE INTO RoomStates VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||||
[chat_id, user, "unknown", power_level]);
|
[
|
||||||
});
|
eventContent["event_id"],
|
||||||
}
|
chat_id,
|
||||||
break;
|
eventContent["origin_server_ts"],
|
||||||
}
|
eventContent["sender"],
|
||||||
|
state_key,
|
||||||
|
json.encode(eventContent["unsigned"] ?? ""),
|
||||||
|
json.encode(eventContent["prev_content"] ?? ""),
|
||||||
|
eventContent["type"],
|
||||||
|
json.encode(eventContent["content"]),
|
||||||
|
]);
|
||||||
|
} else
|
||||||
|
txn.rawInsert("INSERT OR REPLACE INTO RoomAccountData VALUES(?, ?, ?)", [
|
||||||
|
eventContent["type"],
|
||||||
|
chat_id,
|
||||||
|
json.encode(eventContent["content"]),
|
||||||
|
]);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a User object by a given Matrix ID and a Room.
|
/// Returns a User object by a given Matrix ID and a Room.
|
||||||
Future<User> getUser({String matrixID, Room room}) async {
|
Future<User> getUser({String matrixID, Room room}) async {
|
||||||
List<Map<String, dynamic>> res = await db.rawQuery(
|
List<Map<String, dynamic>> res = await db.rawQuery(
|
||||||
"SELECT * FROM Users WHERE matrix_id=? AND chat_id=?",
|
"SELECT * FROM RoomStates WHERE state_key=? AND room_id=?",
|
||||||
[matrixID, room.id]);
|
[matrixID, room.id]);
|
||||||
if (res.length != 1) return null;
|
if (res.length != 1) return null;
|
||||||
return User.fromJson(res[0], room);
|
return RoomState.fromJson(res[0], room).asUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Loads all Users in the database to provide a contact list
|
/// Loads all Users in the database to provide a contact list
|
||||||
/// except users who are in the Room with the ID [exceptRoomID].
|
/// except users who are in the Room with the ID [exceptRoomID].
|
||||||
Future<List<User>> loadContacts({String exceptRoomID = ""}) async {
|
Future<List<User>> loadContacts({String exceptRoomID = ""}) async {
|
||||||
List<Map<String, dynamic>> res = await db.rawQuery(
|
List<Map<String, dynamic>> res = await db.rawQuery(
|
||||||
"SELECT * FROM Users WHERE matrix_id!=? AND chat_id!=? GROUP BY matrix_id ORDER BY displayname",
|
"SELECT * FROM RoomStates WHERE state_key LIKE '@%:%' AND state_key!=? AND room_id!=? GROUP BY state_key ORDER BY state_key",
|
||||||
[client.userID, exceptRoomID]);
|
[client.userID, exceptRoomID]);
|
||||||
List<User> userList = [];
|
List<User> userList = [];
|
||||||
for (int i = 0; i < res.length; i++)
|
for (int i = 0; i < res.length; i++)
|
||||||
userList.add(User.fromJson(res[i], Room(id: "", client: client)));
|
userList
|
||||||
|
.add(RoomState.fromJson(res[i], Room(id: "", client: client)).asUser);
|
||||||
return userList;
|
return userList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -482,15 +324,15 @@ class Store {
|
||||||
Future<List<User>> loadParticipants(Room room) async {
|
Future<List<User>> loadParticipants(Room room) async {
|
||||||
List<Map<String, dynamic>> res = await db.rawQuery(
|
List<Map<String, dynamic>> res = await db.rawQuery(
|
||||||
"SELECT * " +
|
"SELECT * " +
|
||||||
" FROM Users " +
|
" FROM RoomStates " +
|
||||||
" WHERE chat_id=? " +
|
" WHERE room_id=? " +
|
||||||
" AND membership='join'",
|
" AND type='m.room.member'",
|
||||||
[room.id]);
|
[room.id]);
|
||||||
|
|
||||||
List<User> participants = [];
|
List<User> participants = [];
|
||||||
|
|
||||||
for (num i = 0; i < res.length; i++) {
|
for (num i = 0; i < res.length; i++) {
|
||||||
participants.add(User.fromJson(res[i], room));
|
participants.add(RoomState.fromJson(res[i], room).asUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
return participants;
|
return participants;
|
||||||
|
@ -498,26 +340,18 @@ class Store {
|
||||||
|
|
||||||
/// Returns a list of events for the given room and sets all participants.
|
/// Returns a list of events for the given room and sets all participants.
|
||||||
Future<List<Event>> getEventList(Room room) async {
|
Future<List<Event>> getEventList(Room room) async {
|
||||||
List<Map<String, dynamic>> memberRes = await db.rawQuery(
|
|
||||||
"SELECT * " + " FROM Users " + " WHERE Users.chat_id=?", [room.id]);
|
|
||||||
Map<String, User> userMap = {};
|
|
||||||
for (num i = 0; i < memberRes.length; i++)
|
|
||||||
userMap[memberRes[i]["matrix_id"]] = User.fromJson(memberRes[i], room);
|
|
||||||
|
|
||||||
List<Map<String, dynamic>> eventRes = await db.rawQuery(
|
List<Map<String, dynamic>> eventRes = await db.rawQuery(
|
||||||
"SELECT * " +
|
"SELECT * " +
|
||||||
" FROM Events events " +
|
" FROM Events " +
|
||||||
" WHERE events.chat_id=?" +
|
" WHERE room_id=?" +
|
||||||
" GROUP BY events.id " +
|
" GROUP BY event_id " +
|
||||||
" ORDER BY origin_server_ts DESC",
|
" ORDER BY origin_server_ts DESC",
|
||||||
[room.id]);
|
[room.id]);
|
||||||
|
|
||||||
List<Event> eventList = [];
|
List<Event> eventList = [];
|
||||||
|
|
||||||
for (num i = 0; i < eventRes.length; i++)
|
for (num i = 0; i < eventRes.length; i++)
|
||||||
eventList.add(Event.fromJson(eventRes[i], room,
|
eventList.add(Event.fromJson(eventRes[i], room));
|
||||||
senderUser: userMap[eventRes[i]["sender"]],
|
|
||||||
stateKeyUser: userMap[eventRes[i]["state_key"]]));
|
|
||||||
|
|
||||||
return eventList;
|
return eventList;
|
||||||
}
|
}
|
||||||
|
@ -528,25 +362,17 @@ class Store {
|
||||||
bool onlyDirect = false,
|
bool onlyDirect = false,
|
||||||
bool onlyGroups = false}) async {
|
bool onlyGroups = false}) async {
|
||||||
if (onlyDirect && onlyGroups) return [];
|
if (onlyDirect && onlyGroups) return [];
|
||||||
List<Map<String, dynamic>> res = await db.rawQuery(
|
List<Map<String, dynamic>> res = await db.rawQuery("SELECT * " +
|
||||||
"SELECT rooms.*, events.origin_server_ts, events.content_json, events.type, events.sender, events.status, events.state_key " +
|
" FROM Rooms" +
|
||||||
" FROM Rooms rooms LEFT JOIN Events events " +
|
" WHERE membership" +
|
||||||
" ON rooms.id=events.chat_id " +
|
|
||||||
" WHERE rooms.membership" +
|
|
||||||
(onlyLeft ? "=" : "!=") +
|
(onlyLeft ? "=" : "!=") +
|
||||||
"'leave' " +
|
"'leave' " +
|
||||||
(onlyDirect ? " AND rooms.direct_chat_matrix_id!= '' " : "") +
|
" GROUP BY room_id ");
|
||||||
(onlyGroups ? " AND rooms.direct_chat_matrix_id= '' " : "") +
|
|
||||||
" GROUP BY rooms.id " +
|
|
||||||
" ORDER BY origin_server_ts DESC ");
|
|
||||||
List<Room> roomList = [];
|
List<Room> roomList = [];
|
||||||
for (num i = 0; i < res.length; i++) {
|
for (num i = 0; i < res.length; i++) {
|
||||||
try {
|
Room room = await Room.getRoomFromTableRow(res[i], client,
|
||||||
Room room = await Room.getRoomFromTableRow(res[i], client);
|
states: getStatesFromRoomId(res[i]["room_id"]));
|
||||||
roomList.add(room);
|
roomList.add(room);
|
||||||
} catch (e) {
|
|
||||||
print(e.toString());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return roomList;
|
return roomList;
|
||||||
}
|
}
|
||||||
|
@ -554,114 +380,47 @@ class Store {
|
||||||
/// Returns a room without events and participants.
|
/// Returns a room without events and participants.
|
||||||
Future<Room> getRoomById(String id) async {
|
Future<Room> getRoomById(String id) async {
|
||||||
List<Map<String, dynamic>> res =
|
List<Map<String, dynamic>> res =
|
||||||
await db.rawQuery("SELECT * FROM Rooms WHERE id=?", [id]);
|
await db.rawQuery("SELECT * FROM Rooms WHERE room_id=?", [id]);
|
||||||
if (res.length != 1) return null;
|
if (res.length != 1) return null;
|
||||||
return Room.getRoomFromTableRow(res[0], client);
|
return Room.getRoomFromTableRow(res[0], client,
|
||||||
|
states: getStatesFromRoomId(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a room without events and participants.
|
Future<List<Map<String, dynamic>>> getStatesFromRoomId(String id) async {
|
||||||
Future<Room> getRoomByAlias(String alias) async {
|
return db.rawQuery("SELECT * FROM RoomStates WHERE room_id=?", [id]);
|
||||||
List<Map<String, dynamic>> res = await db
|
|
||||||
.rawQuery("SELECT * FROM Rooms WHERE canonical_alias=?", [alias]);
|
|
||||||
if (res.length != 1) return null;
|
|
||||||
return Room.getRoomFromTableRow(res[0], client);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Calculates and returns an avatar for a direct chat by a given [roomID].
|
|
||||||
Future<String> getAvatarFromSingleChat(String roomID) async {
|
|
||||||
String avatarStr = "";
|
|
||||||
List<Map<String, dynamic>> res = await db.rawQuery(
|
|
||||||
"SELECT avatar_url FROM Users " +
|
|
||||||
" WHERE Users.chat_id=? " +
|
|
||||||
" AND (Users.membership='join' OR Users.membership='invite') " +
|
|
||||||
" AND Users.matrix_id!=? ",
|
|
||||||
[roomID, client.userID]);
|
|
||||||
if (res.length == 1) avatarStr = res[0]["avatar_url"];
|
|
||||||
return avatarStr;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Calculates a chat name for a groupchat without a name. The chat name will
|
|
||||||
/// be the name of all users (excluding the user of this client) divided by
|
|
||||||
/// ','.
|
|
||||||
Future<String> getChatNameFromMemberNames(String roomID) async {
|
|
||||||
String displayname = 'Empty chat';
|
|
||||||
List<Map<String, dynamic>> rs = await db.rawQuery(
|
|
||||||
"SELECT Users.displayname, Users.matrix_id, Users.membership FROM Users " +
|
|
||||||
" WHERE Users.chat_id=? " +
|
|
||||||
" AND (Users.membership='join' OR Users.membership='invite') " +
|
|
||||||
" AND Users.matrix_id!=? ",
|
|
||||||
[roomID, client.userID]);
|
|
||||||
if (rs.length > 0) {
|
|
||||||
displayname = "";
|
|
||||||
for (var i = 0; i < rs.length; i++) {
|
|
||||||
String username = rs[i]["displayname"];
|
|
||||||
if (username == "" || username == null) username = rs[i]["matrix_id"];
|
|
||||||
if (rs[i]["state_key"] != client.userID) displayname += username + ", ";
|
|
||||||
}
|
|
||||||
if (displayname == "" || displayname == null)
|
|
||||||
displayname = 'Empty chat';
|
|
||||||
else
|
|
||||||
displayname = displayname.substring(0, displayname.length - 2);
|
|
||||||
}
|
|
||||||
return displayname;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the (first) room ID from the store which is a private chat with
|
|
||||||
/// the user [userID]. Returns null if there is none.
|
|
||||||
Future<String> getDirectChatRoomID(String userID) async {
|
|
||||||
List<Map<String, dynamic>> res = await db.rawQuery(
|
|
||||||
"SELECT id FROM Rooms WHERE direct_chat_matrix_id=? AND membership!='leave' LIMIT 1",
|
|
||||||
[userID]);
|
|
||||||
if (res.length != 1) return null;
|
|
||||||
return res[0]["id"];
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the power level of the user for the given [roomID]. Returns null if
|
|
||||||
/// the room or the own user wasn't found.
|
|
||||||
Future<int> getPowerLevel(String roomID) async {
|
|
||||||
List<Map<String, dynamic>> res = await db.rawQuery(
|
|
||||||
"SELECT power_level FROM Users WHERE matrix_id=? AND chat_id=?",
|
|
||||||
[roomID, client.userID]);
|
|
||||||
if (res.length != 1) return null;
|
|
||||||
return res[0]["power_level"];
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the power levels from all users for the given [roomID].
|
|
||||||
Future<Map<String, int>> getPowerLevels(String roomID) async {
|
|
||||||
List<Map<String, dynamic>> res = await db.rawQuery(
|
|
||||||
"SELECT matrix_id, power_level FROM Users WHERE chat_id=?",
|
|
||||||
[roomID, client.userID]);
|
|
||||||
Map<String, int> powerMap = {};
|
|
||||||
for (int i = 0; i < res.length; i++)
|
|
||||||
powerMap[res[i]["matrix_id"]] = res[i]["power_level"];
|
|
||||||
return powerMap;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<Map<String, List<String>>> getAccountDataDirectChats() async {
|
|
||||||
Map<String, List<String>> directChats = {};
|
|
||||||
List<Map<String, dynamic>> res = await db.rawQuery(
|
|
||||||
"SELECT id, direct_chat_matrix_id FROM Rooms WHERE direct_chat_matrix_id!=''");
|
|
||||||
for (int i = 0; i < res.length; i++) {
|
|
||||||
if (directChats.containsKey(res[i]["direct_chat_matrix_id"]))
|
|
||||||
directChats[res[i]["direct_chat_matrix_id"]].add(res[i]["id"]);
|
|
||||||
else
|
|
||||||
directChats[res[i]["direct_chat_matrix_id"]] = [res[i]["id"]];
|
|
||||||
}
|
|
||||||
return directChats;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> forgetRoom(String roomID) async {
|
Future<void> forgetRoom(String roomID) async {
|
||||||
await db.rawDelete("DELETE FROM Rooms WHERE id=?", [roomID]);
|
await db.rawDelete("DELETE FROM Rooms WHERE room_id=?", [roomID]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Searches for the event in the store.
|
/// Searches for the event in the store.
|
||||||
Future<Event> getEventById(String eventID, Room room) async {
|
Future<Event> getEventById(String eventID, Room room) async {
|
||||||
List<Map<String, dynamic>> res = await db.rawQuery(
|
List<Map<String, dynamic>> res = await db.rawQuery(
|
||||||
"SELECT * FROM Events WHERE id=? AND chat_id=?", [eventID, room.id]);
|
"SELECT * FROM Events WHERE id=? AND room_id=?", [eventID, room.id]);
|
||||||
if (res.length == 0) return null;
|
if (res.length == 0) return null;
|
||||||
return Event.fromJson(res[0], room,
|
return Event.fromJson(res[0], room);
|
||||||
senderUser: (await room.getUserByMXID(res[0]["sender"])));
|
}
|
||||||
|
|
||||||
|
Future<Map<String, AccountData>> getAccountData() async {
|
||||||
|
Map<String, AccountData> newAccountData = {};
|
||||||
|
List<Map<String, dynamic>> rawAccountData =
|
||||||
|
await db.rawQuery("SELECT * FROM AccountData");
|
||||||
|
for (int i = 0; i < rawAccountData.length; i++)
|
||||||
|
newAccountData[rawAccountData[i]["type"]] =
|
||||||
|
AccountData.fromJson(rawAccountData[i]);
|
||||||
|
return newAccountData;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Map<String, Presence>> getPresences() async {
|
||||||
|
Map<String, Presence> newPresences = {};
|
||||||
|
List<Map<String, dynamic>> rawPresences =
|
||||||
|
await db.rawQuery("SELECT * FROM Presences");
|
||||||
|
for (int i = 0; i < rawPresences.length; i++)
|
||||||
|
newPresences[rawPresences[i]["type"]] =
|
||||||
|
Presence.fromJson(rawPresences[i]);
|
||||||
|
return newPresences;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future forgetNotification(String roomID) async {
|
Future forgetNotification(String roomID) async {
|
||||||
|
@ -679,7 +438,8 @@ class Store {
|
||||||
"INSERT INTO NotificationsCache(id, chat_id, event_id) VALUES (?, ?, ?)",
|
"INSERT INTO NotificationsCache(id, chat_id, event_id) VALUES (?, ?, ?)",
|
||||||
[uniqueID, roomID, event_id]);
|
[uniqueID, roomID, event_id]);
|
||||||
// Make sure we got the same unique ID everywhere
|
// Make sure we got the same unique ID everywhere
|
||||||
await db.rawUpdate("UPDATE NotificationsCache SET id=? WHERE chat_id=?", [uniqueID, roomID]);
|
await db.rawUpdate("UPDATE NotificationsCache SET id=? WHERE chat_id=?",
|
||||||
|
[uniqueID, roomID]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -707,70 +467,63 @@ class Store {
|
||||||
'UNIQUE(client))',
|
'UNIQUE(client))',
|
||||||
|
|
||||||
/// The database scheme for the Room class.
|
/// The database scheme for the Room class.
|
||||||
"Rooms": 'CREATE TABLE IF NOT EXISTS Rooms(' +
|
'Rooms': 'CREATE TABLE IF NOT EXISTS Rooms(' +
|
||||||
'id TEXT PRIMARY KEY, ' +
|
'room_id TEXT PRIMARY KEY, ' +
|
||||||
'membership TEXT, ' +
|
'membership TEXT, ' +
|
||||||
'topic TEXT, ' +
|
|
||||||
'highlight_count INTEGER, ' +
|
'highlight_count INTEGER, ' +
|
||||||
'notification_count INTEGER, ' +
|
'notification_count INTEGER, ' +
|
||||||
|
'prev_batch TEXT, ' +
|
||||||
'joined_member_count INTEGER, ' +
|
'joined_member_count INTEGER, ' +
|
||||||
'invited_member_count INTEGER, ' +
|
'invited_member_count INTEGER, ' +
|
||||||
'heroes TEXT, ' +
|
'heroes TEXT, ' +
|
||||||
'prev_batch TEXT, ' +
|
'UNIQUE(room_id))',
|
||||||
'avatar_url TEXT, ' +
|
|
||||||
'draft TEXT, ' +
|
|
||||||
'unread INTEGER, ' + // Timestamp of when the user has last read the chat
|
|
||||||
'fully_read TEXT, ' + // ID of the fully read marker event
|
|
||||||
'description TEXT, ' +
|
|
||||||
'canonical_alias TEXT, ' + // The address in the form: #roomname:homeserver.org
|
|
||||||
'direct_chat_matrix_id TEXT, ' + //If this room is a direct chat, this is the matrix ID of the user
|
|
||||||
'notification_settings TEXT, ' + // Must be one of [all, mention]
|
|
||||||
|
|
||||||
// Security rules
|
/// The database scheme for the TimelineEvent class.
|
||||||
'guest_access TEXT, ' +
|
'Events': 'CREATE TABLE IF NOT EXISTS Events(' +
|
||||||
'history_visibility TEXT, ' +
|
'event_id TEXT PRIMARY KEY, ' +
|
||||||
'join_rules TEXT, ' +
|
'room_id TEXT, ' +
|
||||||
|
'origin_server_ts INTEGER, ' +
|
||||||
|
'sender TEXT, ' +
|
||||||
|
'type TEXT, ' +
|
||||||
|
'unsigned TEXT, ' +
|
||||||
|
'content TEXT, ' +
|
||||||
|
'prev_content TEXT, ' +
|
||||||
|
'state_key TEXT, ' +
|
||||||
|
"status INTEGER, " +
|
||||||
|
'UNIQUE(event_id))',
|
||||||
|
|
||||||
// Power levels
|
/// The database scheme for room states.
|
||||||
'power_events_default INTEGER, ' +
|
'RoomStates': 'CREATE TABLE IF NOT EXISTS RoomStates(' +
|
||||||
'power_state_default INTEGER, ' +
|
'event_id TEXT PRIMARY KEY, ' +
|
||||||
'power_redact INTEGER, ' +
|
'room_id TEXT, ' +
|
||||||
'power_invite INTEGER, ' +
|
|
||||||
'power_ban INTEGER, ' +
|
|
||||||
'power_kick INTEGER, ' +
|
|
||||||
'power_user_default INTEGER, ' +
|
|
||||||
|
|
||||||
// Power levels for events
|
|
||||||
'power_event_avatar INTEGER, ' +
|
|
||||||
'power_event_history_visibility INTEGER, ' +
|
|
||||||
'power_event_canonical_alias INTEGER, ' +
|
|
||||||
'power_event_aliases INTEGER, ' +
|
|
||||||
'power_event_name INTEGER, ' +
|
|
||||||
'power_event_power_levels INTEGER, ' +
|
|
||||||
'UNIQUE(id))',
|
|
||||||
|
|
||||||
/// The database scheme for the Event class.
|
|
||||||
"Events": 'CREATE TABLE IF NOT EXISTS Events(' +
|
|
||||||
'id TEXT PRIMARY KEY, ' +
|
|
||||||
'chat_id TEXT, ' +
|
|
||||||
'origin_server_ts INTEGER, ' +
|
'origin_server_ts INTEGER, ' +
|
||||||
'sender TEXT, ' +
|
'sender TEXT, ' +
|
||||||
'state_key TEXT, ' +
|
'state_key TEXT, ' +
|
||||||
'content_body TEXT, ' +
|
'unsigned TEXT, ' +
|
||||||
|
'prev_content TEXT, ' +
|
||||||
'type TEXT, ' +
|
'type TEXT, ' +
|
||||||
'content_json TEXT, ' +
|
'content TEXT, ' +
|
||||||
"status INTEGER, " +
|
'UNIQUE(room_id,state_key,type))',
|
||||||
'UNIQUE(id))',
|
|
||||||
|
|
||||||
/// The database scheme for the User class.
|
/// The database scheme for room states.
|
||||||
"Users": 'CREATE TABLE IF NOT EXISTS Users(' +
|
'AccountData': 'CREATE TABLE IF NOT EXISTS AccountData(' +
|
||||||
'chat_id TEXT, ' + // The chat id of this membership
|
'type TEXT PRIMARY KEY, ' +
|
||||||
'matrix_id TEXT, ' + // The matrix id of this user
|
'content TEXT, ' +
|
||||||
'displayname TEXT, ' +
|
'UNIQUE(type))',
|
||||||
'avatar_url TEXT, ' +
|
|
||||||
'membership TEXT, ' + // The status of the membership. Must be one of [join, invite, ban, leave]
|
/// The database scheme for room states.
|
||||||
'power_level INTEGER, ' + // The power level of this user. Must be in [0,..,100]
|
'RoomAccountData': 'CREATE TABLE IF NOT EXISTS RoomAccountData(' +
|
||||||
'UNIQUE(chat_id, matrix_id))',
|
'type TEXT PRIMARY KEY, ' +
|
||||||
|
'room_id TEXT, ' +
|
||||||
|
'content TEXT, ' +
|
||||||
|
'UNIQUE(type,room_id))',
|
||||||
|
|
||||||
|
/// The database scheme for room states.
|
||||||
|
'Presences': 'CREATE TABLE IF NOT EXISTS Presences(' +
|
||||||
|
'type TEXT PRIMARY KEY, ' +
|
||||||
|
'sender TEXT, ' +
|
||||||
|
'content TEXT, ' +
|
||||||
|
'UNIQUE(sender))',
|
||||||
|
|
||||||
/// The database scheme for the NotificationsCache class.
|
/// The database scheme for the NotificationsCache class.
|
||||||
"NotificationsCache": 'CREATE TABLE IF NOT EXISTS NotificationsCache(' +
|
"NotificationsCache": 'CREATE TABLE IF NOT EXISTS NotificationsCache(' +
|
||||||
|
|
|
@ -28,6 +28,9 @@ import 'Room.dart';
|
||||||
import 'User.dart';
|
import 'User.dart';
|
||||||
import 'sync/EventUpdate.dart';
|
import 'sync/EventUpdate.dart';
|
||||||
|
|
||||||
|
typedef onTimelineUpdateCallback = void Function();
|
||||||
|
typedef onTimelineInsertCallback = void Function(int insertID);
|
||||||
|
|
||||||
/// Represents the timeline of a room. The callbacks [onUpdate], [onDelete],
|
/// Represents the timeline of a room. The callbacks [onUpdate], [onDelete],
|
||||||
/// [onInsert] and [onResort] will be triggered automatically. The initial
|
/// [onInsert] and [onResort] will be triggered automatically. The initial
|
||||||
/// event list will be retreived when created by the [room.getTimeline] method.
|
/// event list will be retreived when created by the [room.getTimeline] method.
|
||||||
|
@ -47,8 +50,8 @@ class Timeline {
|
||||||
int _findEvent({String event_id, String unsigned_txid}) {
|
int _findEvent({String event_id, String unsigned_txid}) {
|
||||||
int i;
|
int i;
|
||||||
for (i = 0; i < events.length; i++) {
|
for (i = 0; i < events.length; i++) {
|
||||||
if (events[i].id == event_id ||
|
if (events[i].eventId == event_id ||
|
||||||
(unsigned_txid != null && events[i].id == unsigned_txid)) break;
|
(unsigned_txid != null && events[i].eventId == unsigned_txid)) break;
|
||||||
}
|
}
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
|
@ -82,33 +85,7 @@ class Timeline {
|
||||||
eventUpdate.content["avatar_url"] = senderUser.avatarUrl.mxc;
|
eventUpdate.content["avatar_url"] = senderUser.avatarUrl.mxc;
|
||||||
}
|
}
|
||||||
|
|
||||||
User stateKeyUser;
|
|
||||||
if (eventUpdate.content.containsKey("state_key")) {
|
|
||||||
stateKeyUser = await room.client.store?.getUser(
|
|
||||||
matrixID: eventUpdate.content["state_key"], room: room);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (senderUser != null && stateKeyUser != null) {
|
|
||||||
newEvent = Event.fromJson(eventUpdate.content, room,
|
|
||||||
senderUser: senderUser, stateKeyUser: stateKeyUser);
|
|
||||||
} else if (senderUser != null) {
|
|
||||||
newEvent = Event.fromJson(eventUpdate.content, room,
|
|
||||||
senderUser: senderUser);
|
|
||||||
} else if (stateKeyUser != null) {
|
|
||||||
newEvent = Event.fromJson(eventUpdate.content, room,
|
|
||||||
stateKeyUser: stateKeyUser);
|
|
||||||
} else {
|
|
||||||
newEvent = Event.fromJson(eventUpdate.content, room);
|
newEvent = Event.fromJson(eventUpdate.content, room);
|
||||||
}
|
|
||||||
|
|
||||||
// TODO update to type check when https://gitlab.com/famedly/famedlysdk/merge_requests/28/ is merged
|
|
||||||
if (newEvent.content.containsKey("m.relates_to")) {
|
|
||||||
Map<String, dynamic> relates_to = newEvent.content["m.relates_to"];
|
|
||||||
if (relates_to.containsKey("m.in_reply_to")) {
|
|
||||||
newEvent.replyEvent = await room.getEventById(newEvent
|
|
||||||
.content["m.relates_to"]["m.in_reply_to"]["event_id"]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
events.insert(0, newEvent);
|
events.insert(0, newEvent);
|
||||||
if (onInsert != null) onInsert(0);
|
if (onInsert != null) onInsert(0);
|
||||||
|
@ -128,6 +105,3 @@ class Timeline {
|
||||||
if (onUpdate != null) onUpdate();
|
if (onUpdate != null) onUpdate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef onTimelineUpdateCallback = void Function();
|
|
||||||
typedef onTimelineInsertCallback = void Function(int insertID);
|
|
||||||
|
|
|
@ -22,7 +22,9 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import 'package:famedlysdk/src/Room.dart';
|
import 'package:famedlysdk/src/Room.dart';
|
||||||
|
import 'package:famedlysdk/src/RoomState.dart';
|
||||||
import 'package:famedlysdk/src/responses/ErrorResponse.dart';
|
import 'package:famedlysdk/src/responses/ErrorResponse.dart';
|
||||||
|
import 'package:famedlysdk/src/utils/ChatTime.dart';
|
||||||
import 'package:famedlysdk/src/utils/MxContent.dart';
|
import 'package:famedlysdk/src/utils/MxContent.dart';
|
||||||
|
|
||||||
import 'Connection.dart';
|
import 'Connection.dart';
|
||||||
|
@ -30,85 +32,80 @@ import 'Connection.dart';
|
||||||
enum Membership { join, invite, leave, ban }
|
enum Membership { join, invite, leave, ban }
|
||||||
|
|
||||||
/// Represents a Matrix User which may be a participant in a Matrix Room.
|
/// Represents a Matrix User which may be a participant in a Matrix Room.
|
||||||
class User {
|
class User extends RoomState {
|
||||||
|
factory User(
|
||||||
|
String id, {
|
||||||
|
String membership,
|
||||||
|
String displayName,
|
||||||
|
String avatarUrl,
|
||||||
|
Room room,
|
||||||
|
}) {
|
||||||
|
Map<String, String> content = {};
|
||||||
|
if (membership != null) content["membership"] = membership;
|
||||||
|
if (displayName != null) content["displayname"] = displayName;
|
||||||
|
if (avatarUrl != null) content["avatar_url"] = avatarUrl;
|
||||||
|
return User.fromState(
|
||||||
|
stateKey: id,
|
||||||
|
content: content,
|
||||||
|
typeKey: "m.room.member",
|
||||||
|
roomId: room?.id,
|
||||||
|
room: room,
|
||||||
|
time: ChatTime.now(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
User.fromState(
|
||||||
|
{dynamic prevContent,
|
||||||
|
String stateKey,
|
||||||
|
dynamic content,
|
||||||
|
String typeKey,
|
||||||
|
String eventId,
|
||||||
|
String roomId,
|
||||||
|
String senderId,
|
||||||
|
ChatTime time,
|
||||||
|
dynamic unsigned,
|
||||||
|
Room room})
|
||||||
|
: super(
|
||||||
|
stateKey: stateKey,
|
||||||
|
prevContent: prevContent,
|
||||||
|
content: content,
|
||||||
|
typeKey: typeKey,
|
||||||
|
eventId: eventId,
|
||||||
|
roomId: roomId,
|
||||||
|
senderId: senderId,
|
||||||
|
time: time,
|
||||||
|
unsigned: unsigned,
|
||||||
|
room: room);
|
||||||
|
|
||||||
/// The full qualified Matrix ID in the format @username:server.abc.
|
/// The full qualified Matrix ID in the format @username:server.abc.
|
||||||
final String id;
|
String get id => stateKey;
|
||||||
|
|
||||||
/// The displayname of the user if the user has set one.
|
/// The displayname of the user if the user has set one.
|
||||||
final String displayName;
|
String get displayName => content != null ? content["displayname"] : null;
|
||||||
|
|
||||||
/// The membership status of the user. One of:
|
/// The membership status of the user. One of:
|
||||||
/// join
|
/// join
|
||||||
/// invite
|
/// invite
|
||||||
/// leave
|
/// leave
|
||||||
/// ban
|
/// ban
|
||||||
Membership membership;
|
Membership get membership => Membership.values.firstWhere((e) {
|
||||||
|
if (content["membership"] != null) {
|
||||||
|
return e.toString() == 'Membership.' + content['membership'];
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
/// The avatar if the user has one.
|
/// The avatar if the user has one.
|
||||||
MxContent avatarUrl;
|
MxContent get avatarUrl => content != null && content["avatar_url"] is String
|
||||||
|
? MxContent(content["avatar_url"])
|
||||||
/// The powerLevel of the user. Normally:
|
: MxContent("");
|
||||||
/// 0=Normal user
|
|
||||||
/// 50=Moderator
|
|
||||||
/// 100=Admin
|
|
||||||
int powerLevel = 0;
|
|
||||||
|
|
||||||
/// All users normally belong to a room.
|
|
||||||
final Room room;
|
|
||||||
|
|
||||||
@Deprecated("Use membership instead!")
|
|
||||||
String get status => membership.toString().split('.').last;
|
|
||||||
|
|
||||||
@Deprecated("Use ID instead!")
|
|
||||||
String get mxid => id;
|
|
||||||
|
|
||||||
@Deprecated("Use avatarUrl instead!")
|
|
||||||
MxContent get avatar_url => avatarUrl;
|
|
||||||
|
|
||||||
User(
|
|
||||||
String id, {
|
|
||||||
this.membership,
|
|
||||||
this.displayName,
|
|
||||||
this.avatarUrl,
|
|
||||||
this.powerLevel,
|
|
||||||
this.room,
|
|
||||||
}) : this.id = id ?? "";
|
|
||||||
|
|
||||||
/// Returns the displayname or the local part of the Matrix ID if the user
|
/// Returns the displayname or the local part of the Matrix ID if the user
|
||||||
/// has no displayname.
|
/// has no displayname.
|
||||||
String calcDisplayname() => (displayName == null || displayName.isEmpty)
|
String calcDisplayname() => (displayName == null || displayName.isEmpty)
|
||||||
? id.replaceFirst("@", "").split(":")[0]
|
? stateKey.replaceFirst("@", "").split(":")[0]
|
||||||
: displayName;
|
: displayName;
|
||||||
|
|
||||||
/// 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'] ?? json['sender'],
|
|
||||||
displayName: json['displayname'],
|
|
||||||
avatarUrl: MxContent(json['avatar_url']),
|
|
||||||
membership: Membership.values.firstWhere((e) {
|
|
||||||
if (json["membership"] != null) {
|
|
||||||
return e.toString() == 'Membership.' + json['membership'];
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}, orElse: () => null),
|
|
||||||
powerLevel: json['power_level'],
|
|
||||||
room: room);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Checks if the client's user has the permission to kick this user.
|
|
||||||
Future<bool> get canKick async {
|
|
||||||
final int ownPowerLevel = await room.client.store.getPowerLevel(room.id);
|
|
||||||
return ownPowerLevel > powerLevel &&
|
|
||||||
ownPowerLevel >= room.powerLevels["power_kick"];
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Checks if the client's user has the permission to ban or unban this user.
|
|
||||||
Future<bool> get canBan async {
|
|
||||||
final int ownPowerLevel = await room.client.store.getPowerLevel(room.id);
|
|
||||||
return ownPowerLevel > powerLevel &&
|
|
||||||
ownPowerLevel >= room.powerLevels["power_ban"];
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Call the Matrix API to kick this user from this room.
|
/// Call the Matrix API to kick this user from this room.
|
||||||
Future<dynamic> kick() async {
|
Future<dynamic> kick() async {
|
||||||
dynamic res = await room.kick(id);
|
dynamic res = await room.kick(id);
|
||||||
|
@ -137,7 +134,7 @@ class User {
|
||||||
/// Returns null on error.
|
/// Returns null on error.
|
||||||
Future<String> startDirectChat() async {
|
Future<String> startDirectChat() async {
|
||||||
// Try to find an existing direct chat
|
// Try to find an existing direct chat
|
||||||
String roomID = await room.client?.store?.getDirectChatRoomID(id);
|
String roomID = await room.client?.getDirectChatFromUserId(id);
|
||||||
if (roomID != null) return roomID;
|
if (roomID != null) return roomID;
|
||||||
|
|
||||||
// Start a new direct chat
|
// Start a new direct chat
|
||||||
|
|
|
@ -59,7 +59,6 @@ class ChatTime {
|
||||||
return toTimeString();
|
return toTimeString();
|
||||||
} else if (sameWeek) {
|
} else if (sameWeek) {
|
||||||
switch (dateTime.weekday) {
|
switch (dateTime.weekday) {
|
||||||
// TODO: Needs localization
|
|
||||||
case 1:
|
case 1:
|
||||||
return "Montag";
|
return "Montag";
|
||||||
case 2:
|
case 2:
|
||||||
|
|
|
@ -23,8 +23,10 @@
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:famedlysdk/src/AccountData.dart';
|
||||||
import 'package:famedlysdk/src/Client.dart';
|
import 'package:famedlysdk/src/Client.dart';
|
||||||
import 'package:famedlysdk/src/Connection.dart';
|
import 'package:famedlysdk/src/Connection.dart';
|
||||||
|
import 'package:famedlysdk/src/Presence.dart';
|
||||||
import 'package:famedlysdk/src/User.dart';
|
import 'package:famedlysdk/src/User.dart';
|
||||||
import 'package:famedlysdk/src/requests/SetPushersRequest.dart';
|
import 'package:famedlysdk/src/requests/SetPushersRequest.dart';
|
||||||
import 'package:famedlysdk/src/responses/ErrorResponse.dart';
|
import 'package:famedlysdk/src/responses/ErrorResponse.dart';
|
||||||
|
@ -61,10 +63,19 @@ void main() {
|
||||||
Future<ErrorResponse> errorFuture =
|
Future<ErrorResponse> errorFuture =
|
||||||
matrix.connection.onError.stream.first;
|
matrix.connection.onError.stream.first;
|
||||||
|
|
||||||
|
int presenceCounter = 0;
|
||||||
|
int accountDataCounter = 0;
|
||||||
|
matrix.onPresence = (Presence data) {
|
||||||
|
presenceCounter++;
|
||||||
|
};
|
||||||
|
matrix.onAccountData = (AccountData data) {
|
||||||
|
accountDataCounter++;
|
||||||
|
};
|
||||||
|
|
||||||
final bool checkResp1 =
|
final bool checkResp1 =
|
||||||
await matrix.checkServer("https://fakeServer.wrongaddress");
|
await matrix.checkServer("https://fakeserver.wrongaddress");
|
||||||
final bool checkResp2 =
|
final bool checkResp2 =
|
||||||
await matrix.checkServer("https://fakeServer.notExisting");
|
await matrix.checkServer("https://fakeserver.notexisting");
|
||||||
|
|
||||||
ErrorResponse checkError = await errorFuture;
|
ErrorResponse checkError = await errorFuture;
|
||||||
|
|
||||||
|
@ -107,6 +118,23 @@ void main() {
|
||||||
expect(loginState, LoginState.logged);
|
expect(loginState, LoginState.logged);
|
||||||
expect(firstSync, true);
|
expect(firstSync, true);
|
||||||
expect(sync["next_batch"] == matrix.prevBatch, true);
|
expect(sync["next_batch"] == matrix.prevBatch, true);
|
||||||
|
|
||||||
|
expect(matrix.accountData.length, 2);
|
||||||
|
expect(matrix.getDirectChatFromUserId("@bob:example.com"),
|
||||||
|
"!726s6s6q:example.com");
|
||||||
|
expect(matrix.roomList.rooms[1].directChatMatrixID, "@bob:example.com");
|
||||||
|
expect(matrix.directChats, matrix.accountData["m.direct"].content);
|
||||||
|
expect(matrix.presences.length, 1);
|
||||||
|
expect(matrix.roomList.rooms.length, 2);
|
||||||
|
expect(matrix.roomList.rooms[1].canonicalAlias,
|
||||||
|
"#famedlyContactDiscovery:${matrix.userID.split(":")[1]}");
|
||||||
|
final List<User> contacts = await matrix.loadFamedlyContacts();
|
||||||
|
expect(contacts.length, 1);
|
||||||
|
expect(contacts[0].senderId, "@alice:example.org");
|
||||||
|
expect(
|
||||||
|
matrix.presences["@alice:example.com"].content["presence"], "online");
|
||||||
|
expect(presenceCounter, 1);
|
||||||
|
expect(accountDataCounter, 2);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Try to get ErrorResponse', () async {
|
test('Try to get ErrorResponse', () async {
|
||||||
|
@ -172,36 +200,39 @@ void main() {
|
||||||
|
|
||||||
List<EventUpdate> eventUpdateList = await eventUpdateListFuture;
|
List<EventUpdate> eventUpdateList = await eventUpdateListFuture;
|
||||||
|
|
||||||
expect(eventUpdateList.length, 7);
|
expect(eventUpdateList.length, 8);
|
||||||
|
|
||||||
expect(eventUpdateList[0].eventType == "m.room.member", true);
|
expect(eventUpdateList[0].eventType, "m.room.member");
|
||||||
expect(eventUpdateList[0].roomID == "!726s6s6q:example.com", true);
|
expect(eventUpdateList[0].roomID, "!726s6s6q:example.com");
|
||||||
expect(eventUpdateList[0].type == "state", true);
|
expect(eventUpdateList[0].type, "state");
|
||||||
|
|
||||||
expect(eventUpdateList[1].eventType == "m.room.member", true);
|
expect(eventUpdateList[1].eventType, "m.room.canonical_alias");
|
||||||
expect(eventUpdateList[1].roomID == "!726s6s6q:example.com", true);
|
expect(eventUpdateList[1].roomID, "!726s6s6q:example.com");
|
||||||
expect(eventUpdateList[1].type == "timeline", true);
|
expect(eventUpdateList[1].type, "state");
|
||||||
|
|
||||||
expect(eventUpdateList[2].eventType == "m.room.message", true);
|
expect(eventUpdateList[2].eventType, "m.room.member");
|
||||||
expect(eventUpdateList[2].roomID == "!726s6s6q:example.com", true);
|
expect(eventUpdateList[2].roomID, "!726s6s6q:example.com");
|
||||||
expect(eventUpdateList[2].type == "timeline", true);
|
expect(eventUpdateList[2].type, "timeline");
|
||||||
|
|
||||||
expect(eventUpdateList[3].eventType == "m.tag", true);
|
expect(eventUpdateList[3].eventType, "m.room.message");
|
||||||
expect(eventUpdateList[3].roomID == "!726s6s6q:example.com", true);
|
expect(eventUpdateList[3].roomID, "!726s6s6q:example.com");
|
||||||
expect(eventUpdateList[3].type == "account_data", true);
|
expect(eventUpdateList[3].type, "timeline");
|
||||||
|
|
||||||
expect(eventUpdateList[4].eventType == "org.example.custom.room.config",
|
expect(eventUpdateList[4].eventType, "m.tag");
|
||||||
true);
|
expect(eventUpdateList[4].roomID, "!726s6s6q:example.com");
|
||||||
expect(eventUpdateList[4].roomID == "!726s6s6q:example.com", true);
|
expect(eventUpdateList[4].type, "account_data");
|
||||||
expect(eventUpdateList[4].type == "account_data", true);
|
|
||||||
|
|
||||||
expect(eventUpdateList[5].eventType == "m.room.name", true);
|
expect(eventUpdateList[5].eventType, "org.example.custom.room.config");
|
||||||
expect(eventUpdateList[5].roomID == "!696r7674:example.com", true);
|
expect(eventUpdateList[5].roomID, "!726s6s6q:example.com");
|
||||||
expect(eventUpdateList[5].type == "invite_state", true);
|
expect(eventUpdateList[5].type, "account_data");
|
||||||
|
|
||||||
expect(eventUpdateList[6].eventType == "m.room.member", true);
|
expect(eventUpdateList[6].eventType, "m.room.name");
|
||||||
expect(eventUpdateList[6].roomID == "!696r7674:example.com", true);
|
expect(eventUpdateList[6].roomID, "!696r7674:example.com");
|
||||||
expect(eventUpdateList[6].type == "invite_state", true);
|
expect(eventUpdateList[6].type, "invite_state");
|
||||||
|
|
||||||
|
expect(eventUpdateList[7].eventType, "m.room.member");
|
||||||
|
expect(eventUpdateList[7].roomID, "!696r7674:example.com");
|
||||||
|
expect(eventUpdateList[7].type, "invite_state");
|
||||||
});
|
});
|
||||||
|
|
||||||
test('User Update Test', () async {
|
test('User Update Test', () async {
|
||||||
|
@ -209,16 +240,13 @@ void main() {
|
||||||
|
|
||||||
List<UserUpdate> eventUpdateList = await userUpdateListFuture;
|
List<UserUpdate> eventUpdateList = await userUpdateListFuture;
|
||||||
|
|
||||||
expect(eventUpdateList.length, 3);
|
expect(eventUpdateList.length, 4);
|
||||||
|
|
||||||
expect(eventUpdateList[0].eventType == "m.presence", true);
|
expect(eventUpdateList[0].eventType == "m.presence", true);
|
||||||
expect(eventUpdateList[0].type == "presence", true);
|
expect(eventUpdateList[0].type == "presence", true);
|
||||||
|
|
||||||
expect(eventUpdateList[1].eventType == "org.example.custom.config", true);
|
expect(eventUpdateList[1].eventType == "org.example.custom.config", true);
|
||||||
expect(eventUpdateList[1].type == "account_data", true);
|
expect(eventUpdateList[1].type == "account_data", true);
|
||||||
|
|
||||||
expect(eventUpdateList[2].eventType == "m.new_device", true);
|
|
||||||
expect(eventUpdateList[2].type == "to_device", true);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('should get created', create);
|
testWidgets('should get created', create);
|
||||||
|
|
|
@ -24,8 +24,7 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:famedlysdk/famedlysdk.dart';
|
import 'package:famedlysdk/famedlysdk.dart';
|
||||||
import 'package:famedlysdk/src/Event.dart';
|
import 'package:famedlysdk/src/RoomState.dart';
|
||||||
import 'package:famedlysdk/src/User.dart';
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
import 'FakeMatrixApi.dart';
|
import 'FakeMatrixApi.dart';
|
||||||
|
@ -36,9 +35,6 @@ void main() {
|
||||||
final int timestamp = DateTime.now().millisecondsSinceEpoch;
|
final int timestamp = DateTime.now().millisecondsSinceEpoch;
|
||||||
final String id = "!4fsdfjisjf:server.abc";
|
final String id = "!4fsdfjisjf:server.abc";
|
||||||
final String senderID = "@alice:server.abc";
|
final String senderID = "@alice:server.abc";
|
||||||
final String senderDisplayname = "Alice";
|
|
||||||
final String empty = "";
|
|
||||||
final Membership membership = Membership.join;
|
|
||||||
final String type = "m.room.message";
|
final String type = "m.room.message";
|
||||||
final String msgtype = "m.text";
|
final String msgtype = "m.text";
|
||||||
final String body = "Hello World";
|
final String body = "Hello World";
|
||||||
|
@ -49,29 +45,29 @@ void main() {
|
||||||
|
|
||||||
Map<String, dynamic> jsonObj = {
|
Map<String, dynamic> jsonObj = {
|
||||||
"event_id": id,
|
"event_id": id,
|
||||||
"matrix_id": senderID,
|
"sender": senderID,
|
||||||
"displayname": senderDisplayname,
|
|
||||||
"avatar_url": empty,
|
|
||||||
"membership": membership.toString().split('.').last,
|
|
||||||
"origin_server_ts": timestamp,
|
"origin_server_ts": timestamp,
|
||||||
"state_key": empty,
|
|
||||||
"type": type,
|
"type": type,
|
||||||
"content_json": contentJson,
|
"status": 2,
|
||||||
|
"content": contentJson,
|
||||||
};
|
};
|
||||||
|
|
||||||
test("Create from json", () async {
|
test("Create from json", () async {
|
||||||
Event event = Event.fromJson(jsonObj, null);
|
Event event = Event.fromJson(jsonObj, null);
|
||||||
|
|
||||||
expect(event.id, id);
|
expect(event.eventId, id);
|
||||||
expect(event.sender.id, senderID);
|
expect(event.senderId, senderID);
|
||||||
expect(event.sender.displayName, senderDisplayname);
|
|
||||||
expect(event.sender.avatarUrl.mxc, empty);
|
|
||||||
expect(event.sender.membership, membership);
|
|
||||||
expect(event.status, 2);
|
expect(event.status, 2);
|
||||||
expect(event.text, body);
|
expect(event.text, body);
|
||||||
expect(event.formattedText, formatted_body);
|
expect(event.formattedText, formatted_body);
|
||||||
expect(event.getBody(), body);
|
expect(event.getBody(), body);
|
||||||
expect(event.type, EventTypes.Text);
|
expect(event.type, EventTypes.Text);
|
||||||
|
jsonObj["state_key"] = "";
|
||||||
|
RoomState state = RoomState.fromJson(jsonObj, null);
|
||||||
|
expect(state.eventId, id);
|
||||||
|
expect(state.stateKey, "");
|
||||||
|
expect(state.key, "m.room.message");
|
||||||
|
expect(state.timelineEvent.status, 1);
|
||||||
});
|
});
|
||||||
test("Test all EventTypes", () async {
|
test("Test all EventTypes", () async {
|
||||||
Event event;
|
Event event;
|
||||||
|
@ -121,7 +117,7 @@ void main() {
|
||||||
expect(event.type, EventTypes.HistoryVisibility);
|
expect(event.type, EventTypes.HistoryVisibility);
|
||||||
|
|
||||||
jsonObj["type"] = "m.room.message";
|
jsonObj["type"] = "m.room.message";
|
||||||
jsonObj["content"] = json.decode(jsonObj["content_json"]);
|
jsonObj["content"] = json.decode(jsonObj["content"]);
|
||||||
|
|
||||||
jsonObj["content"]["msgtype"] = "m.notice";
|
jsonObj["content"]["msgtype"] = "m.notice";
|
||||||
event = Event.fromJson(jsonObj, null);
|
event = Event.fromJson(jsonObj, null);
|
||||||
|
|
|
@ -39,7 +39,7 @@ class FakeMatrixApi extends MockClient {
|
||||||
method == "GET" ? request.url.queryParameters : request.body;
|
method == "GET" ? request.url.queryParameters : request.body;
|
||||||
var res = {};
|
var res = {};
|
||||||
|
|
||||||
print("$method request to $action with Data: $data");
|
//print("$method request to $action with Data: $data");
|
||||||
|
|
||||||
// Sync requests with timeout
|
// Sync requests with timeout
|
||||||
if (data is Map<String, dynamic> && data["timeout"] is String) {
|
if (data is Map<String, dynamic> && data["timeout"] is String) {
|
||||||
|
@ -64,6 +64,20 @@ class FakeMatrixApi extends MockClient {
|
||||||
|
|
||||||
static final Map<String, Map<String, dynamic>> api = {
|
static final Map<String, Map<String, dynamic>> api = {
|
||||||
"GET": {
|
"GET": {
|
||||||
|
"/client/r0/rooms/!localpart:server.abc/state/m.room.member/@getme:example.com":
|
||||||
|
(var req) => {
|
||||||
|
"content": {
|
||||||
|
"membership": "join",
|
||||||
|
"displayname": "You got me",
|
||||||
|
},
|
||||||
|
"type": "m.room.member",
|
||||||
|
"event_id": "143273582443PhrSn:example.org",
|
||||||
|
"room_id": "!localpart:server.abc",
|
||||||
|
"sender": "@getme:example.com",
|
||||||
|
"state_key": "@getme:example.com",
|
||||||
|
"origin_server_ts": 1432735824653,
|
||||||
|
"unsigned": {"age": 1234}
|
||||||
|
},
|
||||||
"/client/r0/rooms/!localpart:server.abc/event/1234": (var req) => {
|
"/client/r0/rooms/!localpart:server.abc/event/1234": (var req) => {
|
||||||
"content": {
|
"content": {
|
||||||
"body": "This is an example text message",
|
"body": "This is an example text message",
|
||||||
|
@ -78,7 +92,7 @@ class FakeMatrixApi extends MockClient {
|
||||||
"origin_server_ts": 1432735824653,
|
"origin_server_ts": 1432735824653,
|
||||||
"unsigned": {"age": 1234}
|
"unsigned": {"age": 1234}
|
||||||
},
|
},
|
||||||
"/client/r0/rooms/!1234:example.com/messages?from=1234&dir=b&limit=100":
|
"/client/r0/rooms/!1234:example.com/messages?from=1234&dir=b&limit=100&filter=%7B%22room%22:%7B%22state%22:%7B%22lazy_load_members%22:true%7D%7D%7D":
|
||||||
(var req) => {
|
(var req) => {
|
||||||
"start": "t47429-4392820_219380_26003_2265",
|
"start": "t47429-4392820_219380_26003_2265",
|
||||||
"end": "t47409-4357353_219380_26003_2265",
|
"end": "t47409-4357353_219380_26003_2265",
|
||||||
|
@ -153,6 +167,24 @@ class FakeMatrixApi extends MockClient {
|
||||||
{"type": "m.login.password"}
|
{"type": "m.login.password"}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"/client/r0/rooms/!726s6s6q:example.com/members": (var req) => {
|
||||||
|
"chunk": [
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"membership": "join",
|
||||||
|
"avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF",
|
||||||
|
"displayname": "Alice Margatroid"
|
||||||
|
},
|
||||||
|
"type": "m.room.member",
|
||||||
|
"event_id": "§143273582443PhrSn:example.org",
|
||||||
|
"room_id": "!636q39766251:example.com",
|
||||||
|
"sender": "@alice:example.org",
|
||||||
|
"origin_server_ts": 1432735824653,
|
||||||
|
"unsigned": {"age": 1234},
|
||||||
|
"state_key": "@alice:example.org"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"/client/r0/rooms/!localpart:server.abc/members": (var req) => {
|
"/client/r0/rooms/!localpart:server.abc/members": (var req) => {
|
||||||
"chunk": [
|
"chunk": [
|
||||||
{
|
{
|
||||||
|
@ -333,7 +365,16 @@ class FakeMatrixApi extends MockClient {
|
||||||
{
|
{
|
||||||
"type": "org.example.custom.config",
|
"type": "org.example.custom.config",
|
||||||
"content": {"custom_config_key": "custom_config_value"}
|
"content": {"custom_config_key": "custom_config_value"}
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"@bob:example.com": [
|
||||||
|
"!726s6s6q:example.com",
|
||||||
|
"!hgfedcba:example.com"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"type": "m.direct"
|
||||||
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"to_device": {
|
"to_device": {
|
||||||
|
@ -364,6 +405,17 @@ class FakeMatrixApi extends MockClient {
|
||||||
"content": {"membership": "join"},
|
"content": {"membership": "join"},
|
||||||
"origin_server_ts": 1417731086795,
|
"origin_server_ts": 1417731086795,
|
||||||
"event_id": "66697273743031:example.com"
|
"event_id": "66697273743031:example.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"sender": "@alice:example.com",
|
||||||
|
"type": "m.room.canonical_alias",
|
||||||
|
"content": {
|
||||||
|
"alias":
|
||||||
|
"#famedlyContactDiscovery:fakeServer.notExisting"
|
||||||
|
},
|
||||||
|
"state_key": "",
|
||||||
|
"origin_server_ts": 1417731086796,
|
||||||
|
"event_id": "66697273743032:example.com"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -465,12 +517,30 @@ class FakeMatrixApi extends MockClient {
|
||||||
"room_id": "!1234:fakeServer.notExisting",
|
"room_id": "!1234:fakeServer.notExisting",
|
||||||
},
|
},
|
||||||
"/client/r0/rooms/!localpart:server.abc/read_markers": (var reqI) => {},
|
"/client/r0/rooms/!localpart:server.abc/read_markers": (var reqI) => {},
|
||||||
|
"/client/r0/rooms/!localpart:server.abc/kick": (var reqI) => {},
|
||||||
|
"/client/r0/rooms/!localpart:server.abc/ban": (var reqI) => {},
|
||||||
|
"/client/r0/rooms/!localpart:server.abc/unban": (var reqI) => {},
|
||||||
|
"/client/r0/rooms/!localpart:server.abc/invite": (var reqI) => {},
|
||||||
},
|
},
|
||||||
"PUT": {
|
"PUT": {
|
||||||
"/client/r0/rooms/!1234:example.com/send/m.room.message/1234":
|
"/client/r0/rooms/!1234:example.com/send/m.room.message/1234":
|
||||||
(var reqI) => {
|
(var reqI) => {
|
||||||
"event_id": "42",
|
"event_id": "42",
|
||||||
},
|
},
|
||||||
|
"/client/r0/rooms/!localpart:server.abc/state/m.room.name": (var reqI) =>
|
||||||
|
{
|
||||||
|
"event_id": "42",
|
||||||
|
},
|
||||||
|
"/client/r0/rooms/!localpart:server.abc/state/m.room.topic": (var reqI) =>
|
||||||
|
{
|
||||||
|
"event_id": "42",
|
||||||
|
},
|
||||||
|
"/client/r0/rooms/!localpart:server.abc/state/m.room.power_levels":
|
||||||
|
(var reqI) => {
|
||||||
|
"event_id": "42",
|
||||||
|
},
|
||||||
|
"/client/r0/user/@test:fakeServer.notExisting/account_data/m.direct":
|
||||||
|
(var reqI) => {},
|
||||||
},
|
},
|
||||||
"DELETE": {
|
"DELETE": {
|
||||||
"/unknown/token": (var req) => {"errcode": "M_UNKNOWN_TOKEN"},
|
"/unknown/token": (var req) => {"errcode": "M_UNKNOWN_TOKEN"},
|
||||||
|
|
|
@ -24,7 +24,10 @@
|
||||||
import 'package:famedlysdk/src/Client.dart';
|
import 'package:famedlysdk/src/Client.dart';
|
||||||
import 'package:famedlysdk/src/Event.dart';
|
import 'package:famedlysdk/src/Event.dart';
|
||||||
import 'package:famedlysdk/src/Room.dart';
|
import 'package:famedlysdk/src/Room.dart';
|
||||||
|
import 'package:famedlysdk/src/RoomState.dart';
|
||||||
|
import 'package:famedlysdk/src/Timeline.dart';
|
||||||
import 'package:famedlysdk/src/User.dart';
|
import 'package:famedlysdk/src/User.dart';
|
||||||
|
import 'package:famedlysdk/src/utils/ChatTime.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
import 'FakeMatrixApi.dart';
|
import 'FakeMatrixApi.dart';
|
||||||
|
@ -50,24 +53,9 @@ void main() {
|
||||||
|
|
||||||
test("Create from json", () async {
|
test("Create from json", () async {
|
||||||
final String id = "!localpart:server.abc";
|
final String id = "!localpart:server.abc";
|
||||||
final String name = "My Room";
|
|
||||||
final Membership membership = Membership.join;
|
final Membership membership = Membership.join;
|
||||||
final String topic = "This is my own room";
|
|
||||||
final int unread = DateTime.now().millisecondsSinceEpoch;
|
|
||||||
final int notificationCount = 2;
|
final int notificationCount = 2;
|
||||||
final int highlightCount = 1;
|
final int highlightCount = 1;
|
||||||
final String fullyRead = "fjh82jdjifd:server.abc";
|
|
||||||
final String notificationSettings = "all";
|
|
||||||
final String guestAccess = "forbidden";
|
|
||||||
final String canonicalAlias = "#testroom:example.com";
|
|
||||||
final String historyVisibility = "invite";
|
|
||||||
final String joinRules = "invite";
|
|
||||||
final int now = DateTime.now().millisecondsSinceEpoch;
|
|
||||||
final String msgtype = "m.text";
|
|
||||||
final String body = "Hello World";
|
|
||||||
final String formatted_body = "<b>Hello</b> World";
|
|
||||||
final String contentJson =
|
|
||||||
'{"msgtype":"$msgtype","body":"$body","formatted_body":"$formatted_body"}';
|
|
||||||
final List<String> heroes = [
|
final List<String> heroes = [
|
||||||
"@alice:matrix.org",
|
"@alice:matrix.org",
|
||||||
"@bob:example.com",
|
"@bob:example.com",
|
||||||
|
@ -75,37 +63,12 @@ void main() {
|
||||||
];
|
];
|
||||||
|
|
||||||
Map<String, dynamic> jsonObj = {
|
Map<String, dynamic> jsonObj = {
|
||||||
"id": id,
|
"room_id": id,
|
||||||
"membership": membership.toString().split('.').last,
|
"membership": membership.toString().split('.').last,
|
||||||
"topic": name,
|
|
||||||
"description": topic,
|
|
||||||
"avatar_url": "",
|
"avatar_url": "",
|
||||||
"notification_count": notificationCount,
|
"notification_count": notificationCount,
|
||||||
"highlight_count": highlightCount,
|
"highlight_count": highlightCount,
|
||||||
"unread": unread,
|
|
||||||
"fully_read": fullyRead,
|
|
||||||
"notification_settings": notificationSettings,
|
|
||||||
"direct_chat_matrix_id": "",
|
|
||||||
"draft": "",
|
|
||||||
"prev_batch": "",
|
"prev_batch": "",
|
||||||
"guest_access": guestAccess,
|
|
||||||
"history_visibility": historyVisibility,
|
|
||||||
"join_rules": joinRules,
|
|
||||||
"canonical_alias": canonicalAlias,
|
|
||||||
"power_events_default": 0,
|
|
||||||
"power_state_default": 0,
|
|
||||||
"power_redact": 0,
|
|
||||||
"power_invite": 0,
|
|
||||||
"power_ban": 0,
|
|
||||||
"power_kick": 0,
|
|
||||||
"power_user_default": 0,
|
|
||||||
"power_event_avatar": 0,
|
|
||||||
"power_event_history_visibility": 0,
|
|
||||||
"power_event_canonical_alias": 0,
|
|
||||||
"power_event_aliases": 0,
|
|
||||||
"power_event_name": 0,
|
|
||||||
"power_event_power_levels": 0,
|
|
||||||
"content_json": contentJson,
|
|
||||||
"joined_member_count": notificationCount,
|
"joined_member_count": notificationCount,
|
||||||
"invited_member_count": notificationCount,
|
"invited_member_count": notificationCount,
|
||||||
"heroes": heroes.join(","),
|
"heroes": heroes.join(","),
|
||||||
|
@ -115,37 +78,69 @@ void main() {
|
||||||
|
|
||||||
expect(room.id, id);
|
expect(room.id, id);
|
||||||
expect(room.membership, membership);
|
expect(room.membership, membership);
|
||||||
expect(room.name, name);
|
|
||||||
expect(room.displayname, name);
|
|
||||||
expect(room.topic, topic);
|
|
||||||
expect(room.avatar.mxc, "");
|
|
||||||
expect(room.notificationCount, notificationCount);
|
expect(room.notificationCount, notificationCount);
|
||||||
expect(room.highlightCount, highlightCount);
|
expect(room.highlightCount, highlightCount);
|
||||||
expect(room.unread.toTimeStamp(), unread);
|
|
||||||
expect(room.fullyRead, fullyRead);
|
|
||||||
expect(room.notificationSettings, notificationSettings);
|
|
||||||
expect(room.directChatMatrixID, "");
|
|
||||||
expect(room.draft, "");
|
|
||||||
expect(room.canonicalAlias, canonicalAlias);
|
|
||||||
expect(room.prev_batch, "");
|
|
||||||
expect(room.guestAccess, guestAccess);
|
|
||||||
expect(room.historyVisibility, historyVisibility);
|
|
||||||
expect(room.joinRules, joinRules);
|
|
||||||
expect(room.lastMessage, body);
|
|
||||||
expect(room.timeCreated.toTimeStamp() >= now, true);
|
|
||||||
room.powerLevels.forEach((String key, int value) {
|
|
||||||
expect(value, 0);
|
|
||||||
});
|
|
||||||
expect(room.mJoinedMemberCount, notificationCount);
|
expect(room.mJoinedMemberCount, notificationCount);
|
||||||
expect(room.mInvitedMemberCount, notificationCount);
|
expect(room.mInvitedMemberCount, notificationCount);
|
||||||
expect(room.mHeroes, heroes);
|
expect(room.mHeroes, heroes);
|
||||||
|
|
||||||
jsonObj["topic"] = "";
|
|
||||||
room = await Room.getRoomFromTableRow(jsonObj, matrix);
|
|
||||||
expect(room.displayname, "testroom");
|
|
||||||
jsonObj["canonical_alias"] = "";
|
|
||||||
room = await Room.getRoomFromTableRow(jsonObj, matrix);
|
|
||||||
expect(room.displayname, "alice, bob, charley");
|
expect(room.displayname, "alice, bob, charley");
|
||||||
|
|
||||||
|
room.states["m.room.canonical_alias"] = RoomState(
|
||||||
|
senderId: "@test:example.com",
|
||||||
|
typeKey: "m.room.canonical_alias",
|
||||||
|
roomId: room.id,
|
||||||
|
room: room,
|
||||||
|
eventId: "123",
|
||||||
|
content: {"alias": "#testalias:example.com"},
|
||||||
|
stateKey: "");
|
||||||
|
expect(room.displayname, "testalias");
|
||||||
|
expect(room.canonicalAlias, "#testalias:example.com");
|
||||||
|
|
||||||
|
room.states["m.room.name"] = RoomState(
|
||||||
|
senderId: "@test:example.com",
|
||||||
|
typeKey: "m.room.name",
|
||||||
|
roomId: room.id,
|
||||||
|
room: room,
|
||||||
|
eventId: "123",
|
||||||
|
content: {"name": "testname"},
|
||||||
|
stateKey: "");
|
||||||
|
expect(room.displayname, "testname");
|
||||||
|
|
||||||
|
expect(room.topic, "");
|
||||||
|
room.states["m.room.topic"] = RoomState(
|
||||||
|
senderId: "@test:example.com",
|
||||||
|
typeKey: "m.room.topic",
|
||||||
|
roomId: room.id,
|
||||||
|
room: room,
|
||||||
|
eventId: "123",
|
||||||
|
content: {"topic": "testtopic"},
|
||||||
|
stateKey: "");
|
||||||
|
expect(room.topic, "testtopic");
|
||||||
|
|
||||||
|
expect(room.avatar.mxc, "");
|
||||||
|
room.states["m.room.avatar"] = RoomState(
|
||||||
|
senderId: "@test:example.com",
|
||||||
|
typeKey: "m.room.avatar",
|
||||||
|
roomId: room.id,
|
||||||
|
room: room,
|
||||||
|
eventId: "123",
|
||||||
|
content: {"url": "mxc://testurl"},
|
||||||
|
stateKey: "");
|
||||||
|
expect(room.avatar.mxc, "mxc://testurl");
|
||||||
|
|
||||||
|
expect(room.lastEvent, null);
|
||||||
|
room.states["m.room.message"] = RoomState(
|
||||||
|
senderId: "@test:example.com",
|
||||||
|
typeKey: "m.room.message",
|
||||||
|
roomId: room.id,
|
||||||
|
room: room,
|
||||||
|
eventId: "12345",
|
||||||
|
time: ChatTime.now(),
|
||||||
|
content: {"msgtype": "m.text", "body": "test"},
|
||||||
|
stateKey: "");
|
||||||
|
expect(room.lastEvent.eventId, "12345");
|
||||||
|
expect(room.lastMessage, "test");
|
||||||
|
expect(room.timeCreated, room.lastEvent.time);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("sendReadReceipt", () async {
|
test("sendReadReceipt", () async {
|
||||||
|
@ -167,7 +162,98 @@ void main() {
|
||||||
|
|
||||||
test("getEventByID", () async {
|
test("getEventByID", () async {
|
||||||
final Event event = await room.getEventById("1234");
|
final Event event = await room.getEventById("1234");
|
||||||
expect(event.id, "143273582443PhrSn:example.org");
|
expect(event.eventId, "143273582443PhrSn:example.org");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("setName", () async {
|
||||||
|
final dynamic resp = await room.setName("Testname");
|
||||||
|
expect(resp["event_id"], "42");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("setDescription", () async {
|
||||||
|
final dynamic resp = await room.setDescription("Testname");
|
||||||
|
expect(resp["event_id"], "42");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("kick", () async {
|
||||||
|
final dynamic resp = await room.kick("Testname");
|
||||||
|
expect(resp, {});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("ban", () async {
|
||||||
|
final dynamic resp = await room.ban("Testname");
|
||||||
|
expect(resp, {});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("unban", () async {
|
||||||
|
final dynamic resp = await room.unban("Testname");
|
||||||
|
expect(resp, {});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("PowerLevels", () async {
|
||||||
|
room.states["m.room.power_levels"] = RoomState(
|
||||||
|
senderId: "@test:example.com",
|
||||||
|
typeKey: "m.room.power_levels",
|
||||||
|
roomId: room.id,
|
||||||
|
room: room,
|
||||||
|
eventId: "123",
|
||||||
|
content: {
|
||||||
|
"ban": 50,
|
||||||
|
"events": {"m.room.name": 100, "m.room.power_levels": 100},
|
||||||
|
"events_default": 0,
|
||||||
|
"invite": 50,
|
||||||
|
"kick": 50,
|
||||||
|
"notifications": {"room": 20},
|
||||||
|
"redact": 50,
|
||||||
|
"state_default": 50,
|
||||||
|
"users": {"@test:fakeServer.notExisting": 100},
|
||||||
|
"users_default": 10
|
||||||
|
},
|
||||||
|
stateKey: "");
|
||||||
|
expect(room.ownPowerLevel, 100);
|
||||||
|
expect(room.getPowerLevelByUserId(matrix.userID), room.ownPowerLevel);
|
||||||
|
expect(room.getPowerLevelByUserId("@nouser:example.com"), 10);
|
||||||
|
expect(room.powerLevels,
|
||||||
|
room.states["m.room.power_levels"].content["users"]);
|
||||||
|
final dynamic resp =
|
||||||
|
await room.setPower("@test:fakeServer.notExisting", 90);
|
||||||
|
expect(resp["event_id"], "42");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("invite", () async {
|
||||||
|
final dynamic resp = await room.invite("Testname");
|
||||||
|
expect(resp, {});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("getParticipants", () async {
|
||||||
|
room.states["@alice:test.abc"] = RoomState(
|
||||||
|
senderId: "@alice:test.abc",
|
||||||
|
typeKey: "m.room.member",
|
||||||
|
roomId: room.id,
|
||||||
|
room: room,
|
||||||
|
eventId: "12345",
|
||||||
|
time: ChatTime.now(),
|
||||||
|
content: {"displayname": "alice"},
|
||||||
|
stateKey: "@alice:test.abc");
|
||||||
|
final List<User> userList = room.getParticipants();
|
||||||
|
expect(userList.length, 1);
|
||||||
|
expect(userList[0].displayName, "alice");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("addToDirectChat", () async {
|
||||||
|
final dynamic resp = await room.addToDirectChat("Testname");
|
||||||
|
expect(resp, {});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("getTimeline", () async {
|
||||||
|
final Timeline timeline = await room.getTimeline();
|
||||||
|
expect(timeline.events, []);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("getUserByMXID", () async {
|
||||||
|
final User user = await room.getUserByMXID("@getme:example.com");
|
||||||
|
expect(user.stateKey, "@getme:example.com");
|
||||||
|
expect(user.calcDisplayname(), "You got me");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,7 +41,12 @@ void main() {
|
||||||
client.connection.httpClient = FakeMatrixApi();
|
client.connection.httpClient = FakeMatrixApi();
|
||||||
client.homeserver = "https://fakeServer.notExisting";
|
client.homeserver = "https://fakeServer.notExisting";
|
||||||
|
|
||||||
Room room = Room(id: roomID, client: client, prev_batch: "1234");
|
Room room = Room(
|
||||||
|
id: roomID,
|
||||||
|
client: client,
|
||||||
|
prev_batch: "1234",
|
||||||
|
states: {},
|
||||||
|
roomAccountData: {});
|
||||||
Timeline timeline = Timeline(
|
Timeline timeline = Timeline(
|
||||||
room: room,
|
room: room,
|
||||||
events: [],
|
events: [],
|
||||||
|
@ -87,10 +92,9 @@ void main() {
|
||||||
expect(insertList, [0, 0]);
|
expect(insertList, [0, 0]);
|
||||||
expect(insertList.length, timeline.events.length);
|
expect(insertList.length, timeline.events.length);
|
||||||
expect(timeline.events.length, 2);
|
expect(timeline.events.length, 2);
|
||||||
expect(timeline.events[0].id, "1");
|
expect(timeline.events[0].eventId, "1");
|
||||||
expect(timeline.events[0].sender.id, "@alice:example.com");
|
expect(timeline.events[0].sender.id, "@alice:example.com");
|
||||||
expect(timeline.events[0].time.toTimeStamp(), testTimeStamp);
|
expect(timeline.events[0].time.toTimeStamp(), testTimeStamp);
|
||||||
expect(timeline.events[0].environment, "m.room.message");
|
|
||||||
expect(timeline.events[0].getBody(), "Testcase");
|
expect(timeline.events[0].getBody(), "Testcase");
|
||||||
expect(timeline.events[0].time > timeline.events[1].time, true);
|
expect(timeline.events[0].time > timeline.events[1].time, true);
|
||||||
});
|
});
|
||||||
|
@ -103,7 +107,7 @@ void main() {
|
||||||
expect(updateCount, 4);
|
expect(updateCount, 4);
|
||||||
expect(insertList, [0, 0, 0]);
|
expect(insertList, [0, 0, 0]);
|
||||||
expect(insertList.length, timeline.events.length);
|
expect(insertList.length, timeline.events.length);
|
||||||
expect(timeline.events[0].id, "42");
|
expect(timeline.events[0].eventId, "42");
|
||||||
expect(timeline.events[0].status, 1);
|
expect(timeline.events[0].status, 1);
|
||||||
|
|
||||||
client.connection.onEvent.add(EventUpdate(
|
client.connection.onEvent.add(EventUpdate(
|
||||||
|
@ -125,7 +129,7 @@ void main() {
|
||||||
expect(updateCount, 5);
|
expect(updateCount, 5);
|
||||||
expect(insertList, [0, 0, 0]);
|
expect(insertList, [0, 0, 0]);
|
||||||
expect(insertList.length, timeline.events.length);
|
expect(insertList.length, timeline.events.length);
|
||||||
expect(timeline.events[0].id, "42");
|
expect(timeline.events[0].eventId, "42");
|
||||||
expect(timeline.events[0].status, 2);
|
expect(timeline.events[0].status, 2);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -189,9 +193,9 @@ void main() {
|
||||||
|
|
||||||
expect(updateCount, 19);
|
expect(updateCount, 19);
|
||||||
expect(timeline.events.length, 9);
|
expect(timeline.events.length, 9);
|
||||||
expect(timeline.events[6].id, "1143273582443PhrSn:example.org");
|
expect(timeline.events[6].eventId, "1143273582443PhrSn:example.org");
|
||||||
expect(timeline.events[7].id, "2143273582443PhrSn:example.org");
|
expect(timeline.events[7].eventId, "2143273582443PhrSn:example.org");
|
||||||
expect(timeline.events[8].id, "3143273582443PhrSn:example.org");
|
expect(timeline.events[8].eventId, "3143273582443PhrSn:example.org");
|
||||||
expect(room.prev_batch, "t47409-4357353_219380_26003_2265");
|
expect(room.prev_batch, "t47409-4357353_219380_26003_2265");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
* along with famedlysdk. If not, see <http://www.gnu.org/licenses/>.
|
* along with famedlysdk. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import 'package:famedlysdk/src/RoomState.dart';
|
||||||
import 'package:famedlysdk/src/User.dart';
|
import 'package:famedlysdk/src/User.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
@ -32,30 +33,35 @@ void main() {
|
||||||
final Membership membership = Membership.join;
|
final Membership membership = Membership.join;
|
||||||
final String displayName = "Alice";
|
final String displayName = "Alice";
|
||||||
final String avatarUrl = "";
|
final String avatarUrl = "";
|
||||||
final int powerLevel = 50;
|
|
||||||
|
|
||||||
final Map<String, dynamic> jsonObj = {
|
final Map<String, dynamic> jsonObj = {
|
||||||
"matrix_id": id,
|
"content": {
|
||||||
"displayname": displayName,
|
"membership": "join",
|
||||||
"avatar_url": avatarUrl,
|
"avatar_url": avatarUrl,
|
||||||
"membership": membership.toString().split('.').last,
|
"displayname": displayName
|
||||||
"power_level": powerLevel,
|
},
|
||||||
|
"type": "m.room.member",
|
||||||
|
"event_id": "143273582443PhrSn:example.org",
|
||||||
|
"room_id": "!636q39766251:example.com",
|
||||||
|
"sender": id,
|
||||||
|
"origin_server_ts": 1432735824653,
|
||||||
|
"unsigned": {"age": 1234},
|
||||||
|
"state_key": id
|
||||||
};
|
};
|
||||||
|
|
||||||
User user = User.fromJson(jsonObj, null);
|
User user = RoomState.fromJson(jsonObj, null).asUser;
|
||||||
|
|
||||||
expect(user.id, id);
|
expect(user.id, id);
|
||||||
expect(user.membership, membership);
|
expect(user.membership, membership);
|
||||||
expect(user.displayName, displayName);
|
expect(user.displayName, displayName);
|
||||||
expect(user.avatarUrl.mxc, avatarUrl);
|
expect(user.avatarUrl.mxc, avatarUrl);
|
||||||
expect(user.powerLevel, powerLevel);
|
|
||||||
expect(user.calcDisplayname(), displayName);
|
expect(user.calcDisplayname(), displayName);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("calcDisplayname", () async {
|
test("calcDisplayname", () async {
|
||||||
final User user1 = User("@alice:example.com");
|
final User user1 = User("@alice:example.com");
|
||||||
final User user2 = User("@alice:example.com", displayName: "SuperAlice");
|
final User user2 = User("@SuperAlice:example.com");
|
||||||
final User user3 = User("@alice:example.com", displayName: "");
|
final User user3 = User("@alice:example.com");
|
||||||
expect(user1.calcDisplayname(), "alice");
|
expect(user1.calcDisplayname(), "alice");
|
||||||
expect(user2.calcDisplayname(), "SuperAlice");
|
expect(user2.calcDisplayname(), "SuperAlice");
|
||||||
expect(user3.calcDisplayname(), "alice");
|
expect(user3.calcDisplayname(), "alice");
|
||||||
|
|
Loading…
Reference in a new issue