Merge branch 'sdk-refactor-simplify' into 'master'
[SDK| Big refactoring See merge request famedly/famedlysdk!142
This commit is contained in:
commit
920d7144ec
|
@ -39,15 +39,15 @@ Client matrix = Client("HappyChat", store: Store(this));
|
|||
3. Connect to a Matrix Homeserver and listen to the streams:
|
||||
|
||||
```dart
|
||||
matrix.connection.onLoginStateChanged.stream.listen((bool loginState){
|
||||
matrix.onLoginStateChanged.stream.listen((bool loginState){
|
||||
print("LoginState: ${loginState.toString()}");
|
||||
});
|
||||
|
||||
matrix.connection.onEvent.stream.listen((EventUpdate eventUpdate){
|
||||
matrix.onEvent.stream.listen((EventUpdate eventUpdate){
|
||||
print("New event update!");
|
||||
});
|
||||
|
||||
matrix.connection.onRoomUpdate.stream.listen((RoomUpdate eventUpdate){
|
||||
matrix.onRoomUpdate.stream.listen((RoomUpdate eventUpdate){
|
||||
print("New room update!");
|
||||
});
|
||||
|
||||
|
@ -59,7 +59,7 @@ final bool loginValid = await matrix.login("username", "password");
|
|||
4. Send a message to a Room:
|
||||
|
||||
```dart
|
||||
final resp = await matrix.connection.jsonRequest(
|
||||
final resp = await matrix.jsonRequest(
|
||||
type: "PUT",
|
||||
action: "/r0/rooms/!fjd823j:example.com/send/m.room.message/$txnId",
|
||||
data: {
|
||||
|
|
|
@ -23,25 +23,21 @@
|
|||
|
||||
library famedlysdk;
|
||||
|
||||
export 'package:famedlysdk/src/requests/SetPushersRequest.dart';
|
||||
export 'package:famedlysdk/src/responses/PushrulesResponse.dart';
|
||||
export 'package:famedlysdk/src/sync/RoomUpdate.dart';
|
||||
export 'package:famedlysdk/src/sync/EventUpdate.dart';
|
||||
export 'package:famedlysdk/src/sync/UserUpdate.dart';
|
||||
export 'package:famedlysdk/src/utils/ChatTime.dart';
|
||||
export 'package:famedlysdk/src/utils/MatrixException.dart';
|
||||
export 'package:famedlysdk/src/utils/MatrixFile.dart';
|
||||
export 'package:famedlysdk/src/utils/MxContent.dart';
|
||||
export 'package:famedlysdk/src/utils/Profile.dart';
|
||||
export 'package:famedlysdk/src/utils/PushRules.dart';
|
||||
export 'package:famedlysdk/src/utils/StatesMap.dart';
|
||||
export 'package:famedlysdk/src/AccountData.dart';
|
||||
export 'package:famedlysdk/src/Client.dart';
|
||||
export 'package:famedlysdk/src/Connection.dart';
|
||||
export 'package:famedlysdk/src/Event.dart';
|
||||
export 'package:famedlysdk/src/Presence.dart';
|
||||
export 'package:famedlysdk/src/Room.dart';
|
||||
export 'package:famedlysdk/src/RoomAccountData.dart';
|
||||
export 'package:famedlysdk/src/RoomList.dart';
|
||||
export 'package:famedlysdk/src/RoomState.dart';
|
||||
export 'package:famedlysdk/src/StoreAPI.dart';
|
||||
export 'package:famedlysdk/src/Timeline.dart';
|
||||
export 'package:famedlysdk/src/User.dart';
|
||||
|
|
|
@ -21,8 +21,9 @@
|
|||
* along with famedlysdk. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'package:famedlysdk/src/RoomState.dart';
|
||||
import 'package:famedlysdk/famedlysdk.dart';
|
||||
|
||||
/// The global private data created by this user.
|
||||
class AccountData {
|
||||
/// The json payload of the content. The content highly depends on the type.
|
||||
final Map<String, dynamic> content;
|
||||
|
@ -35,7 +36,7 @@ class AccountData {
|
|||
/// 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']);
|
||||
Event.getMapFromPayload(jsonPayload['content']);
|
||||
return AccountData(content: content, typeKey: jsonPayload['type']);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,35 +30,40 @@ import 'package:famedlysdk/src/Presence.dart';
|
|||
import 'package:famedlysdk/src/StoreAPI.dart';
|
||||
import 'package:famedlysdk/src/sync/UserUpdate.dart';
|
||||
import 'package:famedlysdk/src/utils/MatrixFile.dart';
|
||||
|
||||
import 'Connection.dart';
|
||||
import 'Room.dart';
|
||||
import 'RoomList.dart';
|
||||
//import 'Store.dart';
|
||||
import 'RoomState.dart';
|
||||
import 'Event.dart';
|
||||
import 'User.dart';
|
||||
import 'requests/SetPushersRequest.dart';
|
||||
import 'responses/PushrulesResponse.dart';
|
||||
import 'utils/Profile.dart';
|
||||
import 'dart:convert';
|
||||
import 'package:famedlysdk/src/Room.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:mime_type/mime_type.dart';
|
||||
import 'sync/EventUpdate.dart';
|
||||
import 'sync/RoomUpdate.dart';
|
||||
import 'sync/UserUpdate.dart';
|
||||
import 'utils/MatrixException.dart';
|
||||
|
||||
typedef AccountDataEventCB = void Function(AccountData accountData);
|
||||
typedef PresenceCB = void Function(Presence presence);
|
||||
|
||||
enum HTTPType { GET, POST, PUT, DELETE }
|
||||
|
||||
enum LoginState { logged, loggedOut }
|
||||
|
||||
/// Represents a Matrix client to communicate with a
|
||||
/// [Matrix](https://matrix.org) homeserver and is the entry point for this
|
||||
/// SDK.
|
||||
class Client {
|
||||
/// Handles the connection for this client.
|
||||
Connection connection;
|
||||
@deprecated
|
||||
Client get connection => this;
|
||||
|
||||
/// Optional persistent store for all data.
|
||||
StoreAPI store;
|
||||
|
||||
Client(this.clientName, {this.debug = false, this.store}) {
|
||||
connection = Connection(this);
|
||||
|
||||
if (this.clientName != "testclient") store = null; //Store(this);
|
||||
connection.onLoginStateChanged.stream.listen((loginState) {
|
||||
this.onLoginStateChanged.stream.listen((loginState) {
|
||||
print("LoginState: ${loginState.toString()}");
|
||||
});
|
||||
}
|
||||
|
@ -70,35 +75,43 @@ class Client {
|
|||
final String clientName;
|
||||
|
||||
/// The homeserver this client is communicating with.
|
||||
String homeserver;
|
||||
String get homeserver => _homeserver;
|
||||
String _homeserver;
|
||||
|
||||
/// The Matrix ID of the current logged user.
|
||||
String userID;
|
||||
String get userID => _userID;
|
||||
String _userID;
|
||||
|
||||
/// This is the access token for the matrix client. When it is undefined, then
|
||||
/// the user needs to sign in first.
|
||||
String accessToken;
|
||||
String get accessToken => _accessToken;
|
||||
String _accessToken;
|
||||
|
||||
/// This points to the position in the synchronization history.
|
||||
String prevBatch;
|
||||
|
||||
/// The device ID is an unique identifier for this device.
|
||||
String deviceID;
|
||||
String get deviceID => _deviceID;
|
||||
String _deviceID;
|
||||
|
||||
/// The device name is a human readable identifier for this device.
|
||||
String deviceName;
|
||||
String get deviceName => _deviceName;
|
||||
String _deviceName;
|
||||
|
||||
/// Which version of the matrix specification does this server support?
|
||||
List<String> matrixVersions;
|
||||
List<String> get matrixVersions => _matrixVersions;
|
||||
List<String> _matrixVersions;
|
||||
|
||||
/// Wheither the server supports lazy load members.
|
||||
bool lazyLoadMembers = false;
|
||||
bool get lazyLoadMembers => _lazyLoadMembers;
|
||||
bool _lazyLoadMembers = false;
|
||||
|
||||
/// Returns the current login state.
|
||||
bool isLogged() => accessToken != null;
|
||||
|
||||
/// A list of all rooms the user is participating or invited.
|
||||
RoomList roomList;
|
||||
List<Room> get rooms => _rooms;
|
||||
List<Room> _rooms = [];
|
||||
|
||||
/// Key/Value store of account data.
|
||||
Map<String, AccountData> accountData = {};
|
||||
|
@ -112,6 +125,20 @@ class Client {
|
|||
/// Callback will be called on presences.
|
||||
PresenceCB onPresence;
|
||||
|
||||
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 handleUserUpdate(UserUpdate userUpdate) {
|
||||
if (userUpdate.type == "account_data") {
|
||||
AccountData newAccountData = AccountData.fromJson(userUpdate.content);
|
||||
|
@ -134,21 +161,21 @@ class Client {
|
|||
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];
|
||||
if (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(
|
||||
this.jsonRequest(
|
||||
type: HTTPType.PUT,
|
||||
action: "/client/r0/user/${userID}/account_data/m.direct",
|
||||
data: directChats);
|
||||
return getDirectChatFromUserId(userId);
|
||||
}
|
||||
for (int i = 0; i < roomList.rooms.length; i++)
|
||||
if (roomList.rooms[i].membership == Membership.invite &&
|
||||
roomList.rooms[i].states[userID]?.senderId == userId &&
|
||||
roomList.rooms[i].states[userID].content["is_direct"] == true)
|
||||
return roomList.rooms[i].id;
|
||||
for (int i = 0; i < this.rooms.length; i++)
|
||||
if (this.rooms[i].membership == Membership.invite &&
|
||||
this.rooms[i].states[userID]?.senderId == userId &&
|
||||
this.rooms[i].states[userID].content["is_direct"] == true)
|
||||
return this.rooms[i].id;
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -158,9 +185,9 @@ class Client {
|
|||
/// Throws FormatException, TimeoutException and MatrixException on error.
|
||||
Future<bool> checkServer(serverUrl) async {
|
||||
try {
|
||||
homeserver = serverUrl;
|
||||
final versionResp = await connection.jsonRequest(
|
||||
type: HTTPType.GET, action: "/client/versions");
|
||||
_homeserver = serverUrl;
|
||||
final versionResp = await this
|
||||
.jsonRequest(type: HTTPType.GET, action: "/client/versions");
|
||||
|
||||
final List<String> versions = List<String>.from(versionResp["versions"]);
|
||||
|
||||
|
@ -172,18 +199,18 @@ class Client {
|
|||
}
|
||||
}
|
||||
|
||||
matrixVersions = versions;
|
||||
_matrixVersions = versions;
|
||||
|
||||
if (versionResp.containsKey("unstable_features") &&
|
||||
versionResp["unstable_features"].containsKey("m.lazy_load_members")) {
|
||||
lazyLoadMembers = versionResp["unstable_features"]
|
||||
_lazyLoadMembers = versionResp["unstable_features"]
|
||||
["m.lazy_load_members"]
|
||||
? true
|
||||
: false;
|
||||
}
|
||||
|
||||
final loginResp = await connection.jsonRequest(
|
||||
type: HTTPType.GET, action: "/client/r0/login");
|
||||
final loginResp = await this
|
||||
.jsonRequest(type: HTTPType.GET, action: "/client/r0/login");
|
||||
|
||||
final List<dynamic> flows = loginResp["flows"];
|
||||
|
||||
|
@ -197,7 +224,7 @@ class Client {
|
|||
}
|
||||
return true;
|
||||
} catch (_) {
|
||||
this.homeserver = this.matrixVersions = null;
|
||||
this._homeserver = this._matrixVersions = null;
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
@ -206,17 +233,19 @@ class Client {
|
|||
/// authentication. Returns false if the login was not successful. Throws
|
||||
/// MatrixException if login was not successful.
|
||||
Future<bool> login(String username, String password) async {
|
||||
final loginResp = await connection
|
||||
.jsonRequest(type: HTTPType.POST, action: "/client/r0/login", data: {
|
||||
"type": "m.login.password",
|
||||
"user": username,
|
||||
"identifier": {
|
||||
"type": "m.id.user",
|
||||
"user": username,
|
||||
},
|
||||
"password": password,
|
||||
"initial_device_display_name": "Famedly Talk"
|
||||
});
|
||||
final loginResp = await jsonRequest(
|
||||
type: HTTPType.POST,
|
||||
action: "/client/r0/login",
|
||||
data: {
|
||||
"type": "m.login.password",
|
||||
"user": username,
|
||||
"identifier": {
|
||||
"type": "m.id.user",
|
||||
"user": username,
|
||||
},
|
||||
"password": password,
|
||||
"initial_device_display_name": "Famedly Talk"
|
||||
});
|
||||
|
||||
final userID = loginResp["user_id"];
|
||||
final accessToken = loginResp["access_token"];
|
||||
|
@ -224,7 +253,7 @@ class Client {
|
|||
return false;
|
||||
}
|
||||
|
||||
await connection.connect(
|
||||
await this.connect(
|
||||
newToken: accessToken,
|
||||
newUserID: userID,
|
||||
newHomeserver: homeserver,
|
||||
|
@ -239,12 +268,11 @@ class Client {
|
|||
/// including all persistent data from the store.
|
||||
Future<void> logout() async {
|
||||
try {
|
||||
await connection.jsonRequest(
|
||||
type: HTTPType.POST, action: "/client/r0/logout");
|
||||
await this.jsonRequest(type: HTTPType.POST, action: "/client/r0/logout");
|
||||
} catch (exception) {
|
||||
rethrow;
|
||||
} finally {
|
||||
await connection.clear();
|
||||
await this.clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -252,33 +280,17 @@ class Client {
|
|||
/// fetch the user's own profile information or other users; either locally
|
||||
/// or on remote homeservers.
|
||||
Future<Profile> getProfileFromUserId(String userId) async {
|
||||
final dynamic resp = await connection.jsonRequest(
|
||||
final dynamic resp = await this.jsonRequest(
|
||||
type: HTTPType.GET, action: "/client/r0/profile/${userId}");
|
||||
return Profile.fromJson(resp);
|
||||
}
|
||||
|
||||
/// Creates a new [RoomList] object.
|
||||
RoomList getRoomList(
|
||||
{onRoomListUpdateCallback onUpdate,
|
||||
onRoomListInsertCallback onInsert,
|
||||
onRoomListRemoveCallback onRemove}) {
|
||||
List<Room> rooms = roomList.rooms;
|
||||
return RoomList(
|
||||
client: this,
|
||||
onlyLeft: false,
|
||||
onUpdate: onUpdate,
|
||||
onInsert: onInsert,
|
||||
onRemove: onRemove,
|
||||
rooms: rooms);
|
||||
}
|
||||
|
||||
Future<List<Room>> get archive async {
|
||||
List<Room> archiveList = [];
|
||||
String syncFilters =
|
||||
'{"room":{"include_leave":true,"timeline":{"limit":10}}}';
|
||||
String action = "/client/r0/sync?filter=$syncFilters&timeout=0";
|
||||
final sync =
|
||||
await connection.jsonRequest(type: HTTPType.GET, action: action);
|
||||
final sync = await this.jsonRequest(type: HTTPType.GET, action: action);
|
||||
if (sync["rooms"]["leave"] is Map<String, dynamic>) {
|
||||
for (var entry in sync["rooms"]["leave"].entries) {
|
||||
final String id = entry.key;
|
||||
|
@ -301,13 +313,13 @@ class Client {
|
|||
if (room["timeline"] is Map<String, dynamic> &&
|
||||
room["timeline"]["events"] is List<dynamic>) {
|
||||
for (dynamic event in room["timeline"]["events"]) {
|
||||
leftRoom.setState(RoomState.fromJson(event, leftRoom));
|
||||
leftRoom.setState(Event.fromJson(event, leftRoom));
|
||||
}
|
||||
}
|
||||
if (room["state"] is Map<String, dynamic> &&
|
||||
room["state"]["events"] is List<dynamic>) {
|
||||
for (dynamic event in room["state"]["events"]) {
|
||||
leftRoom.setState(RoomState.fromJson(event, leftRoom));
|
||||
leftRoom.setState(Event.fromJson(event, leftRoom));
|
||||
}
|
||||
}
|
||||
archiveList.add(leftRoom);
|
||||
|
@ -316,12 +328,9 @@ class Client {
|
|||
return archiveList;
|
||||
}
|
||||
|
||||
/// Searches in the roomList and in the archive for a room with the given [id].
|
||||
Room getRoomById(String id) => roomList.getRoomById(id);
|
||||
|
||||
Future<dynamic> joinRoomById(String id) async {
|
||||
return await connection.jsonRequest(
|
||||
type: HTTPType.POST, action: "/client/r0/join/$id");
|
||||
return await this
|
||||
.jsonRequest(type: HTTPType.POST, action: "/client/r0/join/$id");
|
||||
}
|
||||
|
||||
/// Loads the contact list for this user excluding the user itself.
|
||||
|
@ -330,14 +339,14 @@ class Client {
|
|||
/// defined by the autojoin room feature in Synapse.
|
||||
Future<List<User>> loadFamedlyContacts() async {
|
||||
List<User> contacts = [];
|
||||
Room contactDiscoveryRoom = roomList
|
||||
.getRoomByAlias("#famedlyContactDiscovery:${userID.split(":")[1]}");
|
||||
Room contactDiscoveryRoom =
|
||||
this.getRoomByAlias("#famedlyContactDiscovery:${userID.split(":")[1]}");
|
||||
if (contactDiscoveryRoom != null)
|
||||
contacts = await contactDiscoveryRoom.requestParticipants();
|
||||
else {
|
||||
Map<String, bool> userMap = {};
|
||||
for (int i = 0; i < roomList.rooms.length; i++) {
|
||||
List<User> roomUsers = roomList.rooms[i].getParticipants();
|
||||
for (int i = 0; i < this.rooms.length; i++) {
|
||||
List<User> roomUsers = this.rooms[i].getParticipants();
|
||||
for (int j = 0; j < roomUsers.length; j++) {
|
||||
if (userMap[roomUsers[j].id] != true) contacts.add(roomUsers[j]);
|
||||
userMap[roomUsers[j].id] = true;
|
||||
|
@ -361,7 +370,7 @@ class Client {
|
|||
for (int i = 0; i < invite.length; i++) inviteIDs.add(invite[i].id);
|
||||
|
||||
try {
|
||||
final dynamic resp = await connection.jsonRequest(
|
||||
final dynamic resp = await this.jsonRequest(
|
||||
type: HTTPType.POST,
|
||||
action: "/client/r0/createRoom",
|
||||
data: params == null
|
||||
|
@ -377,8 +386,8 @@ class Client {
|
|||
|
||||
/// Uploads a new user avatar for this user.
|
||||
Future<void> setAvatar(MatrixFile file) async {
|
||||
final uploadResp = await connection.upload(file);
|
||||
await connection.jsonRequest(
|
||||
final uploadResp = await this.upload(file);
|
||||
await this.jsonRequest(
|
||||
type: HTTPType.PUT,
|
||||
action: "/client/r0/profile/$userID/avatar_url",
|
||||
data: {"avatar_url": uploadResp});
|
||||
|
@ -387,22 +396,584 @@ class Client {
|
|||
|
||||
/// Fetches the pushrules for the logged in user.
|
||||
/// These are needed for notifications on Android
|
||||
Future<PushrulesResponse> getPushrules() async {
|
||||
final dynamic resp = await connection.jsonRequest(
|
||||
Future<PushRules> getPushrules() async {
|
||||
final dynamic resp = await this.jsonRequest(
|
||||
type: HTTPType.GET,
|
||||
action: "/client/r0/pushrules/",
|
||||
);
|
||||
|
||||
return PushrulesResponse.fromJson(resp);
|
||||
return PushRules.fromJson(resp);
|
||||
}
|
||||
|
||||
/// This endpoint allows the creation, modification and deletion of pushers for this user ID.
|
||||
Future<void> setPushers(SetPushersRequest data) async {
|
||||
await connection.jsonRequest(
|
||||
Future<void> setPushers(String pushKey, String kind, String appId,
|
||||
String appDisplayName, String deviceDisplayName, String lang, String url,
|
||||
{bool append, String profileTag, String format}) async {
|
||||
Map<String, dynamic> data = {
|
||||
"lang": lang,
|
||||
"kind": kind,
|
||||
"app_display_name": appDisplayName,
|
||||
"device_display_name": deviceDisplayName,
|
||||
"profile_tag": profileTag,
|
||||
"app_id": appId,
|
||||
"pushkey": pushKey,
|
||||
"data": {"url": url}
|
||||
};
|
||||
|
||||
if (format != null) data["data"]["format"] = format;
|
||||
if (profileTag != null) data["profile_tag"] = profileTag;
|
||||
if (append != null) data["append"] = append;
|
||||
|
||||
await this.jsonRequest(
|
||||
type: HTTPType.POST,
|
||||
action: "/client/r0/pushers/set",
|
||||
data: data.toJson(),
|
||||
data: data,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
static String syncFilters = '{"room":{"state":{"lazy_load_members":true}}}';
|
||||
|
||||
http.Client httpClient = http.Client();
|
||||
|
||||
/// The newEvent signal is the most important signal in this concept. Every time
|
||||
/// the app receives a new synchronization, this event is called for every signal
|
||||
/// to update the GUI. For example, for a new message, it is called:
|
||||
/// onRoomEvent( "m.room.message", "!chat_id:server.com", "timeline", {sender: "@bob:server.com", body: "Hello world"} )
|
||||
final StreamController<EventUpdate> onEvent =
|
||||
new StreamController.broadcast();
|
||||
|
||||
/// Outside of the events there are updates for the global chat states which
|
||||
/// are handled by this signal:
|
||||
final StreamController<RoomUpdate> onRoomUpdate =
|
||||
new StreamController.broadcast();
|
||||
|
||||
/// Outside of rooms there are account updates like account_data or presences.
|
||||
final StreamController<UserUpdate> onUserEvent =
|
||||
new StreamController.broadcast();
|
||||
|
||||
/// Called when the login state e.g. user gets logged out.
|
||||
final StreamController<LoginState> onLoginStateChanged =
|
||||
new StreamController.broadcast();
|
||||
|
||||
/// Synchronization erros are coming here.
|
||||
final StreamController<MatrixException> onError =
|
||||
new StreamController.broadcast();
|
||||
|
||||
/// This is called once, when the first sync has received.
|
||||
final StreamController<bool> onFirstSync = new StreamController.broadcast();
|
||||
|
||||
/// When a new sync response is coming in, this gives the complete payload.
|
||||
final StreamController<dynamic> onSync = new StreamController.broadcast();
|
||||
|
||||
/// Matrix synchronisation is done with https long polling. This needs a
|
||||
/// timeout which is usually 30 seconds.
|
||||
int syncTimeoutSec = 30;
|
||||
|
||||
/// How long should the app wait until it retrys the synchronisation after
|
||||
/// an error?
|
||||
int syncErrorTimeoutSec = 3;
|
||||
|
||||
/// Sets the user credentials and starts the synchronisation.
|
||||
///
|
||||
/// Before you can connect you need at least an [accessToken], a [homeserver],
|
||||
/// a [userID], a [deviceID], and a [deviceName].
|
||||
///
|
||||
/// You get this informations
|
||||
/// by logging in to your Matrix account, using the [login API](https://matrix.org/docs/spec/client_server/r0.4.0.html#post-matrix-client-r0-login).
|
||||
///
|
||||
/// To log in you can use [jsonRequest()] after you have set the [homeserver]
|
||||
/// to a valid url. For example:
|
||||
///
|
||||
/// ```
|
||||
/// final resp = await matrix
|
||||
/// .jsonRequest(type: HTTPType.POST, action: "/client/r0/login", data: {
|
||||
/// "type": "m.login.password",
|
||||
/// "user": "test",
|
||||
/// "password": "1234",
|
||||
/// "initial_device_display_name": "Fluffy Matrix Client"
|
||||
/// });
|
||||
/// ```
|
||||
///
|
||||
/// Returns:
|
||||
///
|
||||
/// ```
|
||||
/// {
|
||||
/// "user_id": "@cheeky_monkey:matrix.org",
|
||||
/// "access_token": "abc123",
|
||||
/// "device_id": "GHTYAJCE"
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Sends [LoginState.logged] to [onLoginStateChanged].
|
||||
void connect(
|
||||
{String newToken,
|
||||
String newHomeserver,
|
||||
String newUserID,
|
||||
String newDeviceName,
|
||||
String newDeviceID,
|
||||
List<String> newMatrixVersions,
|
||||
bool newLazyLoadMembers,
|
||||
String newPrevBatch}) async {
|
||||
this._accessToken = newToken;
|
||||
this._homeserver = newHomeserver;
|
||||
this._userID = newUserID;
|
||||
this._deviceID = newDeviceID;
|
||||
this._deviceName = newDeviceName;
|
||||
this._matrixVersions = newMatrixVersions;
|
||||
this._lazyLoadMembers = newLazyLoadMembers;
|
||||
this.prevBatch = newPrevBatch;
|
||||
|
||||
if (this.store != null) {
|
||||
this.store.storeClient();
|
||||
this._rooms = await this.store.getRoomList(onlyLeft: false);
|
||||
this.accountData = await this.store.getAccountData();
|
||||
this.presences = await this.store.getPresences();
|
||||
}
|
||||
|
||||
_userEventSub ??= onUserEvent.stream.listen(this.handleUserUpdate);
|
||||
|
||||
onLoginStateChanged.add(LoginState.logged);
|
||||
|
||||
_sync();
|
||||
}
|
||||
|
||||
StreamSubscription _userEventSub;
|
||||
|
||||
/// Resets all settings and stops the synchronisation.
|
||||
void clear() {
|
||||
this.store?.clear();
|
||||
this._accessToken = this._homeserver = this._userID = this._deviceID = this
|
||||
._deviceName =
|
||||
this._matrixVersions = this._lazyLoadMembers = this.prevBatch = null;
|
||||
onLoginStateChanged.add(LoginState.loggedOut);
|
||||
}
|
||||
|
||||
/// Used for all Matrix json requests using the [c2s API](https://matrix.org/docs/spec/client_server/r0.4.0.html).
|
||||
///
|
||||
/// Throws: TimeoutException, FormatException, MatrixException
|
||||
///
|
||||
/// You must first call [this.connect()] or set [this.homeserver] before you can use
|
||||
/// this! For example to send a message to a Matrix room with the id
|
||||
/// '!fjd823j:example.com' you call:
|
||||
///
|
||||
/// ```
|
||||
/// final resp = await jsonRequest(
|
||||
/// type: HTTPType.PUT,
|
||||
/// action: "/r0/rooms/!fjd823j:example.com/send/m.room.message/$txnId",
|
||||
/// data: {
|
||||
/// "msgtype": "m.text",
|
||||
/// "body": "hello"
|
||||
/// }
|
||||
/// );
|
||||
/// ```
|
||||
///
|
||||
Future<Map<String, dynamic>> jsonRequest(
|
||||
{HTTPType type,
|
||||
String action,
|
||||
dynamic data = "",
|
||||
int timeout,
|
||||
String contentType = "application/json"}) async {
|
||||
if (this.isLogged() == false && this.homeserver == null)
|
||||
throw ("No homeserver specified.");
|
||||
if (timeout == null) timeout = syncTimeoutSec + 5;
|
||||
dynamic json;
|
||||
if (data is Map) data.removeWhere((k, v) => v == null);
|
||||
(!(data is String)) ? json = jsonEncode(data) : json = data;
|
||||
if (data is List<int> || action.startsWith("/media/r0/upload")) json = data;
|
||||
|
||||
final url = "${this.homeserver}/_matrix${action}";
|
||||
|
||||
Map<String, String> headers = {};
|
||||
if (type == HTTPType.PUT || type == HTTPType.POST)
|
||||
headers["Content-Type"] = contentType;
|
||||
if (this.isLogged())
|
||||
headers["Authorization"] = "Bearer ${this.accessToken}";
|
||||
|
||||
if (this.debug)
|
||||
print(
|
||||
"[REQUEST ${type.toString().split('.').last}] Action: $action, Data: $data");
|
||||
|
||||
http.Response resp;
|
||||
Map<String, dynamic> jsonResp = {};
|
||||
try {
|
||||
switch (type.toString().split('.').last) {
|
||||
case "GET":
|
||||
resp = await httpClient
|
||||
.get(url, headers: headers)
|
||||
.timeout(Duration(seconds: timeout));
|
||||
break;
|
||||
case "POST":
|
||||
resp = await httpClient
|
||||
.post(url, body: json, headers: headers)
|
||||
.timeout(Duration(seconds: timeout));
|
||||
break;
|
||||
case "PUT":
|
||||
resp = await httpClient
|
||||
.put(url, body: json, headers: headers)
|
||||
.timeout(Duration(seconds: timeout));
|
||||
break;
|
||||
case "DELETE":
|
||||
resp = await httpClient
|
||||
.delete(url, headers: headers)
|
||||
.timeout(Duration(seconds: timeout));
|
||||
break;
|
||||
}
|
||||
jsonResp = jsonDecode(resp.body)
|
||||
as Map<String, dynamic>; // May throw FormatException
|
||||
|
||||
if (jsonResp.containsKey("errcode") && jsonResp["errcode"] is String) {
|
||||
// The server has responsed with an matrix related error.
|
||||
MatrixException exception = MatrixException(resp);
|
||||
if (exception.error == MatrixError.M_UNKNOWN_TOKEN) {
|
||||
// The token is no longer valid. Need to sign off....
|
||||
onError.add(exception);
|
||||
clear();
|
||||
}
|
||||
|
||||
throw exception;
|
||||
}
|
||||
|
||||
if (this.debug) print("[RESPONSE] ${jsonResp.toString()}");
|
||||
} on ArgumentError catch (exception) {
|
||||
print(exception);
|
||||
// Ignore this error
|
||||
} catch (_) {
|
||||
print(_);
|
||||
rethrow;
|
||||
}
|
||||
|
||||
return jsonResp;
|
||||
}
|
||||
|
||||
/// Uploads a file with the name [fileName] as base64 encoded to the server
|
||||
/// and returns the mxc url as a string.
|
||||
Future<String> upload(MatrixFile file) async {
|
||||
dynamic fileBytes;
|
||||
if (this.homeserver != "https://fakeServer.notExisting")
|
||||
fileBytes = file.bytes;
|
||||
String fileName = file.path.split("/").last.toLowerCase();
|
||||
String mimeType = mime(file.path);
|
||||
print("[UPLOADING] $fileName, type: $mimeType, size: ${fileBytes?.length}");
|
||||
final Map<String, dynamic> resp = await jsonRequest(
|
||||
type: HTTPType.POST,
|
||||
action: "/media/r0/upload?filename=$fileName",
|
||||
data: fileBytes,
|
||||
contentType: mimeType);
|
||||
return resp["content_uri"];
|
||||
}
|
||||
|
||||
Future<dynamic> _syncRequest;
|
||||
|
||||
Future<void> _sync() async {
|
||||
if (this.isLogged() == false) return;
|
||||
|
||||
String action = "/client/r0/sync?filter=$syncFilters";
|
||||
|
||||
if (this.prevBatch != null) {
|
||||
action += "&timeout=30000";
|
||||
action += "&since=${this.prevBatch}";
|
||||
}
|
||||
try {
|
||||
_syncRequest = jsonRequest(type: HTTPType.GET, action: action);
|
||||
final int hash = _syncRequest.hashCode;
|
||||
final syncResp = await _syncRequest;
|
||||
if (hash != _syncRequest.hashCode) return;
|
||||
if (this.store != null)
|
||||
await this.store.transaction(() {
|
||||
handleSync(syncResp);
|
||||
this.store.storePrevBatch(syncResp);
|
||||
return;
|
||||
});
|
||||
else
|
||||
await handleSync(syncResp);
|
||||
if (this.prevBatch == null) this.onFirstSync.add(true);
|
||||
this.prevBatch = syncResp["next_batch"];
|
||||
if (hash == _syncRequest.hashCode) _sync();
|
||||
} on MatrixException catch (exception) {
|
||||
onError.add(exception);
|
||||
await Future.delayed(Duration(seconds: syncErrorTimeoutSec), _sync);
|
||||
} catch (exception) {
|
||||
await Future.delayed(Duration(seconds: syncErrorTimeoutSec), _sync);
|
||||
}
|
||||
}
|
||||
|
||||
void handleSync(dynamic sync) {
|
||||
if (sync["rooms"] is Map<String, dynamic>) {
|
||||
if (sync["rooms"]["join"] is Map<String, dynamic>)
|
||||
_handleRooms(sync["rooms"]["join"], Membership.join);
|
||||
if (sync["rooms"]["invite"] is Map<String, dynamic>)
|
||||
_handleRooms(sync["rooms"]["invite"], Membership.invite);
|
||||
if (sync["rooms"]["leave"] is Map<String, dynamic>)
|
||||
_handleRooms(sync["rooms"]["leave"], Membership.leave);
|
||||
}
|
||||
if (sync["presence"] is Map<String, dynamic> &&
|
||||
sync["presence"]["events"] is List<dynamic>) {
|
||||
_handleGlobalEvents(sync["presence"]["events"], "presence");
|
||||
}
|
||||
if (sync["account_data"] is Map<String, dynamic> &&
|
||||
sync["account_data"]["events"] is List<dynamic>) {
|
||||
_handleGlobalEvents(sync["account_data"]["events"], "account_data");
|
||||
}
|
||||
if (sync["to_device"] is Map<String, dynamic> &&
|
||||
sync["to_device"]["events"] is List<dynamic>) {
|
||||
_handleGlobalEvents(sync["to_device"]["events"], "to_device");
|
||||
}
|
||||
onSync.add(sync);
|
||||
}
|
||||
|
||||
void _handleRooms(Map<String, dynamic> rooms, Membership membership) {
|
||||
rooms.forEach((String id, dynamic room) async {
|
||||
// calculate the notification counts, the limitedTimeline and prevbatch
|
||||
num highlight_count = 0;
|
||||
num notification_count = 0;
|
||||
String prev_batch = "";
|
||||
bool limitedTimeline = false;
|
||||
|
||||
if (room["unread_notifications"] is Map<String, dynamic>) {
|
||||
if (room["unread_notifications"]["highlight_count"] is num)
|
||||
highlight_count = room["unread_notifications"]["highlight_count"];
|
||||
if (room["unread_notifications"]["notification_count"] is num)
|
||||
notification_count =
|
||||
room["unread_notifications"]["notification_count"];
|
||||
}
|
||||
|
||||
if (room["timeline"] is Map<String, dynamic>) {
|
||||
if (room["timeline"]["limited"] is bool)
|
||||
limitedTimeline = room["timeline"]["limited"];
|
||||
if (room["timeline"]["prev_batch"] is String)
|
||||
prev_batch = room["timeline"]["prev_batch"];
|
||||
}
|
||||
|
||||
RoomSummary summary;
|
||||
|
||||
if (room["summary"] is Map<String, dynamic>) {
|
||||
summary = RoomSummary.fromJson(room["summary"]);
|
||||
}
|
||||
|
||||
RoomUpdate update = RoomUpdate(
|
||||
id: id,
|
||||
membership: membership,
|
||||
notification_count: notification_count,
|
||||
highlight_count: highlight_count,
|
||||
limitedTimeline: limitedTimeline,
|
||||
prev_batch: prev_batch,
|
||||
summary: summary,
|
||||
);
|
||||
_updateRoomsByRoomUpdate(update);
|
||||
this.store?.storeRoomUpdate(update);
|
||||
onRoomUpdate.add(update);
|
||||
|
||||
/// Handle now all room events and save them in the database
|
||||
if (room["state"] is Map<String, dynamic> &&
|
||||
room["state"]["events"] is List<dynamic>)
|
||||
_handleRoomEvents(id, room["state"]["events"], "state");
|
||||
|
||||
if (room["invite_state"] is Map<String, dynamic> &&
|
||||
room["invite_state"]["events"] is List<dynamic>)
|
||||
_handleRoomEvents(id, room["invite_state"]["events"], "invite_state");
|
||||
|
||||
if (room["timeline"] is Map<String, dynamic> &&
|
||||
room["timeline"]["events"] is List<dynamic>)
|
||||
_handleRoomEvents(id, room["timeline"]["events"], "timeline");
|
||||
|
||||
if (room["ephemeral"] is Map<String, dynamic> &&
|
||||
room["ephemeral"]["events"] is List<dynamic>)
|
||||
_handleEphemerals(id, room["ephemeral"]["events"]);
|
||||
|
||||
if (room["account_data"] is Map<String, dynamic> &&
|
||||
room["account_data"]["events"] is List<dynamic>)
|
||||
_handleRoomEvents(id, room["account_data"]["events"], "account_data");
|
||||
});
|
||||
}
|
||||
|
||||
void _handleEphemerals(String id, List<dynamic> events) {
|
||||
for (num i = 0; i < events.length; i++) {
|
||||
_handleEvent(events[i], id, "ephemeral");
|
||||
|
||||
// Receipt events are deltas between two states. We will create a
|
||||
// fake room account data event for this and store the difference
|
||||
// there.
|
||||
if (events[i]["type"] == "m.receipt") {
|
||||
Room room = this.getRoomById(id);
|
||||
if (room == null) room = Room(id: id);
|
||||
|
||||
Map<String, dynamic> receiptStateContent =
|
||||
room.roomAccountData["m.receipt"]?.content ?? {};
|
||||
for (var eventEntry in events[i]["content"].entries) {
|
||||
final String eventID = eventEntry.key;
|
||||
if (events[i]["content"][eventID]["m.read"] != null) {
|
||||
final Map<String, dynamic> userTimestampMap =
|
||||
events[i]["content"][eventID]["m.read"];
|
||||
for (var userTimestampMapEntry in userTimestampMap.entries) {
|
||||
final String mxid = userTimestampMapEntry.key;
|
||||
|
||||
// Remove previous receipt event from this user
|
||||
for (var entry in receiptStateContent.entries) {
|
||||
if (entry.value["m.read"] is Map<String, dynamic> &&
|
||||
entry.value["m.read"].containsKey(mxid)) {
|
||||
entry.value["m.read"].remove(mxid);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (userTimestampMap[mxid] is Map<String, dynamic> &&
|
||||
userTimestampMap[mxid].containsKey("ts")) {
|
||||
receiptStateContent[mxid] = {
|
||||
"event_id": eventID,
|
||||
"ts": userTimestampMap[mxid]["ts"],
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
events[i]["content"] = receiptStateContent;
|
||||
_handleEvent(events[i], id, "account_data");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _handleRoomEvents(String chat_id, List<dynamic> events, String type) {
|
||||
for (num i = 0; i < events.length; i++) {
|
||||
_handleEvent(events[i], chat_id, type);
|
||||
}
|
||||
}
|
||||
|
||||
void _handleGlobalEvents(List<dynamic> events, String type) {
|
||||
for (int i = 0; i < events.length; i++)
|
||||
if (events[i]["type"] is String &&
|
||||
events[i]["content"] is Map<String, dynamic>) {
|
||||
UserUpdate update = UserUpdate(
|
||||
eventType: events[i]["type"],
|
||||
type: type,
|
||||
content: events[i],
|
||||
);
|
||||
this.store?.storeUserEventUpdate(update);
|
||||
onUserEvent.add(update);
|
||||
}
|
||||
}
|
||||
|
||||
void _handleEvent(Map<String, dynamic> event, String roomID, String type) {
|
||||
if (event["type"] is String && event["content"] is Map<String, dynamic>) {
|
||||
EventUpdate update = EventUpdate(
|
||||
eventType: event["type"],
|
||||
roomID: roomID,
|
||||
type: type,
|
||||
content: event,
|
||||
);
|
||||
_updateRoomsByEventUpdate(update);
|
||||
this.store?.storeEventUpdate(update);
|
||||
onEvent.add(update);
|
||||
}
|
||||
}
|
||||
|
||||
void _updateRoomsByRoomUpdate(RoomUpdate chatUpdate) {
|
||||
// Update the chat list item.
|
||||
// Search the room in the rooms
|
||||
num j = 0;
|
||||
for (j = 0; j < rooms.length; j++) {
|
||||
if (rooms[j].id == chatUpdate.id) break;
|
||||
}
|
||||
final bool found = (j < rooms.length && rooms[j].id == chatUpdate.id);
|
||||
final bool isLeftRoom = chatUpdate.membership == Membership.leave;
|
||||
|
||||
// Does the chat already exist in the list rooms?
|
||||
if (!found && !isLeftRoom) {
|
||||
num position = chatUpdate.membership == Membership.invite ? 0 : j;
|
||||
// Add the new chat to the list
|
||||
Room newRoom = Room(
|
||||
id: chatUpdate.id,
|
||||
membership: chatUpdate.membership,
|
||||
prev_batch: chatUpdate.prev_batch,
|
||||
highlightCount: chatUpdate.highlight_count,
|
||||
notificationCount: chatUpdate.notification_count,
|
||||
mHeroes: chatUpdate.summary?.mHeroes,
|
||||
mJoinedMemberCount: chatUpdate.summary?.mJoinedMemberCount,
|
||||
mInvitedMemberCount: chatUpdate.summary?.mInvitedMemberCount,
|
||||
roomAccountData: {},
|
||||
client: this,
|
||||
);
|
||||
rooms.insert(position, newRoom);
|
||||
}
|
||||
// If the membership is "leave" then remove the item and stop here
|
||||
else if (found && isLeftRoom) {
|
||||
rooms.removeAt(j);
|
||||
}
|
||||
// Update notification, highlight count and/or additional informations
|
||||
else if (found &&
|
||||
chatUpdate.membership != Membership.leave &&
|
||||
(rooms[j].membership != chatUpdate.membership ||
|
||||
rooms[j].notificationCount != chatUpdate.notification_count ||
|
||||
rooms[j].highlightCount != chatUpdate.highlight_count ||
|
||||
chatUpdate.summary != null)) {
|
||||
rooms[j].membership = chatUpdate.membership;
|
||||
rooms[j].notificationCount = chatUpdate.notification_count;
|
||||
rooms[j].highlightCount = chatUpdate.highlight_count;
|
||||
if (chatUpdate.prev_batch != null)
|
||||
rooms[j].prev_batch = chatUpdate.prev_batch;
|
||||
if (chatUpdate.summary != null) {
|
||||
if (chatUpdate.summary.mHeroes != null)
|
||||
rooms[j].mHeroes = chatUpdate.summary.mHeroes;
|
||||
if (chatUpdate.summary.mJoinedMemberCount != null)
|
||||
rooms[j].mJoinedMemberCount = chatUpdate.summary.mJoinedMemberCount;
|
||||
if (chatUpdate.summary.mInvitedMemberCount != null)
|
||||
rooms[j].mInvitedMemberCount = chatUpdate.summary.mInvitedMemberCount;
|
||||
}
|
||||
if (rooms[j].onUpdate != null) rooms[j].onUpdate();
|
||||
}
|
||||
sortAndUpdate();
|
||||
}
|
||||
|
||||
void _updateRoomsByEventUpdate(EventUpdate eventUpdate) {
|
||||
if (eventUpdate.type == "history") return;
|
||||
// Search the room in the rooms
|
||||
num j = 0;
|
||||
for (j = 0; j < rooms.length; j++) {
|
||||
if (rooms[j].id == eventUpdate.roomID) break;
|
||||
}
|
||||
final bool found = (j < rooms.length && rooms[j].id == eventUpdate.roomID);
|
||||
if (!found) return;
|
||||
if (eventUpdate.type == "timeline" ||
|
||||
eventUpdate.type == "state" ||
|
||||
eventUpdate.type == "invite_state") {
|
||||
Event stateEvent = Event.fromJson(eventUpdate.content, rooms[j]);
|
||||
if (stateEvent.type == EventTypes.Redaction) {
|
||||
final String redacts = eventUpdate.content["redacts"];
|
||||
rooms[j].states.states.forEach(
|
||||
(String key, Map<String, Event> states) => states.forEach(
|
||||
(String key, Event state) {
|
||||
if (state.eventId == redacts) {
|
||||
state.setRedactionEvent(stateEvent);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
} else {
|
||||
Event prevState =
|
||||
rooms[j].getState(stateEvent.typeKey, stateEvent.stateKey);
|
||||
if (prevState != null &&
|
||||
prevState.time.millisecondsSinceEpoch >
|
||||
stateEvent.time.millisecondsSinceEpoch) return;
|
||||
rooms[j].setState(stateEvent);
|
||||
}
|
||||
} else if (eventUpdate.type == "account_data") {
|
||||
rooms[j].roomAccountData[eventUpdate.eventType] =
|
||||
RoomAccountData.fromJson(eventUpdate.content, rooms[j]);
|
||||
} else if (eventUpdate.type == "ephemeral") {
|
||||
rooms[j].ephemerals[eventUpdate.eventType] =
|
||||
RoomAccountData.fromJson(eventUpdate.content, rooms[j]);
|
||||
}
|
||||
if (rooms[j].onUpdate != null) rooms[j].onUpdate();
|
||||
if (eventUpdate.type == "timeline") sortAndUpdate();
|
||||
}
|
||||
|
||||
bool sortLock = false;
|
||||
|
||||
sortAndUpdate() {
|
||||
if (prevBatch == null) return;
|
||||
if (sortLock || rooms.length < 2) return;
|
||||
sortLock = true;
|
||||
rooms?.sort((a, b) => b.timeCreated.millisecondsSinceEpoch
|
||||
.compareTo(a.timeCreated.millisecondsSinceEpoch));
|
||||
sortLock = false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,494 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Zender & Kurtz GbR.
|
||||
*
|
||||
* Authors:
|
||||
* Christian Pauly <krille@famedly.com>
|
||||
* Marcel Radzio <mtrnord@famedly.com>
|
||||
*
|
||||
* This file is part of famedlysdk.
|
||||
*
|
||||
* famedlysdk is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* famedlysdk is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with famedlysdk. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:core';
|
||||
|
||||
import 'package:famedlysdk/src/Room.dart';
|
||||
import 'package:famedlysdk/src/RoomList.dart';
|
||||
import 'package:famedlysdk/src/utils/MatrixFile.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:mime_type/mime_type.dart';
|
||||
|
||||
import 'Client.dart';
|
||||
import 'User.dart';
|
||||
import 'sync/EventUpdate.dart';
|
||||
import 'sync/RoomUpdate.dart';
|
||||
import 'sync/UserUpdate.dart';
|
||||
import 'utils/MatrixException.dart';
|
||||
|
||||
enum HTTPType { GET, POST, PUT, DELETE }
|
||||
|
||||
/// Represents a Matrix connection to communicate with a
|
||||
/// [Matrix](https://matrix.org) homeserver.
|
||||
class Connection {
|
||||
final Client client;
|
||||
|
||||
Connection(this.client);
|
||||
|
||||
static String syncFilters = '{"room":{"state":{"lazy_load_members":true}}}';
|
||||
|
||||
/// Handles the connection to the Matrix Homeserver. You can change this to a
|
||||
/// MockClient for testing.
|
||||
http.Client httpClient = http.Client();
|
||||
|
||||
/// The newEvent signal is the most important signal in this concept. Every time
|
||||
/// the app receives a new synchronization, this event is called for every signal
|
||||
/// to update the GUI. For example, for a new message, it is called:
|
||||
/// onRoomEvent( "m.room.message", "!chat_id:server.com", "timeline", {sender: "@bob:server.com", body: "Hello world"} )
|
||||
final StreamController<EventUpdate> onEvent =
|
||||
new StreamController.broadcast();
|
||||
|
||||
/// Outside of the events there are updates for the global chat states which
|
||||
/// are handled by this signal:
|
||||
final StreamController<RoomUpdate> onRoomUpdate =
|
||||
new StreamController.broadcast();
|
||||
|
||||
/// Outside of rooms there are account updates like account_data or presences.
|
||||
final StreamController<UserUpdate> onUserEvent =
|
||||
new StreamController.broadcast();
|
||||
|
||||
/// Called when the login state e.g. user gets logged out.
|
||||
final StreamController<LoginState> onLoginStateChanged =
|
||||
new StreamController.broadcast();
|
||||
|
||||
/// Synchronization erros are coming here.
|
||||
final StreamController<MatrixException> onError =
|
||||
new StreamController.broadcast();
|
||||
|
||||
/// This is called once, when the first sync has received.
|
||||
final StreamController<bool> onFirstSync = new StreamController.broadcast();
|
||||
|
||||
/// When a new sync response is coming in, this gives the complete payload.
|
||||
final StreamController<dynamic> onSync = new StreamController.broadcast();
|
||||
|
||||
/// Matrix synchronisation is done with https long polling. This needs a
|
||||
/// timeout which is usually 30 seconds.
|
||||
int syncTimeoutSec = 30;
|
||||
|
||||
/// How long should the app wait until it retrys the synchronisation after
|
||||
/// an error?
|
||||
int syncErrorTimeoutSec = 3;
|
||||
|
||||
/// Sets the user credentials and starts the synchronisation.
|
||||
///
|
||||
/// Before you can connect you need at least an [accessToken], a [homeserver],
|
||||
/// a [userID], a [deviceID], and a [deviceName].
|
||||
///
|
||||
/// You get this informations
|
||||
/// by logging in to your Matrix account, using the [login API](https://matrix.org/docs/spec/client_server/r0.4.0.html#post-matrix-client-r0-login).
|
||||
///
|
||||
/// To log in you can use [jsonRequest()] after you have set the [homeserver]
|
||||
/// to a valid url. For example:
|
||||
///
|
||||
/// ```
|
||||
/// final resp = await matrix
|
||||
/// .jsonRequest(type: HTTPType.POST, action: "/client/r0/login", data: {
|
||||
/// "type": "m.login.password",
|
||||
/// "user": "test",
|
||||
/// "password": "1234",
|
||||
/// "initial_device_display_name": "Fluffy Matrix Client"
|
||||
/// });
|
||||
/// ```
|
||||
///
|
||||
/// Returns:
|
||||
///
|
||||
/// ```
|
||||
/// {
|
||||
/// "user_id": "@cheeky_monkey:matrix.org",
|
||||
/// "access_token": "abc123",
|
||||
/// "device_id": "GHTYAJCE"
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Sends [LoginState.logged] to [onLoginStateChanged].
|
||||
void connect(
|
||||
{String newToken,
|
||||
String newHomeserver,
|
||||
String newUserID,
|
||||
String newDeviceName,
|
||||
String newDeviceID,
|
||||
List<String> newMatrixVersions,
|
||||
bool newLazyLoadMembers,
|
||||
String newPrevBatch}) async {
|
||||
client.accessToken = newToken;
|
||||
client.homeserver = newHomeserver;
|
||||
client.userID = newUserID;
|
||||
client.deviceID = newDeviceID;
|
||||
client.deviceName = newDeviceName;
|
||||
client.matrixVersions = newMatrixVersions;
|
||||
client.lazyLoadMembers = newLazyLoadMembers;
|
||||
client.prevBatch = newPrevBatch;
|
||||
|
||||
List<Room> rooms = [];
|
||||
if (client.store != null) {
|
||||
client.store.storeClient();
|
||||
rooms = await client.store.getRoomList(onlyLeft: false);
|
||||
client.accountData = await client.store.getAccountData();
|
||||
client.presences = await client.store.getPresences();
|
||||
}
|
||||
|
||||
client.roomList = RoomList(
|
||||
client: client,
|
||||
onlyLeft: false,
|
||||
onUpdate: null,
|
||||
onInsert: null,
|
||||
onRemove: null,
|
||||
rooms: rooms);
|
||||
|
||||
_userEventSub ??= onUserEvent.stream.listen(client.handleUserUpdate);
|
||||
|
||||
onLoginStateChanged.add(LoginState.logged);
|
||||
|
||||
_sync();
|
||||
}
|
||||
|
||||
StreamSubscription _userEventSub;
|
||||
|
||||
/// Resets all settings and stops the synchronisation.
|
||||
void clear() {
|
||||
client.store?.clear();
|
||||
client.accessToken = client.homeserver = client.userID = client.deviceID =
|
||||
client.deviceName = client.matrixVersions =
|
||||
client.lazyLoadMembers = client.prevBatch = null;
|
||||
onLoginStateChanged.add(LoginState.loggedOut);
|
||||
}
|
||||
|
||||
/// Used for all Matrix json requests using the [c2s API](https://matrix.org/docs/spec/client_server/r0.4.0.html).
|
||||
///
|
||||
/// Throws: TimeoutException, FormatException, MatrixException
|
||||
///
|
||||
/// You must first call [this.connect()] or set [this.homeserver] before you can use
|
||||
/// this! For example to send a message to a Matrix room with the id
|
||||
/// '!fjd823j:example.com' you call:
|
||||
///
|
||||
/// ```
|
||||
/// final resp = await jsonRequest(
|
||||
/// type: HTTPType.PUT,
|
||||
/// action: "/r0/rooms/!fjd823j:example.com/send/m.room.message/$txnId",
|
||||
/// data: {
|
||||
/// "msgtype": "m.text",
|
||||
/// "body": "hello"
|
||||
/// }
|
||||
/// );
|
||||
/// ```
|
||||
///
|
||||
Future<Map<String, dynamic>> jsonRequest(
|
||||
{HTTPType type,
|
||||
String action,
|
||||
dynamic data = "",
|
||||
int timeout,
|
||||
String contentType = "application/json"}) async {
|
||||
if (client.isLogged() == false && client.homeserver == null)
|
||||
throw ("No homeserver specified.");
|
||||
if (timeout == null) timeout = syncTimeoutSec + 5;
|
||||
dynamic json;
|
||||
if (data is Map) data.removeWhere((k, v) => v == null);
|
||||
(!(data is String)) ? json = jsonEncode(data) : json = data;
|
||||
if (data is List<int> || action.startsWith("/media/r0/upload")) json = data;
|
||||
|
||||
final url = "${client.homeserver}/_matrix${action}";
|
||||
|
||||
Map<String, String> headers = {};
|
||||
if (type == HTTPType.PUT || type == HTTPType.POST)
|
||||
headers["Content-Type"] = contentType;
|
||||
if (client.isLogged())
|
||||
headers["Authorization"] = "Bearer ${client.accessToken}";
|
||||
|
||||
if (client.debug)
|
||||
print(
|
||||
"[REQUEST ${type.toString().split('.').last}] Action: $action, Data: $data");
|
||||
|
||||
http.Response resp;
|
||||
Map<String, dynamic> jsonResp = {};
|
||||
try {
|
||||
switch (type.toString().split('.').last) {
|
||||
case "GET":
|
||||
resp = await httpClient
|
||||
.get(url, headers: headers)
|
||||
.timeout(Duration(seconds: timeout));
|
||||
break;
|
||||
case "POST":
|
||||
resp = await httpClient
|
||||
.post(url, body: json, headers: headers)
|
||||
.timeout(Duration(seconds: timeout));
|
||||
break;
|
||||
case "PUT":
|
||||
resp = await httpClient
|
||||
.put(url, body: json, headers: headers)
|
||||
.timeout(Duration(seconds: timeout));
|
||||
break;
|
||||
case "DELETE":
|
||||
resp = await httpClient
|
||||
.delete(url, headers: headers)
|
||||
.timeout(Duration(seconds: timeout));
|
||||
break;
|
||||
}
|
||||
jsonResp = jsonDecode(resp.body)
|
||||
as Map<String, dynamic>; // May throw FormatException
|
||||
|
||||
if (jsonResp.containsKey("errcode") && jsonResp["errcode"] is String) {
|
||||
// The server has responsed with an matrix related error.
|
||||
MatrixException exception = MatrixException(resp);
|
||||
if (exception.error == MatrixError.M_UNKNOWN_TOKEN) {
|
||||
// The token is no longer valid. Need to sign off....
|
||||
onError.add(exception);
|
||||
clear();
|
||||
}
|
||||
|
||||
throw exception;
|
||||
}
|
||||
|
||||
if (client.debug) print("[RESPONSE] ${jsonResp.toString()}");
|
||||
} on ArgumentError catch (exception) {
|
||||
print(exception);
|
||||
// Ignore this error
|
||||
} catch (_) {
|
||||
print(_);
|
||||
rethrow;
|
||||
}
|
||||
|
||||
return jsonResp;
|
||||
}
|
||||
|
||||
/// Uploads a file with the name [fileName] as base64 encoded to the server
|
||||
/// and returns the mxc url as a string.
|
||||
Future<String> upload(MatrixFile file) async {
|
||||
dynamic fileBytes;
|
||||
if (client.homeserver != "https://fakeServer.notExisting")
|
||||
fileBytes = file.bytes;
|
||||
String fileName = file.path.split("/").last.toLowerCase();
|
||||
String mimeType = mime(file.path);
|
||||
print("[UPLOADING] $fileName, type: $mimeType, size: ${fileBytes?.length}");
|
||||
final Map<String, dynamic> resp = await jsonRequest(
|
||||
type: HTTPType.POST,
|
||||
action: "/media/r0/upload?filename=$fileName",
|
||||
data: fileBytes,
|
||||
contentType: mimeType);
|
||||
return resp["content_uri"];
|
||||
}
|
||||
|
||||
Future<dynamic> _syncRequest;
|
||||
|
||||
Future<void> _sync() async {
|
||||
if (client.isLogged() == false) return;
|
||||
|
||||
String action = "/client/r0/sync?filter=$syncFilters";
|
||||
|
||||
if (client.prevBatch != null) {
|
||||
action += "&timeout=30000";
|
||||
action += "&since=${client.prevBatch}";
|
||||
}
|
||||
try {
|
||||
_syncRequest = jsonRequest(type: HTTPType.GET, action: action);
|
||||
final int hash = _syncRequest.hashCode;
|
||||
final syncResp = await _syncRequest;
|
||||
if (hash != _syncRequest.hashCode) return;
|
||||
if (client.store != null)
|
||||
await client.store.transaction(() {
|
||||
handleSync(syncResp);
|
||||
client.store.storePrevBatch(syncResp);
|
||||
return;
|
||||
});
|
||||
else
|
||||
await handleSync(syncResp);
|
||||
if (client.prevBatch == null) client.connection.onFirstSync.add(true);
|
||||
client.prevBatch = syncResp["next_batch"];
|
||||
if (hash == _syncRequest.hashCode) _sync();
|
||||
} on MatrixException catch (exception) {
|
||||
onError.add(exception);
|
||||
await Future.delayed(Duration(seconds: syncErrorTimeoutSec), _sync);
|
||||
} catch (exception) {
|
||||
await Future.delayed(Duration(seconds: syncErrorTimeoutSec), _sync);
|
||||
}
|
||||
}
|
||||
|
||||
void handleSync(dynamic sync) {
|
||||
if (sync["rooms"] is Map<String, dynamic>) {
|
||||
if (sync["rooms"]["join"] is Map<String, dynamic>)
|
||||
_handleRooms(sync["rooms"]["join"], Membership.join);
|
||||
if (sync["rooms"]["invite"] is Map<String, dynamic>)
|
||||
_handleRooms(sync["rooms"]["invite"], Membership.invite);
|
||||
if (sync["rooms"]["leave"] is Map<String, dynamic>)
|
||||
_handleRooms(sync["rooms"]["leave"], Membership.leave);
|
||||
}
|
||||
if (sync["presence"] is Map<String, dynamic> &&
|
||||
sync["presence"]["events"] is List<dynamic>) {
|
||||
_handleGlobalEvents(sync["presence"]["events"], "presence");
|
||||
}
|
||||
if (sync["account_data"] is Map<String, dynamic> &&
|
||||
sync["account_data"]["events"] is List<dynamic>) {
|
||||
_handleGlobalEvents(sync["account_data"]["events"], "account_data");
|
||||
}
|
||||
if (sync["to_device"] is Map<String, dynamic> &&
|
||||
sync["to_device"]["events"] is List<dynamic>) {
|
||||
_handleGlobalEvents(sync["to_device"]["events"], "to_device");
|
||||
}
|
||||
onSync.add(sync);
|
||||
}
|
||||
|
||||
void _handleRooms(Map<String, dynamic> rooms, Membership membership) {
|
||||
rooms.forEach((String id, dynamic room) async {
|
||||
// calculate the notification counts, the limitedTimeline and prevbatch
|
||||
num highlight_count = 0;
|
||||
num notification_count = 0;
|
||||
String prev_batch = "";
|
||||
bool limitedTimeline = false;
|
||||
|
||||
if (room["unread_notifications"] is Map<String, dynamic>) {
|
||||
if (room["unread_notifications"]["highlight_count"] is num)
|
||||
highlight_count = room["unread_notifications"]["highlight_count"];
|
||||
if (room["unread_notifications"]["notification_count"] is num)
|
||||
notification_count =
|
||||
room["unread_notifications"]["notification_count"];
|
||||
}
|
||||
|
||||
if (room["timeline"] is Map<String, dynamic>) {
|
||||
if (room["timeline"]["limited"] is bool)
|
||||
limitedTimeline = room["timeline"]["limited"];
|
||||
if (room["timeline"]["prev_batch"] is String)
|
||||
prev_batch = room["timeline"]["prev_batch"];
|
||||
}
|
||||
|
||||
RoomSummary summary;
|
||||
|
||||
if (room["summary"] is Map<String, dynamic>) {
|
||||
summary = RoomSummary.fromJson(room["summary"]);
|
||||
}
|
||||
|
||||
RoomUpdate update = RoomUpdate(
|
||||
id: id,
|
||||
membership: membership,
|
||||
notification_count: notification_count,
|
||||
highlight_count: highlight_count,
|
||||
limitedTimeline: limitedTimeline,
|
||||
prev_batch: prev_batch,
|
||||
summary: summary,
|
||||
);
|
||||
client.store?.storeRoomUpdate(update);
|
||||
onRoomUpdate.add(update);
|
||||
|
||||
/// Handle now all room events and save them in the database
|
||||
if (room["state"] is Map<String, dynamic> &&
|
||||
room["state"]["events"] is List<dynamic>)
|
||||
_handleRoomEvents(id, room["state"]["events"], "state");
|
||||
|
||||
if (room["invite_state"] is Map<String, dynamic> &&
|
||||
room["invite_state"]["events"] is List<dynamic>)
|
||||
_handleRoomEvents(id, room["invite_state"]["events"], "invite_state");
|
||||
|
||||
if (room["timeline"] is Map<String, dynamic> &&
|
||||
room["timeline"]["events"] is List<dynamic>)
|
||||
_handleRoomEvents(id, room["timeline"]["events"], "timeline");
|
||||
|
||||
if (room["ephemeral"] is Map<String, dynamic> &&
|
||||
room["ephemeral"]["events"] is List<dynamic>)
|
||||
_handleEphemerals(id, room["ephemeral"]["events"]);
|
||||
|
||||
if (room["account_data"] is Map<String, dynamic> &&
|
||||
room["account_data"]["events"] is List<dynamic>)
|
||||
_handleRoomEvents(id, room["account_data"]["events"], "account_data");
|
||||
});
|
||||
}
|
||||
|
||||
void _handleEphemerals(String id, List<dynamic> events) {
|
||||
for (num i = 0; i < events.length; i++) {
|
||||
_handleEvent(events[i], id, "ephemeral");
|
||||
|
||||
// Receipt events are deltas between two states. We will create a
|
||||
// fake room account data event for this and store the difference
|
||||
// there.
|
||||
if (events[i]["type"] == "m.receipt") {
|
||||
Room room = client.roomList.getRoomById(id);
|
||||
if (room == null) room = Room(id: id);
|
||||
|
||||
Map<String, dynamic> receiptStateContent =
|
||||
room.roomAccountData["m.receipt"]?.content ?? {};
|
||||
for (var eventEntry in events[i]["content"].entries) {
|
||||
final String eventID = eventEntry.key;
|
||||
if (events[i]["content"][eventID]["m.read"] != null) {
|
||||
final Map<String, dynamic> userTimestampMap =
|
||||
events[i]["content"][eventID]["m.read"];
|
||||
for (var userTimestampMapEntry in userTimestampMap.entries) {
|
||||
final String mxid = userTimestampMapEntry.key;
|
||||
|
||||
// Remove previous receipt event from this user
|
||||
for (var entry in receiptStateContent.entries) {
|
||||
if (entry.value["m.read"] is Map<String, dynamic> &&
|
||||
entry.value["m.read"].containsKey(mxid)) {
|
||||
entry.value["m.read"].remove(mxid);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (userTimestampMap[mxid] is Map<String, dynamic> &&
|
||||
userTimestampMap[mxid].containsKey("ts")) {
|
||||
receiptStateContent[mxid] = {
|
||||
"event_id": eventID,
|
||||
"ts": userTimestampMap[mxid]["ts"],
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
events[i]["content"] = receiptStateContent;
|
||||
_handleEvent(events[i], id, "account_data");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _handleRoomEvents(String chat_id, List<dynamic> events, String type) {
|
||||
for (num i = 0; i < events.length; i++) {
|
||||
_handleEvent(events[i], chat_id, type);
|
||||
}
|
||||
}
|
||||
|
||||
void _handleGlobalEvents(List<dynamic> events, String type) {
|
||||
for (int i = 0; i < events.length; i++)
|
||||
if (events[i]["type"] is String &&
|
||||
events[i]["content"] is Map<String, dynamic>) {
|
||||
UserUpdate update = UserUpdate(
|
||||
eventType: events[i]["type"],
|
||||
type: type,
|
||||
content: events[i],
|
||||
);
|
||||
client.store?.storeUserEventUpdate(update);
|
||||
onUserEvent.add(update);
|
||||
}
|
||||
}
|
||||
|
||||
void _handleEvent(Map<String, dynamic> event, String roomID, String type) {
|
||||
if (event["type"] is String && event["content"] is Map<String, dynamic>) {
|
||||
EventUpdate update = EventUpdate(
|
||||
eventType: event["type"],
|
||||
roomID: roomID,
|
||||
type: type,
|
||||
content: event,
|
||||
);
|
||||
client.store?.storeEventUpdate(update);
|
||||
onEvent.add(update);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum LoginState { logged, loggedOut }
|
|
@ -21,73 +21,268 @@
|
|||
* along with famedlysdk. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'package:famedlysdk/src/RoomState.dart';
|
||||
import 'package:famedlysdk/src/sync/EventUpdate.dart';
|
||||
import 'package:famedlysdk/src/utils/ChatTime.dart';
|
||||
import 'dart:convert';
|
||||
import 'package:famedlysdk/famedlysdk.dart';
|
||||
import 'package:famedlysdk/src/utils/Receipt.dart';
|
||||
|
||||
import './Room.dart';
|
||||
|
||||
/// Defines a timeline event for a room.
|
||||
class Event extends RoomState {
|
||||
class Event {
|
||||
/// 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.
|
||||
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.getUserByMXIDSync(senderId);
|
||||
|
||||
/// The time this event has received at the server. May be null for events like
|
||||
/// account data.
|
||||
final DateTime time;
|
||||
|
||||
/// Optional additional content for this event.
|
||||
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.
|
||||
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;
|
||||
|
||||
/// The status of this event.
|
||||
/// -1=ERROR
|
||||
/// 0=SENDING
|
||||
/// 1=SENT
|
||||
/// 2=RECEIVED
|
||||
/// 2=TIMELINE
|
||||
/// 3=ROOM_STATE
|
||||
int status;
|
||||
|
||||
static const int defaultStatus = 2;
|
||||
static const Map<String, int> STATUS_TYPE = {
|
||||
"ERROR": -1,
|
||||
"SENDING": 0,
|
||||
"SENT": 1,
|
||||
"TIMELINE": 2,
|
||||
"ROOM_STATE": 3,
|
||||
};
|
||||
|
||||
/// Optional. The event that redacted this event, if any. Otherwise null.
|
||||
Event get redactedBecause =>
|
||||
unsigned != null && unsigned.containsKey("redacted_because")
|
||||
? Event.fromJson(unsigned["redacted_because"], room)
|
||||
: null;
|
||||
|
||||
bool get redacted => redactedBecause != null;
|
||||
|
||||
User get stateKeyUser => room.getUserByMXIDSync(stateKey);
|
||||
|
||||
Event(
|
||||
{this.status = defaultStatus,
|
||||
dynamic content,
|
||||
String typeKey,
|
||||
String eventId,
|
||||
String roomId,
|
||||
String senderId,
|
||||
ChatTime time,
|
||||
dynamic unsigned,
|
||||
dynamic prevContent,
|
||||
String stateKey,
|
||||
Room room,
|
||||
Event redactedBecause})
|
||||
: super(
|
||||
content: content,
|
||||
typeKey: typeKey,
|
||||
eventId: eventId,
|
||||
roomId: roomId,
|
||||
senderId: senderId,
|
||||
time: time,
|
||||
unsigned: unsigned,
|
||||
prevContent: prevContent,
|
||||
stateKey: stateKey,
|
||||
room: room);
|
||||
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 Event.fromJson(Map<String, dynamic> jsonPayload, Room room) {
|
||||
final Map<String, dynamic> content =
|
||||
RoomState.getMapFromPayload(jsonPayload['content']);
|
||||
Event.getMapFromPayload(jsonPayload['content']);
|
||||
final Map<String, dynamic> unsigned =
|
||||
RoomState.getMapFromPayload(jsonPayload['unsigned']);
|
||||
Event.getMapFromPayload(jsonPayload['unsigned']);
|
||||
final Map<String, dynamic> prevContent =
|
||||
RoomState.getMapFromPayload(jsonPayload['prev_content']);
|
||||
Event redactedBecause = null;
|
||||
if (unsigned.containsKey("redacted_because"))
|
||||
redactedBecause = Event.fromJson(unsigned["redacted_because"], room);
|
||||
Event.getMapFromPayload(jsonPayload['prev_content']);
|
||||
return Event(
|
||||
status: jsonPayload['status'] ?? defaultStatus,
|
||||
status: jsonPayload['status'] ?? defaultStatus,
|
||||
stateKey: jsonPayload['state_key'],
|
||||
prevContent: prevContent,
|
||||
content: content,
|
||||
typeKey: jsonPayload['type'],
|
||||
eventId: jsonPayload['event_id'],
|
||||
roomId: jsonPayload['room_id'],
|
||||
senderId: jsonPayload['sender'],
|
||||
time: jsonPayload.containsKey('origin_server_ts')
|
||||
? DateTime.fromMillisecondsSinceEpoch(jsonPayload['origin_server_ts'])
|
||||
: DateTime.now(),
|
||||
unsigned: unsigned,
|
||||
room: room,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = new Map<String, dynamic>();
|
||||
if (this.stateKey != null) data['state_key'] = this.stateKey;
|
||||
if (this.prevContent != null && this.prevContent.isNotEmpty)
|
||||
data['prev_content'] = this.prevContent;
|
||||
data['content'] = this.content;
|
||||
data['type'] = this.typeKey;
|
||||
data['event_id'] = this.eventId;
|
||||
data['room_id'] = this.roomId;
|
||||
data['sender'] = this.senderId;
|
||||
data['origin_server_ts'] = this.time.millisecondsSinceEpoch;
|
||||
if (this.unsigned != null && this.unsigned.isNotEmpty)
|
||||
data['unsigned'] = this.unsigned;
|
||||
return data;
|
||||
}
|
||||
|
||||
Event get timelineEvent => Event(
|
||||
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'],
|
||||
typeKey: typeKey,
|
||||
eventId: eventId,
|
||||
room: room,
|
||||
redactedBecause: redactedBecause);
|
||||
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.
|
||||
@deprecated
|
||||
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.redaction":
|
||||
return EventTypes.Redaction;
|
||||
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;
|
||||
}
|
||||
|
||||
void setRedactionEvent(Event redactedBecause) {
|
||||
unsigned = {
|
||||
"redacted_because": redactedBecause.toJson(),
|
||||
};
|
||||
prevContent = null;
|
||||
List<String> contentKeyWhiteList = [];
|
||||
switch (type) {
|
||||
case EventTypes.RoomMember:
|
||||
contentKeyWhiteList.add("membership");
|
||||
break;
|
||||
case EventTypes.RoomMember:
|
||||
contentKeyWhiteList.add("membership");
|
||||
break;
|
||||
case EventTypes.RoomCreate:
|
||||
contentKeyWhiteList.add("creator");
|
||||
break;
|
||||
case EventTypes.RoomJoinRules:
|
||||
contentKeyWhiteList.add("join_rule");
|
||||
break;
|
||||
case EventTypes.RoomPowerLevels:
|
||||
contentKeyWhiteList.add("ban");
|
||||
contentKeyWhiteList.add("events");
|
||||
contentKeyWhiteList.add("events_default");
|
||||
contentKeyWhiteList.add("kick");
|
||||
contentKeyWhiteList.add("redact");
|
||||
contentKeyWhiteList.add("state_default");
|
||||
contentKeyWhiteList.add("users");
|
||||
contentKeyWhiteList.add("users_default");
|
||||
break;
|
||||
case EventTypes.RoomAliases:
|
||||
contentKeyWhiteList.add("aliases");
|
||||
break;
|
||||
case EventTypes.HistoryVisibility:
|
||||
contentKeyWhiteList.add("history_visibility");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
List<String> toRemoveList = [];
|
||||
for (var entry in content.entries) {
|
||||
if (contentKeyWhiteList.indexOf(entry.key) == -1) {
|
||||
toRemoveList.add(entry.key);
|
||||
}
|
||||
}
|
||||
toRemoveList.forEach((s) => content.remove(s));
|
||||
}
|
||||
|
||||
/// Returns the body of this event if it has a body.
|
||||
|
@ -110,8 +305,8 @@ class Event extends RoomState {
|
|||
List<Receipt> receiptsList = [];
|
||||
for (var entry in room.roomAccountData["m.receipt"].content.entries) {
|
||||
if (entry.value["event_id"] == eventId)
|
||||
receiptsList.add(Receipt(
|
||||
room.getUserByMXIDSync(entry.key), ChatTime(entry.value["ts"])));
|
||||
receiptsList.add(Receipt(room.getUserByMXIDSync(entry.key),
|
||||
DateTime.fromMillisecondsSinceEpoch(entry.value["ts"])));
|
||||
}
|
||||
return receiptsList;
|
||||
}
|
||||
|
@ -123,7 +318,7 @@ class Event extends RoomState {
|
|||
if (room.client.store != null)
|
||||
await room.client.store.removeEvent(eventId);
|
||||
|
||||
room.client.connection.onEvent.add(EventUpdate(
|
||||
room.client.onEvent.add(EventUpdate(
|
||||
roomID: room.id,
|
||||
type: "timeline",
|
||||
eventType: typeKey,
|
||||
|
@ -152,3 +347,28 @@ class Event extends RoomState {
|
|||
Future<dynamic> redact({String reason, String txid}) =>
|
||||
room.redactEvent(eventId, reason: reason, txid: txid);
|
||||
}
|
||||
|
||||
enum EventTypes {
|
||||
Text,
|
||||
Emote,
|
||||
Notice,
|
||||
Image,
|
||||
Video,
|
||||
Audio,
|
||||
Redaction,
|
||||
File,
|
||||
Location,
|
||||
Reply,
|
||||
RoomAliases,
|
||||
RoomCanonicalAlias,
|
||||
RoomCreate,
|
||||
RoomJoinRules,
|
||||
RoomMember,
|
||||
RoomPowerLevels,
|
||||
RoomName,
|
||||
RoomTopic,
|
||||
RoomAvatar,
|
||||
GuestAccess,
|
||||
HistoryVisibility,
|
||||
Unknown,
|
||||
}
|
||||
|
|
|
@ -42,8 +42,8 @@ class Presence {
|
|||
|
||||
Presence.fromJson(Map<String, dynamic> json)
|
||||
: sender = json['sender'],
|
||||
displayname = json['content']['avatar_url'],
|
||||
avatarUrl = MxContent(json['content']['avatar_url']),
|
||||
displayname = json['content']['displayname'],
|
||||
avatarUrl = MxContent(json['content']['avatar_url'] ?? ""),
|
||||
currentlyActive = json['content']['currently_active'],
|
||||
lastActiveAgo = json['content']['last_active_ago'],
|
||||
presence = PresenceType.values.firstWhere(
|
||||
|
|
|
@ -24,10 +24,8 @@
|
|||
import 'package:famedlysdk/src/Client.dart';
|
||||
import 'package:famedlysdk/src/Event.dart';
|
||||
import 'package:famedlysdk/src/RoomAccountData.dart';
|
||||
import 'package:famedlysdk/src/RoomState.dart';
|
||||
import 'package:famedlysdk/src/sync/EventUpdate.dart';
|
||||
import 'package:famedlysdk/src/sync/RoomUpdate.dart';
|
||||
import 'package:famedlysdk/src/utils/ChatTime.dart';
|
||||
import 'package:famedlysdk/src/utils/MatrixException.dart';
|
||||
import 'package:famedlysdk/src/utils/MatrixFile.dart';
|
||||
import 'package:famedlysdk/src/utils/MxContent.dart';
|
||||
|
@ -35,7 +33,6 @@ import 'package:famedlysdk/src/utils/MxContent.dart';
|
|||
import 'package:mime_type/mime_type.dart';
|
||||
|
||||
import './User.dart';
|
||||
import 'Connection.dart';
|
||||
import 'Timeline.dart';
|
||||
import 'utils/StatesMap.dart';
|
||||
|
||||
|
@ -76,14 +73,14 @@ class Room {
|
|||
/// Key-Value store for private account data only visible for this user.
|
||||
Map<String, RoomAccountData> roomAccountData = {};
|
||||
|
||||
/// Returns the [RoomState] for the given [typeKey] and optional [stateKey].
|
||||
/// Returns the [Event] for the given [typeKey] and optional [stateKey].
|
||||
/// If no [stateKey] is provided, it defaults to an empty string.
|
||||
RoomState getState(String typeKey, [String stateKey = ""]) =>
|
||||
Event getState(String typeKey, [String stateKey = ""]) =>
|
||||
states.states[typeKey] != null ? states.states[typeKey][stateKey] : null;
|
||||
|
||||
/// Adds the [state] to this room and overwrites a state with the same
|
||||
/// typeKey/stateKey key pair if there is one.
|
||||
void setState(RoomState state) {
|
||||
void setState(Event state) {
|
||||
if (!states.states.containsKey(state.typeKey))
|
||||
states.states[state.typeKey] = {};
|
||||
states.states[state.typeKey][state.stateKey ?? ""] = state;
|
||||
|
@ -150,13 +147,15 @@ class Room {
|
|||
String notificationSettings;
|
||||
|
||||
Event get lastEvent {
|
||||
ChatTime lastTime = ChatTime(0);
|
||||
DateTime lastTime = DateTime.fromMillisecondsSinceEpoch(0);
|
||||
Event lastEvent = getState("m.room.message")?.timelineEvent;
|
||||
if (lastEvent == null)
|
||||
states.forEach((final String key, final entry) {
|
||||
if (!entry.containsKey("")) return;
|
||||
final RoomState state = entry[""];
|
||||
if (state.time != null && state.time > lastTime) {
|
||||
final Event state = entry[""];
|
||||
if (state.time != null &&
|
||||
state.time.millisecondsSinceEpoch >
|
||||
lastTime.millisecondsSinceEpoch) {
|
||||
lastTime = state.time;
|
||||
lastEvent = state.timelineEvent;
|
||||
}
|
||||
|
@ -211,7 +210,7 @@ class Room {
|
|||
} else {
|
||||
if (states["m.room.member"] is Map<String, dynamic>) {
|
||||
for (var entry in states["m.room.member"].entries) {
|
||||
RoomState state = entry.value;
|
||||
Event state = entry.value;
|
||||
if (state.type == EventTypes.RoomMember &&
|
||||
state.stateKey != client?.userID) heroes.add(state.stateKey);
|
||||
}
|
||||
|
@ -241,17 +240,17 @@ class Room {
|
|||
}
|
||||
|
||||
/// When the last message received.
|
||||
ChatTime get timeCreated {
|
||||
DateTime get timeCreated {
|
||||
if (lastEvent != null)
|
||||
return lastEvent.time;
|
||||
else
|
||||
return ChatTime.now();
|
||||
return DateTime.now();
|
||||
}
|
||||
|
||||
/// Call the Matrix API to change the name of this room. Returns the event ID of the
|
||||
/// new m.room.name event.
|
||||
Future<String> setName(String newName) async {
|
||||
final Map<String, dynamic> resp = await client.connection.jsonRequest(
|
||||
final Map<String, dynamic> resp = await client.jsonRequest(
|
||||
type: HTTPType.PUT,
|
||||
action: "/client/r0/rooms/${id}/state/m.room.name",
|
||||
data: {"name": newName});
|
||||
|
@ -260,7 +259,7 @@ class Room {
|
|||
|
||||
/// Call the Matrix API to change the topic of this room.
|
||||
Future<String> setDescription(String newName) async {
|
||||
final Map<String, dynamic> resp = await client.connection.jsonRequest(
|
||||
final Map<String, dynamic> resp = await client.jsonRequest(
|
||||
type: HTTPType.PUT,
|
||||
action: "/client/r0/rooms/${id}/state/m.room.topic",
|
||||
data: {"topic": newName});
|
||||
|
@ -270,7 +269,7 @@ class Room {
|
|||
Future<String> _sendRawEventNow(Map<String, dynamic> content,
|
||||
{String txid = null}) async {
|
||||
if (txid == null) txid = "txid${DateTime.now().millisecondsSinceEpoch}";
|
||||
final Map<String, dynamic> res = await client.connection.jsonRequest(
|
||||
final Map<String, dynamic> res = await client.jsonRequest(
|
||||
type: HTTPType.PUT,
|
||||
action: "/client/r0/rooms/${id}/send/m.room.message/$txid",
|
||||
data: content);
|
||||
|
@ -289,7 +288,7 @@ class Room {
|
|||
if (msgType == "m.video") return sendAudioEvent(file);
|
||||
String fileName = file.path.split("/").last;
|
||||
|
||||
final String uploadResp = await client.connection.upload(file);
|
||||
final String uploadResp = await client.upload(file);
|
||||
|
||||
// Send event
|
||||
Map<String, dynamic> content = {
|
||||
|
@ -308,7 +307,7 @@ class Room {
|
|||
Future<String> sendAudioEvent(MatrixFile file,
|
||||
{String txid = null, int width, int height}) async {
|
||||
String fileName = file.path.split("/").last;
|
||||
final String uploadResp = await client.connection.upload(file);
|
||||
final String uploadResp = await client.upload(file);
|
||||
Map<String, dynamic> content = {
|
||||
"msgtype": "m.audio",
|
||||
"body": fileName,
|
||||
|
@ -325,7 +324,7 @@ class Room {
|
|||
Future<String> sendImageEvent(MatrixFile file,
|
||||
{String txid = null, int width, int height}) async {
|
||||
String fileName = file.path.split("/").last;
|
||||
final String uploadResp = await client.connection.upload(file);
|
||||
final String uploadResp = await client.upload(file);
|
||||
Map<String, dynamic> content = {
|
||||
"msgtype": "m.image",
|
||||
"body": fileName,
|
||||
|
@ -349,7 +348,7 @@ class Room {
|
|||
int thumbnailWidth,
|
||||
int thumbnailHeight}) async {
|
||||
String fileName = file.path.split("/").last;
|
||||
final String uploadResp = await client.connection.upload(file);
|
||||
final String uploadResp = await client.upload(file);
|
||||
Map<String, dynamic> content = {
|
||||
"msgtype": "m.video",
|
||||
"body": fileName,
|
||||
|
@ -370,7 +369,7 @@ class Room {
|
|||
}
|
||||
if (thumbnail != null) {
|
||||
String thumbnailName = file.path.split("/").last;
|
||||
final String thumbnailUploadResp = await client.connection.upload(file);
|
||||
final String thumbnailUploadResp = await client.upload(file);
|
||||
content["info"]["thumbnail_url"] = thumbnailUploadResp;
|
||||
content["info"]["thumbnail_info"] = {
|
||||
"size": thumbnail.size,
|
||||
|
@ -408,7 +407,7 @@ class Room {
|
|||
"origin_server_ts": now,
|
||||
"content": content
|
||||
});
|
||||
client.connection.onEvent.add(eventUpdate);
|
||||
client.onEvent.add(eventUpdate);
|
||||
await client.store?.transaction(() {
|
||||
client.store.storeEventUpdate(eventUpdate);
|
||||
return;
|
||||
|
@ -420,7 +419,7 @@ class Room {
|
|||
eventUpdate.content["status"] = 1;
|
||||
eventUpdate.content["unsigned"] = {"transaction_id": messageID};
|
||||
eventUpdate.content["event_id"] = res;
|
||||
client.connection.onEvent.add(eventUpdate);
|
||||
client.onEvent.add(eventUpdate);
|
||||
await client.store?.transaction(() {
|
||||
client.store.storeEventUpdate(eventUpdate);
|
||||
return;
|
||||
|
@ -430,7 +429,7 @@ class Room {
|
|||
// On error, set status to -1
|
||||
eventUpdate.content["status"] = -1;
|
||||
eventUpdate.content["unsigned"] = {"transaction_id": messageID};
|
||||
client.connection.onEvent.add(eventUpdate);
|
||||
client.onEvent.add(eventUpdate);
|
||||
await client.store?.transaction(() {
|
||||
client.store.storeEventUpdate(eventUpdate);
|
||||
return;
|
||||
|
@ -444,7 +443,7 @@ class Room {
|
|||
/// automatically be set.
|
||||
Future<void> join() async {
|
||||
try {
|
||||
await client.connection.jsonRequest(
|
||||
await client.jsonRequest(
|
||||
type: HTTPType.POST, action: "/client/r0/rooms/${id}/join");
|
||||
if (states.containsKey(client.userID) &&
|
||||
states[client.userID].content["is_direct"] is bool &&
|
||||
|
@ -453,7 +452,7 @@ class Room {
|
|||
} on MatrixException catch (exception) {
|
||||
if (exception.errorMessage == "No known servers") {
|
||||
client.store?.forgetRoom(id);
|
||||
client.connection.onRoomUpdate.add(
|
||||
client.onRoomUpdate.add(
|
||||
RoomUpdate(
|
||||
id: id,
|
||||
membership: Membership.leave,
|
||||
|
@ -469,7 +468,7 @@ class Room {
|
|||
/// chat, this will be removed too.
|
||||
Future<void> leave() async {
|
||||
if (directChatMatrixID != "") await removeFromDirectChat();
|
||||
await client.connection.jsonRequest(
|
||||
await client.jsonRequest(
|
||||
type: HTTPType.POST, action: "/client/r0/rooms/${id}/leave");
|
||||
return;
|
||||
}
|
||||
|
@ -477,14 +476,14 @@ class Room {
|
|||
/// Call the Matrix API to forget this room if you already left it.
|
||||
Future<void> forget() async {
|
||||
client.store.forgetRoom(id);
|
||||
await client.connection.jsonRequest(
|
||||
await client.jsonRequest(
|
||||
type: HTTPType.POST, action: "/client/r0/rooms/${id}/forget");
|
||||
return;
|
||||
}
|
||||
|
||||
/// Call the Matrix API to kick a user from this room.
|
||||
Future<void> kick(String userID) async {
|
||||
await client.connection.jsonRequest(
|
||||
await client.jsonRequest(
|
||||
type: HTTPType.POST,
|
||||
action: "/client/r0/rooms/${id}/kick",
|
||||
data: {"user_id": userID});
|
||||
|
@ -493,7 +492,7 @@ class Room {
|
|||
|
||||
/// Call the Matrix API to ban a user from this room.
|
||||
Future<void> ban(String userID) async {
|
||||
await client.connection.jsonRequest(
|
||||
await client.jsonRequest(
|
||||
type: HTTPType.POST,
|
||||
action: "/client/r0/rooms/${id}/ban",
|
||||
data: {"user_id": userID});
|
||||
|
@ -502,7 +501,7 @@ class Room {
|
|||
|
||||
/// Call the Matrix API to unban a banned user from this room.
|
||||
Future<void> unban(String userID) async {
|
||||
await client.connection.jsonRequest(
|
||||
await client.jsonRequest(
|
||||
type: HTTPType.POST,
|
||||
action: "/client/r0/rooms/${id}/unban",
|
||||
data: {"user_id": userID});
|
||||
|
@ -519,7 +518,7 @@ class Room {
|
|||
if (powerMap["users"] == null) powerMap["users"] = {};
|
||||
powerMap["users"][userID] = power;
|
||||
|
||||
final Map<String, dynamic> resp = await client.connection.jsonRequest(
|
||||
final Map<String, dynamic> resp = await client.jsonRequest(
|
||||
type: HTTPType.PUT,
|
||||
action: "/client/r0/rooms/$id/state/m.room.power_levels",
|
||||
data: powerMap);
|
||||
|
@ -528,7 +527,7 @@ class Room {
|
|||
|
||||
/// Call the Matrix API to invite a user to this room.
|
||||
Future<void> invite(String userID) async {
|
||||
await client.connection.jsonRequest(
|
||||
await client.jsonRequest(
|
||||
type: HTTPType.POST,
|
||||
action: "/client/r0/rooms/${id}/invite",
|
||||
data: {"user_id": userID});
|
||||
|
@ -540,10 +539,10 @@ class Room {
|
|||
/// the historical events will be published in the onEvent stream.
|
||||
Future<void> requestHistory(
|
||||
{int historyCount = DefaultHistoryCount, onHistoryReceived}) async {
|
||||
final dynamic resp = await client.connection.jsonRequest(
|
||||
final dynamic resp = await client.jsonRequest(
|
||||
type: HTTPType.GET,
|
||||
action:
|
||||
"/client/r0/rooms/$id/messages?from=${prev_batch}&dir=b&limit=$historyCount&filter=${Connection.syncFilters}");
|
||||
"/client/r0/rooms/$id/messages?from=${prev_batch}&dir=b&limit=$historyCount&filter=${Client.syncFilters}");
|
||||
|
||||
if (onHistoryReceived != null) onHistoryReceived();
|
||||
prev_batch = resp["end"];
|
||||
|
@ -562,7 +561,7 @@ class Room {
|
|||
eventType: resp["state"][i]["type"],
|
||||
content: resp["state"][i],
|
||||
);
|
||||
client.connection.onEvent.add(eventUpdate);
|
||||
client.onEvent.add(eventUpdate);
|
||||
client.store.storeEventUpdate(eventUpdate);
|
||||
}
|
||||
return;
|
||||
|
@ -575,7 +574,7 @@ class Room {
|
|||
eventType: resp["state"][i]["type"],
|
||||
content: resp["state"][i],
|
||||
);
|
||||
client.connection.onEvent.add(eventUpdate);
|
||||
client.onEvent.add(eventUpdate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -589,7 +588,7 @@ class Room {
|
|||
eventType: history[i]["type"],
|
||||
content: history[i],
|
||||
);
|
||||
client.connection.onEvent.add(eventUpdate);
|
||||
client.onEvent.add(eventUpdate);
|
||||
client.store.storeEventUpdate(eventUpdate);
|
||||
client.store.txn.rawUpdate(
|
||||
"UPDATE Rooms SET prev_batch=? WHERE room_id=?", [resp["end"], id]);
|
||||
|
@ -604,10 +603,10 @@ class Room {
|
|||
eventType: history[i]["type"],
|
||||
content: history[i],
|
||||
);
|
||||
client.connection.onEvent.add(eventUpdate);
|
||||
client.onEvent.add(eventUpdate);
|
||||
}
|
||||
}
|
||||
client.connection.onRoomUpdate.add(
|
||||
client.onRoomUpdate.add(
|
||||
RoomUpdate(
|
||||
id: id,
|
||||
membership: membership,
|
||||
|
@ -628,7 +627,7 @@ class Room {
|
|||
else
|
||||
directChats[userID] = [id];
|
||||
|
||||
await client.connection.jsonRequest(
|
||||
await client.jsonRequest(
|
||||
type: HTTPType.PUT,
|
||||
action: "/client/r0/user/${client.userID}/account_data/m.direct",
|
||||
data: directChats);
|
||||
|
@ -644,7 +643,7 @@ class Room {
|
|||
else
|
||||
return; // Nothing to do here
|
||||
|
||||
await client.connection.jsonRequest(
|
||||
await client.jsonRequest(
|
||||
type: HTTPType.PUT,
|
||||
action: "/client/r0/user/${client.userID}/account_data/m.direct",
|
||||
data: directChats);
|
||||
|
@ -655,7 +654,7 @@ class Room {
|
|||
Future<void> sendReadReceipt(String eventID) async {
|
||||
this.notificationCount = 0;
|
||||
client?.store?.resetNotificationCount(this.id);
|
||||
client.connection.jsonRequest(
|
||||
client.jsonRequest(
|
||||
type: HTTPType.POST,
|
||||
action: "/client/r0/rooms/$id/read_markers",
|
||||
data: {
|
||||
|
@ -689,7 +688,7 @@ class Room {
|
|||
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);
|
||||
Event newState = Event.fromJson(rawStates[i], newRoom);
|
||||
newRoom.setState(newState);
|
||||
}
|
||||
}
|
||||
|
@ -740,7 +739,7 @@ class Room {
|
|||
List<User> userList = [];
|
||||
if (states["m.room.member"] is Map<String, dynamic>) {
|
||||
for (var entry in states["m.room.member"].entries) {
|
||||
RoomState state = entry.value;
|
||||
Event state = entry.value;
|
||||
if (state.type == EventTypes.RoomMember) userList.add(state.asUser);
|
||||
}
|
||||
}
|
||||
|
@ -752,11 +751,11 @@ class Room {
|
|||
Future<List<User>> requestParticipants() async {
|
||||
List<User> participants = [];
|
||||
|
||||
dynamic res = await client.connection.jsonRequest(
|
||||
dynamic res = await client.jsonRequest(
|
||||
type: HTTPType.GET, action: "/client/r0/rooms/${id}/members");
|
||||
|
||||
for (num i = 0; i < res["chunk"].length; i++) {
|
||||
User newUser = RoomState.fromJson(res["chunk"][i], this).asUser;
|
||||
User newUser = Event.fromJson(res["chunk"][i], this).asUser;
|
||||
if (newUser.membership != Membership.leave) participants.add(newUser);
|
||||
}
|
||||
|
||||
|
@ -791,7 +790,7 @@ class Room {
|
|||
if (mxID == null || !_requestingMatrixIds.add(mxID)) return null;
|
||||
Map<String, dynamic> resp;
|
||||
try {
|
||||
resp = await client.connection.jsonRequest(
|
||||
resp = await client.jsonRequest(
|
||||
type: HTTPType.GET,
|
||||
action: "/client/r0/rooms/$id/state/m.room.member/$mxID");
|
||||
} catch (exception) {
|
||||
|
@ -821,7 +820,7 @@ class Room {
|
|||
|
||||
/// Searches for the event on the server. Returns null if not found.
|
||||
Future<Event> getEventById(String eventID) async {
|
||||
final dynamic resp = await client.connection.jsonRequest(
|
||||
final dynamic resp = await client.jsonRequest(
|
||||
type: HTTPType.GET, action: "/client/r0/rooms/$id/event/$eventID");
|
||||
return Event.fromJson(resp, this);
|
||||
}
|
||||
|
@ -829,7 +828,7 @@ class Room {
|
|||
/// Returns the power level of the given user ID.
|
||||
int getPowerLevelByUserId(String userId) {
|
||||
int powerLevel = 0;
|
||||
RoomState powerLevelState = states["m.room.power_levels"];
|
||||
Event powerLevelState = states["m.room.power_levels"];
|
||||
if (powerLevelState == null) return powerLevel;
|
||||
if (powerLevelState.content["users_default"] is int)
|
||||
powerLevel = powerLevelState.content["users_default"];
|
||||
|
@ -844,7 +843,7 @@ class Room {
|
|||
|
||||
/// 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"];
|
||||
Event powerLevelState = states["m.room.power_levels"];
|
||||
if (powerLevelState.content["users"] is Map<String, int>)
|
||||
return powerLevelState.content["users"];
|
||||
return null;
|
||||
|
@ -853,12 +852,11 @@ class Room {
|
|||
/// Uploads a new user avatar for this room. Returns the event ID of the new
|
||||
/// m.room.avatar event.
|
||||
Future<String> setAvatar(MatrixFile file) async {
|
||||
final String uploadResp = await client.connection.upload(file);
|
||||
final Map<String, dynamic> setAvatarResp = await client.connection
|
||||
.jsonRequest(
|
||||
type: HTTPType.PUT,
|
||||
action: "/client/r0/rooms/$id/state/m.room.avatar/",
|
||||
data: {"url": uploadResp});
|
||||
final String uploadResp = await client.upload(file);
|
||||
final Map<String, dynamic> setAvatarResp = await client.jsonRequest(
|
||||
type: HTTPType.PUT,
|
||||
action: "/client/r0/rooms/$id/state/m.room.avatar/",
|
||||
data: {"url": uploadResp});
|
||||
return setAvatarResp["event_id"];
|
||||
}
|
||||
|
||||
|
@ -946,12 +944,12 @@ class Room {
|
|||
// All push notifications should be sent to the user
|
||||
case PushRuleState.notify:
|
||||
if (pushRuleState == PushRuleState.dont_notify)
|
||||
resp = await client.connection.jsonRequest(
|
||||
resp = await client.jsonRequest(
|
||||
type: HTTPType.DELETE,
|
||||
action: "/client/r0/pushrules/global/override/$id",
|
||||
data: {});
|
||||
else if (pushRuleState == PushRuleState.mentions_only)
|
||||
resp = await client.connection.jsonRequest(
|
||||
resp = await client.jsonRequest(
|
||||
type: HTTPType.DELETE,
|
||||
action: "/client/r0/pushrules/global/room/$id",
|
||||
data: {});
|
||||
|
@ -959,18 +957,18 @@ class Room {
|
|||
// Only when someone mentions the user, a push notification should be sent
|
||||
case PushRuleState.mentions_only:
|
||||
if (pushRuleState == PushRuleState.dont_notify) {
|
||||
resp = await client.connection.jsonRequest(
|
||||
resp = await client.jsonRequest(
|
||||
type: HTTPType.DELETE,
|
||||
action: "/client/r0/pushrules/global/override/$id",
|
||||
data: {});
|
||||
resp = await client.connection.jsonRequest(
|
||||
resp = await client.jsonRequest(
|
||||
type: HTTPType.PUT,
|
||||
action: "/client/r0/pushrules/global/room/$id",
|
||||
data: {
|
||||
"actions": ["dont_notify"]
|
||||
});
|
||||
} else if (pushRuleState == PushRuleState.notify)
|
||||
resp = await client.connection.jsonRequest(
|
||||
resp = await client.jsonRequest(
|
||||
type: HTTPType.PUT,
|
||||
action: "/client/r0/pushrules/global/room/$id",
|
||||
data: {
|
||||
|
@ -980,12 +978,12 @@ class Room {
|
|||
// No push notification should be ever sent for this room.
|
||||
case PushRuleState.dont_notify:
|
||||
if (pushRuleState == PushRuleState.mentions_only) {
|
||||
resp = await client.connection.jsonRequest(
|
||||
resp = await client.jsonRequest(
|
||||
type: HTTPType.DELETE,
|
||||
action: "/client/r0/pushrules/global/room/$id",
|
||||
data: {});
|
||||
}
|
||||
resp = await client.connection.jsonRequest(
|
||||
resp = await client.jsonRequest(
|
||||
type: HTTPType.PUT,
|
||||
action: "/client/r0/pushrules/global/override/$id",
|
||||
data: {
|
||||
|
@ -1010,7 +1008,7 @@ class Room {
|
|||
messageID = txid;
|
||||
Map<String, dynamic> data = {};
|
||||
if (reason != null) data["reason"] = reason;
|
||||
final dynamic resp = await client.connection.jsonRequest(
|
||||
final dynamic resp = await client.jsonRequest(
|
||||
type: HTTPType.PUT,
|
||||
action: "/client/r0/rooms/$id/redact/$eventId/$messageID",
|
||||
data: data);
|
||||
|
@ -1022,7 +1020,7 @@ class Room {
|
|||
"typing": isTyping,
|
||||
};
|
||||
if (timeout != null) data["timeout"] = timeout;
|
||||
return client.connection.jsonRequest(
|
||||
return client.jsonRequest(
|
||||
type: HTTPType.PUT,
|
||||
action: "/client/r0/rooms/${this.id}/typing/${client.userID}",
|
||||
data: data,
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
|
||||
import 'package:famedlysdk/famedlysdk.dart';
|
||||
import 'package:famedlysdk/src/AccountData.dart';
|
||||
import 'package:famedlysdk/src/RoomState.dart';
|
||||
import 'package:famedlysdk/src/Event.dart';
|
||||
|
||||
/// Stripped down events for account data and ephemrals of a room.
|
||||
class RoomAccountData extends AccountData {
|
||||
|
@ -40,7 +40,7 @@ class RoomAccountData extends AccountData {
|
|||
factory RoomAccountData.fromJson(
|
||||
Map<String, dynamic> jsonPayload, Room room) {
|
||||
final Map<String, dynamic> content =
|
||||
RoomState.getMapFromPayload(jsonPayload['content']);
|
||||
Event.getMapFromPayload(jsonPayload['content']);
|
||||
return RoomAccountData(
|
||||
content: content,
|
||||
typeKey: jsonPayload['type'],
|
||||
|
|
|
@ -1,226 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Zender & Kurtz GbR.
|
||||
*
|
||||
* Authors:
|
||||
* Christian Pauly <krille@famedly.com>
|
||||
* Marcel Radzio <mtrnord@famedly.com>
|
||||
*
|
||||
* This file is part of famedlysdk.
|
||||
*
|
||||
* famedlysdk is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* famedlysdk is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with famedlysdk. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:core';
|
||||
|
||||
import 'package:famedlysdk/famedlysdk.dart';
|
||||
import 'package:famedlysdk/src/RoomState.dart';
|
||||
|
||||
import 'Client.dart';
|
||||
import 'Room.dart';
|
||||
import 'User.dart';
|
||||
import 'sync/EventUpdate.dart';
|
||||
import 'sync/RoomUpdate.dart';
|
||||
|
||||
typedef onRoomListUpdateCallback = void Function();
|
||||
typedef onRoomListInsertCallback = void Function(int insertID);
|
||||
typedef onRoomListRemoveCallback = void Function(int insertID);
|
||||
|
||||
/// Represents a list of rooms for this client, which will automatically update
|
||||
/// itself and call the [onUpdate], [onInsert] and [onDelete] callbacks. To get
|
||||
/// the initial room list, use the store or create a RoomList instance by using
|
||||
/// [client.getRoomList].
|
||||
class RoomList {
|
||||
final Client client;
|
||||
List<Room> rooms = [];
|
||||
|
||||
final bool onlyLeft;
|
||||
|
||||
/// Will be called, when the room list has changed. Can be used e.g. to update
|
||||
/// the state of a StatefulWidget.
|
||||
final onRoomListUpdateCallback onUpdate;
|
||||
|
||||
/// Will be called, when a new room is added to the list.
|
||||
final onRoomListInsertCallback onInsert;
|
||||
|
||||
/// Will be called, when a room has been removed from the list.
|
||||
final onRoomListRemoveCallback onRemove;
|
||||
|
||||
StreamSubscription<EventUpdate> eventSub;
|
||||
StreamSubscription<RoomUpdate> roomSub;
|
||||
StreamSubscription<bool> firstSyncSub;
|
||||
|
||||
RoomList(
|
||||
{this.client,
|
||||
this.rooms,
|
||||
this.onUpdate,
|
||||
this.onInsert,
|
||||
this.onRemove,
|
||||
this.onlyLeft = false}) {
|
||||
eventSub ??= client.connection.onEvent.stream.listen(_handleEventUpdate);
|
||||
roomSub ??= client.connection.onRoomUpdate.stream.listen(_handleRoomUpdate);
|
||||
firstSyncSub ??=
|
||||
client.connection.onFirstSync.stream.listen((b) => sortAndUpdate());
|
||||
sort();
|
||||
}
|
||||
|
||||
RoomList copyWith({
|
||||
Client client,
|
||||
List<Room> rooms,
|
||||
onRoomListUpdateCallback onUpdate,
|
||||
onRoomListInsertCallback onInsert,
|
||||
onRoomListRemoveCallback onRemove,
|
||||
bool onlyLeft,
|
||||
}) {
|
||||
return RoomList(
|
||||
client: client ?? this.client,
|
||||
rooms: rooms ?? this.rooms,
|
||||
onUpdate: onUpdate ?? this.onUpdate,
|
||||
onInsert: onInsert ?? this.onInsert,
|
||||
onRemove: onRemove ?? this.onRemove,
|
||||
onlyLeft: onlyLeft ?? this.onlyLeft,
|
||||
);
|
||||
}
|
||||
|
||||
Room getRoomByAlias(String alias) {
|
||||
for (int i = 0; i < rooms.length; i++) {
|
||||
if (rooms[i].canonicalAlias == alias) return rooms[i];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Room getRoomById(String id) {
|
||||
for (int j = 0; j < rooms.length; j++) {
|
||||
if (rooms[j].id == id) return rooms[j];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
void _handleRoomUpdate(RoomUpdate chatUpdate) {
|
||||
// Update the chat list item.
|
||||
// Search the room in the rooms
|
||||
num j = 0;
|
||||
for (j = 0; j < rooms.length; j++) {
|
||||
if (rooms[j].id == chatUpdate.id) break;
|
||||
}
|
||||
final bool found = (j < rooms.length && rooms[j].id == chatUpdate.id);
|
||||
final bool isLeftRoom = chatUpdate.membership == Membership.leave;
|
||||
|
||||
// Does the chat already exist in the list rooms?
|
||||
if (!found && ((!onlyLeft && !isLeftRoom) || (onlyLeft && isLeftRoom))) {
|
||||
num position = chatUpdate.membership == Membership.invite ? 0 : j;
|
||||
// Add the new chat to the list
|
||||
Room newRoom = Room(
|
||||
id: chatUpdate.id,
|
||||
membership: chatUpdate.membership,
|
||||
prev_batch: chatUpdate.prev_batch,
|
||||
highlightCount: chatUpdate.highlight_count,
|
||||
notificationCount: chatUpdate.notification_count,
|
||||
mHeroes: chatUpdate.summary?.mHeroes,
|
||||
mJoinedMemberCount: chatUpdate.summary?.mJoinedMemberCount,
|
||||
mInvitedMemberCount: chatUpdate.summary?.mInvitedMemberCount,
|
||||
roomAccountData: {},
|
||||
client: client,
|
||||
);
|
||||
rooms.insert(position, newRoom);
|
||||
if (onInsert != null) onInsert(position);
|
||||
}
|
||||
// If the membership is "leave" or not "leave" but onlyLeft=true then remove the item and stop here
|
||||
else if (found &&
|
||||
((!onlyLeft && isLeftRoom) || (onlyLeft && !isLeftRoom))) {
|
||||
rooms.removeAt(j);
|
||||
if (onRemove != null) onRemove(j);
|
||||
}
|
||||
// Update notification, highlight count and/or additional informations
|
||||
else if (found &&
|
||||
chatUpdate.membership != Membership.leave &&
|
||||
(rooms[j].membership != chatUpdate.membership ||
|
||||
rooms[j].notificationCount != chatUpdate.notification_count ||
|
||||
rooms[j].highlightCount != chatUpdate.highlight_count ||
|
||||
chatUpdate.summary != null)) {
|
||||
rooms[j].membership = chatUpdate.membership;
|
||||
rooms[j].notificationCount = chatUpdate.notification_count;
|
||||
rooms[j].highlightCount = chatUpdate.highlight_count;
|
||||
if (chatUpdate.prev_batch != null)
|
||||
rooms[j].prev_batch = chatUpdate.prev_batch;
|
||||
if (chatUpdate.summary != null) {
|
||||
if (chatUpdate.summary.mHeroes != null)
|
||||
rooms[j].mHeroes = chatUpdate.summary.mHeroes;
|
||||
if (chatUpdate.summary.mJoinedMemberCount != null)
|
||||
rooms[j].mJoinedMemberCount = chatUpdate.summary.mJoinedMemberCount;
|
||||
if (chatUpdate.summary.mInvitedMemberCount != null)
|
||||
rooms[j].mInvitedMemberCount = chatUpdate.summary.mInvitedMemberCount;
|
||||
}
|
||||
if (rooms[j].onUpdate != null) rooms[j].onUpdate();
|
||||
}
|
||||
sortAndUpdate();
|
||||
}
|
||||
|
||||
void _handleEventUpdate(EventUpdate eventUpdate) {
|
||||
if (eventUpdate.type == "history") return;
|
||||
// Search the room in the rooms
|
||||
num j = 0;
|
||||
for (j = 0; j < rooms.length; j++) {
|
||||
if (rooms[j].id == eventUpdate.roomID) break;
|
||||
}
|
||||
final bool found = (j < rooms.length && rooms[j].id == eventUpdate.roomID);
|
||||
if (!found) return;
|
||||
if (eventUpdate.type == "timeline" ||
|
||||
eventUpdate.type == "state" ||
|
||||
eventUpdate.type == "invite_state") {
|
||||
RoomState stateEvent = RoomState.fromJson(eventUpdate.content, rooms[j]);
|
||||
if (stateEvent.type == EventTypes.Redaction) {
|
||||
final String redacts = eventUpdate.content["redacts"];
|
||||
rooms[j].states.states.forEach(
|
||||
(String key, Map<String, RoomState> states) => states.forEach(
|
||||
(String key, RoomState state) {
|
||||
if (state.eventId == redacts) {
|
||||
state.setRedactionEvent(stateEvent);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
} else {
|
||||
RoomState prevState =
|
||||
rooms[j].getState(stateEvent.typeKey, stateEvent.stateKey);
|
||||
if (prevState != null && prevState.time > stateEvent.time) return;
|
||||
rooms[j].setState(stateEvent);
|
||||
}
|
||||
} else if (eventUpdate.type == "account_data") {
|
||||
rooms[j].roomAccountData[eventUpdate.eventType] =
|
||||
RoomAccountData.fromJson(eventUpdate.content, rooms[j]);
|
||||
} else if (eventUpdate.type == "ephemeral") {
|
||||
rooms[j].ephemerals[eventUpdate.eventType] =
|
||||
RoomAccountData.fromJson(eventUpdate.content, rooms[j]);
|
||||
}
|
||||
if (rooms[j].onUpdate != null) rooms[j].onUpdate();
|
||||
if (eventUpdate.type == "timeline") sortAndUpdate();
|
||||
}
|
||||
|
||||
bool sortLock = false;
|
||||
|
||||
sort() {
|
||||
if (sortLock || rooms.length < 2) return;
|
||||
sortLock = true;
|
||||
rooms?.sort((a, b) =>
|
||||
b.timeCreated.toTimeStamp().compareTo(a.timeCreated.toTimeStamp()));
|
||||
sortLock = false;
|
||||
}
|
||||
|
||||
sortAndUpdate() {
|
||||
if (client.prevBatch == null) return;
|
||||
sort();
|
||||
if (onUpdate != null) onUpdate();
|
||||
}
|
||||
}
|
|
@ -1,291 +0,0 @@
|
|||
/*
|
||||
* 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.
|
||||
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.getUserByMXIDSync(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.
|
||||
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.
|
||||
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;
|
||||
|
||||
/// Optional. The event that redacted this event, if any. Otherwise null.
|
||||
RoomState get redactedBecause =>
|
||||
unsigned != null && unsigned.containsKey("redacted_because")
|
||||
? RoomState.fromJson(unsigned["redacted_because"], room)
|
||||
: null;
|
||||
|
||||
bool get redacted => redactedBecause != null;
|
||||
|
||||
User get stateKeyUser => room.getUserByMXIDSync(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,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = new Map<String, dynamic>();
|
||||
if (this.stateKey != null) data['state_key'] = this.stateKey;
|
||||
if (this.prevContent != null && this.prevContent.isNotEmpty)
|
||||
data['prev_content'] = this.prevContent;
|
||||
data['content'] = this.content;
|
||||
data['type'] = this.typeKey;
|
||||
data['event_id'] = this.eventId;
|
||||
data['room_id'] = this.roomId;
|
||||
data['sender'] = this.senderId;
|
||||
data['origin_server_ts'] = this.time.toTimeStamp();
|
||||
if (this.unsigned != null && this.unsigned.isNotEmpty)
|
||||
data['unsigned'] = this.unsigned;
|
||||
return data;
|
||||
}
|
||||
|
||||
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.
|
||||
@deprecated
|
||||
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.redaction":
|
||||
return EventTypes.Redaction;
|
||||
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;
|
||||
}
|
||||
|
||||
void setRedactionEvent(RoomState redactedBecause) {
|
||||
unsigned = {
|
||||
"redacted_because": redactedBecause.toJson(),
|
||||
};
|
||||
prevContent = null;
|
||||
List<String> contentKeyWhiteList = [];
|
||||
switch (type) {
|
||||
case EventTypes.RoomMember:
|
||||
contentKeyWhiteList.add("membership");
|
||||
break;
|
||||
case EventTypes.RoomMember:
|
||||
contentKeyWhiteList.add("membership");
|
||||
break;
|
||||
case EventTypes.RoomCreate:
|
||||
contentKeyWhiteList.add("creator");
|
||||
break;
|
||||
case EventTypes.RoomJoinRules:
|
||||
contentKeyWhiteList.add("join_rule");
|
||||
break;
|
||||
case EventTypes.RoomPowerLevels:
|
||||
contentKeyWhiteList.add("ban");
|
||||
contentKeyWhiteList.add("events");
|
||||
contentKeyWhiteList.add("events_default");
|
||||
contentKeyWhiteList.add("kick");
|
||||
contentKeyWhiteList.add("redact");
|
||||
contentKeyWhiteList.add("state_default");
|
||||
contentKeyWhiteList.add("users");
|
||||
contentKeyWhiteList.add("users_default");
|
||||
break;
|
||||
case EventTypes.RoomAliases:
|
||||
contentKeyWhiteList.add("aliases");
|
||||
break;
|
||||
case EventTypes.HistoryVisibility:
|
||||
contentKeyWhiteList.add("history_visibility");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
List<String> toRemoveList = [];
|
||||
for (var entry in content.entries) {
|
||||
if (contentKeyWhiteList.indexOf(entry.key) == -1) {
|
||||
toRemoveList.add(entry.key);
|
||||
}
|
||||
}
|
||||
toRemoveList.forEach((s) => content.remove(s));
|
||||
}
|
||||
}
|
||||
|
||||
enum EventTypes {
|
||||
Text,
|
||||
Emote,
|
||||
Notice,
|
||||
Image,
|
||||
Video,
|
||||
Audio,
|
||||
Redaction,
|
||||
File,
|
||||
Location,
|
||||
Reply,
|
||||
RoomAliases,
|
||||
RoomCanonicalAlias,
|
||||
RoomCreate,
|
||||
RoomJoinRules,
|
||||
RoomMember,
|
||||
RoomPowerLevels,
|
||||
RoomName,
|
||||
RoomTopic,
|
||||
RoomAvatar,
|
||||
GuestAccess,
|
||||
HistoryVisibility,
|
||||
Unknown,
|
||||
}
|
|
@ -75,7 +75,7 @@ class Timeline {
|
|||
}
|
||||
|
||||
Timeline({this.room, this.events, this.onUpdate, this.onInsert}) {
|
||||
sub ??= room.client.connection.onEvent.stream.listen(_handleEventUpdate);
|
||||
sub ??= room.client.onEvent.stream.listen(_handleEventUpdate);
|
||||
}
|
||||
|
||||
int _findEvent({String event_id, String unsigned_txid}) {
|
||||
|
@ -148,8 +148,8 @@ class Timeline {
|
|||
sort() {
|
||||
if (sortLock || events.length < 2) return;
|
||||
sortLock = true;
|
||||
events
|
||||
?.sort((a, b) => b.time.toTimeStamp().compareTo(a.time.toTimeStamp()));
|
||||
events?.sort((a, b) =>
|
||||
b.time.millisecondsSinceEpoch.compareTo(a.time.millisecondsSinceEpoch));
|
||||
sortLock = false;
|
||||
}
|
||||
|
||||
|
|
|
@ -23,16 +23,13 @@
|
|||
|
||||
import 'package:famedlysdk/famedlysdk.dart';
|
||||
import 'package:famedlysdk/src/Room.dart';
|
||||
import 'package:famedlysdk/src/RoomState.dart';
|
||||
import 'package:famedlysdk/src/utils/ChatTime.dart';
|
||||
import 'package:famedlysdk/src/Event.dart';
|
||||
import 'package:famedlysdk/src/utils/MxContent.dart';
|
||||
|
||||
import 'Connection.dart';
|
||||
|
||||
enum Membership { join, invite, leave, ban }
|
||||
|
||||
/// Represents a Matrix User which may be a participant in a Matrix Room.
|
||||
class User extends RoomState {
|
||||
class User extends Event {
|
||||
factory User(
|
||||
String id, {
|
||||
String membership,
|
||||
|
@ -50,7 +47,7 @@ class User extends RoomState {
|
|||
typeKey: "m.room.member",
|
||||
roomId: room?.id,
|
||||
room: room,
|
||||
time: ChatTime.now(),
|
||||
time: DateTime.now(),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -62,7 +59,7 @@ class User extends RoomState {
|
|||
String eventId,
|
||||
String roomId,
|
||||
String senderId,
|
||||
ChatTime time,
|
||||
DateTime time,
|
||||
dynamic unsigned,
|
||||
Room room})
|
||||
: super(
|
||||
|
@ -131,7 +128,7 @@ class User extends RoomState {
|
|||
if (roomID != null) return roomID;
|
||||
|
||||
// Start a new direct chat
|
||||
final dynamic resp = await room.client.connection.jsonRequest(
|
||||
final dynamic resp = await room.client.jsonRequest(
|
||||
type: HTTPType.POST,
|
||||
action: "/client/r0/createRoom",
|
||||
data: {
|
||||
|
|
|
@ -1,81 +0,0 @@
|
|||
/*
|
||||
* 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 famedly. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
part 'SetPushersRequest.g.dart';
|
||||
|
||||
@JsonSerializable(explicitToJson: true, nullable: false, includeIfNull: false)
|
||||
class SetPushersRequest {
|
||||
// Required Keys
|
||||
@JsonKey(nullable: false)
|
||||
String lang;
|
||||
@JsonKey(nullable: false)
|
||||
String device_display_name;
|
||||
@JsonKey(nullable: false)
|
||||
String app_display_name;
|
||||
@JsonKey(nullable: false)
|
||||
String app_id;
|
||||
@JsonKey(nullable: false)
|
||||
String kind;
|
||||
@JsonKey(nullable: false)
|
||||
String pushkey;
|
||||
@JsonKey(nullable: false)
|
||||
PusherData data;
|
||||
|
||||
// Optional keys
|
||||
String profile_tag;
|
||||
bool append;
|
||||
|
||||
SetPushersRequest({
|
||||
this.lang,
|
||||
this.device_display_name,
|
||||
this.app_display_name,
|
||||
this.app_id,
|
||||
this.kind,
|
||||
this.pushkey,
|
||||
this.data,
|
||||
this.profile_tag,
|
||||
this.append,
|
||||
});
|
||||
|
||||
factory SetPushersRequest.fromJson(Map<String, dynamic> json) =>
|
||||
_$SetPushersRequestFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$SetPushersRequestToJson(this);
|
||||
}
|
||||
|
||||
@JsonSerializable(explicitToJson: true, nullable: false, includeIfNull: false)
|
||||
class PusherData {
|
||||
String url;
|
||||
String format;
|
||||
|
||||
PusherData({
|
||||
this.url,
|
||||
this.format,
|
||||
});
|
||||
|
||||
factory PusherData.fromJson(Map<String, dynamic> json) =>
|
||||
_$PusherDataFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$PusherDataToJson(this);
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'SetPushersRequest.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
SetPushersRequest _$SetPushersRequestFromJson(Map<String, dynamic> json) {
|
||||
return SetPushersRequest(
|
||||
lang: json['lang'] as String,
|
||||
device_display_name: json['device_display_name'] as String,
|
||||
app_display_name: json['app_display_name'] as String,
|
||||
app_id: json['app_id'] as String,
|
||||
kind: json['kind'] as String,
|
||||
pushkey: json['pushkey'] as String,
|
||||
data: PusherData.fromJson(json['data'] as Map<String, dynamic>),
|
||||
profile_tag: json['profile_tag'] as String,
|
||||
append: json['append'] as bool);
|
||||
}
|
||||
|
||||
Map<String, dynamic> _$SetPushersRequestToJson(SetPushersRequest instance) =>
|
||||
<String, dynamic>{
|
||||
'lang': instance.lang,
|
||||
'device_display_name': instance.device_display_name,
|
||||
'app_display_name': instance.app_display_name,
|
||||
'app_id': instance.app_id,
|
||||
'kind': instance.kind,
|
||||
'pushkey': instance.pushkey,
|
||||
'data': instance.data.toJson(),
|
||||
'profile_tag': instance.profile_tag,
|
||||
'append': instance.append
|
||||
};
|
||||
|
||||
PusherData _$PusherDataFromJson(Map<String, dynamic> json) {
|
||||
return PusherData(
|
||||
url: json['url'] as String, format: json['format'] as String);
|
||||
}
|
||||
|
||||
Map<String, dynamic> _$PusherDataToJson(PusherData instance) =>
|
||||
<String, dynamic>{'url': instance.url, 'format': instance.format};
|
|
@ -1,111 +0,0 @@
|
|||
/*
|
||||
* 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 famedly. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
part 'PushrulesResponse.g.dart';
|
||||
|
||||
@JsonSerializable(explicitToJson: true, nullable: false)
|
||||
class PushrulesResponse {
|
||||
@JsonKey(nullable: false)
|
||||
Global global;
|
||||
|
||||
PushrulesResponse(
|
||||
this.global,
|
||||
);
|
||||
|
||||
factory PushrulesResponse.fromJson(Map<String, dynamic> json) =>
|
||||
_$PushrulesResponseFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$PushrulesResponseToJson(this);
|
||||
}
|
||||
|
||||
@JsonSerializable(explicitToJson: true)
|
||||
class Global {
|
||||
List<PushRule> content;
|
||||
List<PushRule> room;
|
||||
List<PushRule> sender;
|
||||
List<PushRule> override;
|
||||
List<PushRule> underride;
|
||||
|
||||
Global(
|
||||
this.content,
|
||||
this.room,
|
||||
this.sender,
|
||||
this.override,
|
||||
this.underride,
|
||||
);
|
||||
|
||||
factory Global.fromJson(Map<String, dynamic> json) => _$GlobalFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$GlobalToJson(this);
|
||||
}
|
||||
|
||||
@JsonSerializable(explicitToJson: true)
|
||||
class PushRule {
|
||||
@JsonKey(nullable: false)
|
||||
List<dynamic> actions;
|
||||
List<Condition> conditions;
|
||||
@JsonKey(nullable: false, name: "default")
|
||||
bool contentDefault;
|
||||
@JsonKey(nullable: false)
|
||||
bool enabled;
|
||||
@JsonKey(nullable: false)
|
||||
String ruleId;
|
||||
String pattern;
|
||||
|
||||
PushRule(
|
||||
this.actions,
|
||||
this.conditions,
|
||||
this.contentDefault,
|
||||
this.enabled,
|
||||
this.ruleId,
|
||||
this.pattern,
|
||||
);
|
||||
|
||||
factory PushRule.fromJson(Map<String, dynamic> json) =>
|
||||
_$PushRuleFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$PushRuleToJson(this);
|
||||
}
|
||||
|
||||
@JsonSerializable(explicitToJson: true)
|
||||
class Condition {
|
||||
String key;
|
||||
@JsonKey(name: "is")
|
||||
String conditionIs;
|
||||
@JsonKey(nullable: false)
|
||||
String kind;
|
||||
String pattern;
|
||||
|
||||
Condition(
|
||||
this.key,
|
||||
this.conditionIs,
|
||||
this.kind,
|
||||
this.pattern,
|
||||
);
|
||||
|
||||
factory Condition.fromJson(Map<String, dynamic> json) =>
|
||||
_$ConditionFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$ConditionToJson(this);
|
||||
}
|
|
@ -1,81 +0,0 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'PushrulesResponse.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
PushrulesResponse _$PushrulesResponseFromJson(Map<String, dynamic> json) {
|
||||
return PushrulesResponse(
|
||||
Global.fromJson(json['global'] as Map<String, dynamic>));
|
||||
}
|
||||
|
||||
Map<String, dynamic> _$PushrulesResponseToJson(PushrulesResponse instance) =>
|
||||
<String, dynamic>{'global': instance.global.toJson()};
|
||||
|
||||
Global _$GlobalFromJson(Map<String, dynamic> json) {
|
||||
return Global(
|
||||
(json['content'] as List)
|
||||
?.map((e) =>
|
||||
e == null ? null : PushRule.fromJson(e as Map<String, dynamic>))
|
||||
?.toList(),
|
||||
(json['room'] as List)
|
||||
?.map((e) =>
|
||||
e == null ? null : PushRule.fromJson(e as Map<String, dynamic>))
|
||||
?.toList(),
|
||||
(json['sender'] as List)
|
||||
?.map((e) =>
|
||||
e == null ? null : PushRule.fromJson(e as Map<String, dynamic>))
|
||||
?.toList(),
|
||||
(json['override'] as List)
|
||||
?.map((e) =>
|
||||
e == null ? null : PushRule.fromJson(e as Map<String, dynamic>))
|
||||
?.toList(),
|
||||
(json['underride'] as List)
|
||||
?.map((e) =>
|
||||
e == null ? null : PushRule.fromJson(e as Map<String, dynamic>))
|
||||
?.toList());
|
||||
}
|
||||
|
||||
Map<String, dynamic> _$GlobalToJson(Global instance) => <String, dynamic>{
|
||||
'content': instance.content?.map((e) => e?.toJson())?.toList(),
|
||||
'room': instance.room?.map((e) => e?.toJson())?.toList(),
|
||||
'sender': instance.sender?.map((e) => e?.toJson())?.toList(),
|
||||
'override': instance.override?.map((e) => e?.toJson())?.toList(),
|
||||
'underride': instance.underride?.map((e) => e?.toJson())?.toList()
|
||||
};
|
||||
|
||||
PushRule _$PushRuleFromJson(Map<String, dynamic> json) {
|
||||
return PushRule(
|
||||
json['actions'] as List,
|
||||
(json['conditions'] as List)
|
||||
?.map((e) =>
|
||||
e == null ? null : Condition.fromJson(e as Map<String, dynamic>))
|
||||
?.toList(),
|
||||
json['default'] as bool,
|
||||
json['enabled'] as bool,
|
||||
json['ruleId'] as String,
|
||||
json['pattern'] as String);
|
||||
}
|
||||
|
||||
Map<String, dynamic> _$PushRuleToJson(PushRule instance) => <String, dynamic>{
|
||||
'actions': instance.actions,
|
||||
'conditions': instance.conditions?.map((e) => e?.toJson())?.toList(),
|
||||
'default': instance.contentDefault,
|
||||
'enabled': instance.enabled,
|
||||
'ruleId': instance.ruleId,
|
||||
'pattern': instance.pattern
|
||||
};
|
||||
|
||||
Condition _$ConditionFromJson(Map<String, dynamic> json) {
|
||||
return Condition(json['key'] as String, json['is'] as String,
|
||||
json['kind'] as String, json['pattern'] as String);
|
||||
}
|
||||
|
||||
Map<String, dynamic> _$ConditionToJson(Condition instance) => <String, dynamic>{
|
||||
'key': instance.key,
|
||||
'is': instance.conditionIs,
|
||||
'kind': instance.kind,
|
||||
'pattern': instance.pattern
|
||||
};
|
|
@ -1,139 +0,0 @@
|
|||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
/// Used to localize and present time in a chat application manner.
|
||||
class ChatTime {
|
||||
DateTime dateTime = DateTime.now();
|
||||
|
||||
/// Insert with a timestamp [ts] which represents the milliseconds since
|
||||
/// the Unix epoch.
|
||||
ChatTime(num ts) {
|
||||
if (ts != null) dateTime = DateTime.fromMillisecondsSinceEpoch(ts);
|
||||
}
|
||||
|
||||
/// Returns a ChatTime object which represents the current time.
|
||||
ChatTime.now() {
|
||||
dateTime = DateTime.now();
|
||||
}
|
||||
|
||||
/// Returns [toTimeString()] if the ChatTime is today, the name of the week
|
||||
/// day if the ChatTime is this week and a date string else.
|
||||
String toString() {
|
||||
DateTime now = DateTime.now();
|
||||
|
||||
bool sameYear = now.year == dateTime.year;
|
||||
|
||||
bool sameDay =
|
||||
sameYear && now.month == dateTime.month && now.day == dateTime.day;
|
||||
|
||||
bool sameWeek = sameYear &&
|
||||
!sameDay &&
|
||||
now.millisecondsSinceEpoch - dateTime.millisecondsSinceEpoch <
|
||||
1000 * 60 * 60 * 24 * 7;
|
||||
|
||||
if (sameDay) {
|
||||
return toTimeString();
|
||||
} else if (sameWeek) {
|
||||
switch (dateTime.weekday) {
|
||||
case 1:
|
||||
return "Montag";
|
||||
case 2:
|
||||
return "Dienstag";
|
||||
case 3:
|
||||
return "Mittwoch";
|
||||
case 4:
|
||||
return "Donnerstag";
|
||||
case 5:
|
||||
return "Freitag";
|
||||
case 6:
|
||||
return "Samstag";
|
||||
case 7:
|
||||
return "Sonntag";
|
||||
}
|
||||
} else if (sameYear) {
|
||||
return "${_z(dateTime.day)}.${_z(dateTime.month)}";
|
||||
}
|
||||
return "${_z(dateTime.day)}.${_z(dateTime.month)}.${_z(dateTime.year)}";
|
||||
}
|
||||
|
||||
/// Returns the milliseconds since the Unix epoch.
|
||||
num toTimeStamp() {
|
||||
return dateTime.millisecondsSinceEpoch;
|
||||
}
|
||||
|
||||
operator <(ChatTime other) {
|
||||
return this.toTimeStamp() < other.toTimeStamp();
|
||||
}
|
||||
|
||||
operator >(ChatTime other) {
|
||||
return this.toTimeStamp() > other.toTimeStamp();
|
||||
}
|
||||
|
||||
operator >=(ChatTime other) {
|
||||
return this.toTimeStamp() >= other.toTimeStamp();
|
||||
}
|
||||
|
||||
operator <=(ChatTime other) {
|
||||
return this.toTimeStamp() <= other.toTimeStamp();
|
||||
}
|
||||
|
||||
operator ==(dynamic other) {
|
||||
if (other is ChatTime)
|
||||
return this.toTimeStamp() == other.toTimeStamp();
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Two message events can belong to the same environment. That means that they
|
||||
/// don't need to display the time they were sent because they are close
|
||||
/// enaugh.
|
||||
static final minutesBetweenEnvironments = 5;
|
||||
|
||||
/// Checks if two ChatTimes are close enough to belong to the same
|
||||
/// environment.
|
||||
bool sameEnvironment(ChatTime prevTime) {
|
||||
return toTimeStamp() - prevTime.toTimeStamp() <
|
||||
1000 * 60 * minutesBetweenEnvironments;
|
||||
}
|
||||
|
||||
/// Returns a simple time String.
|
||||
String toTimeString() {
|
||||
return "${_z(dateTime.hour)}:${_z(dateTime.minute)}";
|
||||
}
|
||||
|
||||
/// If the ChatTime is today, this returns [toTimeString()], if not it also
|
||||
/// shows the date.
|
||||
String toEventTimeString() {
|
||||
DateTime now = DateTime.now();
|
||||
|
||||
bool sameYear = now.year == dateTime.year;
|
||||
|
||||
bool sameDay =
|
||||
sameYear && now.month == dateTime.month && now.day == dateTime.day;
|
||||
|
||||
if (sameDay) return toTimeString();
|
||||
return "${toString()}, ${toTimeString()}";
|
||||
}
|
||||
|
||||
static String _z(int i) => i < 10 ? "0${i.toString()}" : i.toString();
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
/// Workaround until [File] in dart:io and dart:html is unified
|
||||
class MatrixFile {
|
||||
List<int> bytes;
|
||||
String path;
|
||||
|
|
|
@ -1,64 +0,0 @@
|
|||
class PushRule {
|
||||
final String ruleId;
|
||||
final bool isDefault;
|
||||
final bool enabled;
|
||||
final List<Conditions> conditions;
|
||||
final List<dynamic> actions;
|
||||
|
||||
PushRule(
|
||||
{this.ruleId,
|
||||
this.isDefault,
|
||||
this.enabled,
|
||||
this.conditions,
|
||||
this.actions});
|
||||
|
||||
PushRule.fromJson(Map<String, dynamic> json)
|
||||
: ruleId = json['rule_id'],
|
||||
isDefault = json['is_default'],
|
||||
enabled = json['enabled'],
|
||||
conditions = _getConditionsFromJson(json['conditions']),
|
||||
actions = json['actions'];
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = new Map<String, dynamic>();
|
||||
data['rule_id'] = this.ruleId;
|
||||
data['is_default'] = this.isDefault;
|
||||
data['enabled'] = this.enabled;
|
||||
if (this.conditions != null) {
|
||||
data['conditions'] = this.conditions.map((v) => v.toJson()).toList();
|
||||
}
|
||||
data['actions'] = this.actions;
|
||||
return data;
|
||||
}
|
||||
|
||||
static List<Conditions> _getConditionsFromJson(List<dynamic> json) {
|
||||
List<Conditions> conditions = [];
|
||||
if (json == null) return conditions;
|
||||
for (int i = 0; i < json.length; i++) {
|
||||
conditions.add(Conditions.fromJson(json[i]));
|
||||
}
|
||||
return conditions;
|
||||
}
|
||||
}
|
||||
|
||||
class Conditions {
|
||||
String key;
|
||||
String kind;
|
||||
String pattern;
|
||||
|
||||
Conditions({this.key, this.kind, this.pattern});
|
||||
|
||||
Conditions.fromJson(Map<String, dynamic> json) {
|
||||
key = json['key'];
|
||||
kind = json['kind'];
|
||||
pattern = json['pattern'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = new Map<String, dynamic>();
|
||||
data['key'] = this.key;
|
||||
data['kind'] = this.kind;
|
||||
data['pattern'] = this.pattern;
|
||||
return data;
|
||||
}
|
||||
}
|
83
lib/src/utils/PushRules.dart
Normal file
83
lib/src/utils/PushRules.dart
Normal file
|
@ -0,0 +1,83 @@
|
|||
/// The global ruleset.
|
||||
class PushRules {
|
||||
final GlobalPushRules global;
|
||||
|
||||
PushRules.fromJson(Map<String, dynamic> json)
|
||||
: this.global = GlobalPushRules.fromJson(json["global"]);
|
||||
}
|
||||
|
||||
/// The global ruleset.
|
||||
class GlobalPushRules {
|
||||
final List<PushRule> content;
|
||||
final List<PushRule> override;
|
||||
final List<PushRule> room;
|
||||
final List<PushRule> sender;
|
||||
final List<PushRule> underride;
|
||||
|
||||
GlobalPushRules.fromJson(Map<String, dynamic> json)
|
||||
: this.content = json.containsKey("content")
|
||||
? PushRule.fromJsonList(json["content"])
|
||||
: null,
|
||||
this.override = json.containsKey("override")
|
||||
? PushRule.fromJsonList(json["content"])
|
||||
: null,
|
||||
this.room = json.containsKey("room")
|
||||
? PushRule.fromJsonList(json["room"])
|
||||
: null,
|
||||
this.sender = json.containsKey("sender")
|
||||
? PushRule.fromJsonList(json["sender"])
|
||||
: null,
|
||||
this.underride = json.containsKey("underride")
|
||||
? PushRule.fromJsonList(json["underride"])
|
||||
: null;
|
||||
}
|
||||
|
||||
/// A single pushrule.
|
||||
class PushRule {
|
||||
final List actions;
|
||||
final bool isDefault;
|
||||
final bool enabled;
|
||||
final String ruleId;
|
||||
final List<PushRuleConditions> conditions;
|
||||
final String pattern;
|
||||
|
||||
static List<PushRule> fromJsonList(List<dynamic> list) {
|
||||
List<PushRule> objList = [];
|
||||
list.forEach((json) {
|
||||
objList.add(PushRule.fromJson(json));
|
||||
});
|
||||
return objList;
|
||||
}
|
||||
|
||||
PushRule.fromJson(Map<String, dynamic> json)
|
||||
: this.actions = json["actions"],
|
||||
this.isDefault = json["default"],
|
||||
this.enabled = json["enabled"],
|
||||
this.ruleId = json["rule_id"],
|
||||
this.conditions = json.containsKey("conditions")
|
||||
? PushRuleConditions.fromJsonList(json["conditions"])
|
||||
: null,
|
||||
this.pattern = json["pattern"];
|
||||
}
|
||||
|
||||
/// Conditions when this pushrule should be active.
|
||||
class PushRuleConditions {
|
||||
final String kind;
|
||||
final String key;
|
||||
final String pattern;
|
||||
final String is_;
|
||||
|
||||
static List<PushRuleConditions> fromJsonList(List<dynamic> list) {
|
||||
List<PushRuleConditions> objList = [];
|
||||
list.forEach((json) {
|
||||
objList.add(PushRuleConditions.fromJson(json));
|
||||
});
|
||||
return objList;
|
||||
}
|
||||
|
||||
PushRuleConditions.fromJson(Map<String, dynamic> json)
|
||||
: this.kind = json["kind"],
|
||||
this.key = json["key"],
|
||||
this.pattern = json["pattern"],
|
||||
this.is_ = json["is"];
|
||||
}
|
|
@ -1,11 +1,10 @@
|
|||
import 'package:famedlysdk/src/utils/ChatTime.dart';
|
||||
import '../User.dart';
|
||||
|
||||
/// Represents a receipt.
|
||||
/// This [user] has read an event at the given [time].
|
||||
class Receipt {
|
||||
final User user;
|
||||
final ChatTime time;
|
||||
final DateTime time;
|
||||
|
||||
const Receipt(this.user, this.time);
|
||||
}
|
||||
|
|
|
@ -3,9 +3,9 @@ import 'package:famedlysdk/famedlysdk.dart';
|
|||
/// Matrix room states are addressed by a tuple of the [type] and an
|
||||
/// optional [stateKey].
|
||||
class StatesMap {
|
||||
Map<String, Map<String, RoomState>> states = {};
|
||||
Map<String, Map<String, Event>> states = {};
|
||||
|
||||
/// Returns either the [RoomState] or a map of state_keys to [RoomState] objects.
|
||||
/// Returns either the [Event] or a map of state_keys to [Event] objects.
|
||||
/// If you just enter a MatrixID, it will try to return the corresponding m.room.member event.
|
||||
dynamic operator [](String key) {
|
||||
//print("[Warning] This method will be depracated in the future!");
|
||||
|
@ -14,7 +14,7 @@ class StatesMap {
|
|||
return states["m.room.member"][key];
|
||||
}
|
||||
if (!states.containsKey(key)) states[key] = {};
|
||||
if (states[key][""] is RoomState)
|
||||
if (states[key][""] is Event)
|
||||
return states[key][""];
|
||||
else if (states[key].length == 0)
|
||||
return null;
|
||||
|
@ -22,7 +22,7 @@ class StatesMap {
|
|||
return states[key];
|
||||
}
|
||||
|
||||
void operator []=(String key, RoomState val) {
|
||||
void operator []=(String key, Event val) {
|
||||
//print("[Warning] This method will be depracated in the future!");
|
||||
if (key.startsWith("@") && key.contains(":")) {
|
||||
if (!states.containsKey("m.room.member")) states["m.room.member"] = {};
|
||||
|
|
|
@ -1,63 +0,0 @@
|
|||
/*
|
||||
* 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:test/test.dart';
|
||||
import 'package:famedlysdk/src/utils/ChatTime.dart';
|
||||
|
||||
void main() {
|
||||
/// All Tests related to the ChatTime
|
||||
group("ChatTime", () {
|
||||
test("Comparing", () async {
|
||||
final int originServerTs = DateTime.now().millisecondsSinceEpoch -
|
||||
(ChatTime.minutesBetweenEnvironments - 1) * 1000 * 60;
|
||||
final int oldOriginServerTs = DateTime.now().millisecondsSinceEpoch -
|
||||
(ChatTime.minutesBetweenEnvironments + 1) * 1000 * 60;
|
||||
|
||||
final ChatTime chatTime = ChatTime(originServerTs);
|
||||
final ChatTime oldChatTime = ChatTime(oldOriginServerTs);
|
||||
final ChatTime nowTime = ChatTime.now();
|
||||
|
||||
expect(chatTime.toTimeStamp(), originServerTs);
|
||||
expect(nowTime.toTimeStamp() > chatTime.toTimeStamp(), true);
|
||||
expect(nowTime.sameEnvironment(chatTime), true);
|
||||
expect(nowTime.sameEnvironment(oldChatTime), false);
|
||||
|
||||
expect(chatTime > oldChatTime, true);
|
||||
expect(chatTime < oldChatTime, false);
|
||||
expect(chatTime >= oldChatTime, true);
|
||||
expect(chatTime <= oldChatTime, false);
|
||||
expect(chatTime == chatTime, true);
|
||||
expect(chatTime == oldChatTime, false);
|
||||
});
|
||||
|
||||
test("Formatting", () async {
|
||||
final int timestamp = DateTime.now().millisecondsSinceEpoch;
|
||||
final ChatTime chatTime = ChatTime(timestamp);
|
||||
//expect(chatTime.toTimeString(),"05:36"); // This depends on the time and your timezone ;)
|
||||
expect(chatTime.toTimeString(), chatTime.toEventTimeString());
|
||||
|
||||
final ChatTime oldChatTime = ChatTime(156014498475);
|
||||
expect(oldChatTime.toString(), "11.12.1974");
|
||||
});
|
||||
});
|
||||
}
|
|
@ -25,12 +25,9 @@ import 'dart:async';
|
|||
|
||||
import 'package:famedlysdk/src/AccountData.dart';
|
||||
import 'package:famedlysdk/src/Client.dart';
|
||||
import 'package:famedlysdk/src/Connection.dart';
|
||||
import 'package:famedlysdk/src/Presence.dart';
|
||||
import 'package:famedlysdk/src/Room.dart';
|
||||
import 'package:famedlysdk/src/User.dart';
|
||||
import 'package:famedlysdk/src/requests/SetPushersRequest.dart';
|
||||
import 'package:famedlysdk/src/responses/PushrulesResponse.dart';
|
||||
import 'package:famedlysdk/src/sync/EventUpdate.dart';
|
||||
import 'package:famedlysdk/src/sync/RoomUpdate.dart';
|
||||
import 'package:famedlysdk/src/sync/UserUpdate.dart';
|
||||
|
@ -53,11 +50,11 @@ void main() {
|
|||
/// Check if all Elements get created
|
||||
|
||||
matrix = Client("testclient", debug: true);
|
||||
matrix.connection.httpClient = FakeMatrixApi();
|
||||
matrix.httpClient = FakeMatrixApi();
|
||||
|
||||
roomUpdateListFuture = matrix.connection.onRoomUpdate.stream.toList();
|
||||
eventUpdateListFuture = matrix.connection.onEvent.stream.toList();
|
||||
userUpdateListFuture = matrix.connection.onUserEvent.stream.toList();
|
||||
roomUpdateListFuture = matrix.onRoomUpdate.stream.toList();
|
||||
eventUpdateListFuture = matrix.onEvent.stream.toList();
|
||||
userUpdateListFuture = matrix.onUserEvent.stream.toList();
|
||||
|
||||
test('Login', () async {
|
||||
int presenceCounter = 0;
|
||||
|
@ -82,7 +79,7 @@ void main() {
|
|||
expect(matrix.matrixVersions,
|
||||
["r0.0.1", "r0.1.0", "r0.2.0", "r0.3.0", "r0.4.0", "r0.5.0"]);
|
||||
|
||||
final Map<String, dynamic> resp = await matrix.connection
|
||||
final Map<String, dynamic> resp = await matrix
|
||||
.jsonRequest(type: HTTPType.POST, action: "/client/r0/login", data: {
|
||||
"type": "m.login.password",
|
||||
"user": "test",
|
||||
|
@ -91,11 +88,11 @@ void main() {
|
|||
});
|
||||
|
||||
Future<LoginState> loginStateFuture =
|
||||
matrix.connection.onLoginStateChanged.stream.first;
|
||||
Future<bool> firstSyncFuture = matrix.connection.onFirstSync.stream.first;
|
||||
Future<dynamic> syncFuture = matrix.connection.onSync.stream.first;
|
||||
matrix.onLoginStateChanged.stream.first;
|
||||
Future<bool> firstSyncFuture = matrix.onFirstSync.stream.first;
|
||||
Future<dynamic> syncFuture = matrix.onSync.stream.first;
|
||||
|
||||
matrix.connection.connect(
|
||||
matrix.connect(
|
||||
newToken: resp["access_token"],
|
||||
newUserID: resp["user_id"],
|
||||
newHomeserver: matrix.homeserver,
|
||||
|
@ -121,23 +118,23 @@ void main() {
|
|||
expect(matrix.accountData.length, 3);
|
||||
expect(matrix.getDirectChatFromUserId("@bob:example.com"),
|
||||
"!726s6s6q:example.com");
|
||||
expect(matrix.roomList.rooms[1].directChatMatrixID, "@bob:example.com");
|
||||
expect(matrix.rooms[1].directChatMatrixID, "@bob:example.com");
|
||||
expect(matrix.directChats, matrix.accountData["m.direct"].content);
|
||||
expect(matrix.presences.length, 1);
|
||||
expect(matrix.roomList.rooms[1].ephemerals.length, 2);
|
||||
expect(matrix.roomList.rooms[1].typingUsers.length, 1);
|
||||
expect(matrix.roomList.rooms[1].typingUsers[0].id, "@alice:example.com");
|
||||
expect(matrix.roomList.rooms[1].roomAccountData.length, 3);
|
||||
expect(matrix.rooms[1].ephemerals.length, 2);
|
||||
expect(matrix.rooms[1].typingUsers.length, 1);
|
||||
expect(matrix.rooms[1].typingUsers[0].id, "@alice:example.com");
|
||||
expect(matrix.rooms[1].roomAccountData.length, 3);
|
||||
expect(
|
||||
matrix.roomList.rooms[1].roomAccountData["m.receipt"]
|
||||
matrix.rooms[1].roomAccountData["m.receipt"]
|
||||
.content["@alice:example.com"]["ts"],
|
||||
1436451550453);
|
||||
expect(
|
||||
matrix.roomList.rooms[1].roomAccountData["m.receipt"]
|
||||
matrix.rooms[1].roomAccountData["m.receipt"]
|
||||
.content["@alice:example.com"]["event_id"],
|
||||
"7365636s6r6432:example.com");
|
||||
expect(matrix.roomList.rooms.length, 2);
|
||||
expect(matrix.roomList.rooms[1].canonicalAlias,
|
||||
expect(matrix.rooms.length, 2);
|
||||
expect(matrix.rooms[1].canonicalAlias,
|
||||
"#famedlyContactDiscovery:${matrix.userID.split(":")[1]}");
|
||||
final List<User> contacts = await matrix.loadFamedlyContacts();
|
||||
expect(contacts.length, 1);
|
||||
|
@ -147,25 +144,30 @@ void main() {
|
|||
expect(presenceCounter, 1);
|
||||
expect(accountDataCounter, 3);
|
||||
|
||||
matrix.connection.onEvent.add(
|
||||
EventUpdate(
|
||||
roomID: "!726s6s6q:example.com",
|
||||
type: "state",
|
||||
eventType: "m.room.canonical_alias",
|
||||
content: {
|
||||
"sender": "@alice:example.com",
|
||||
"type": "m.room.canonical_alias",
|
||||
"content": {"alias": ""},
|
||||
"state_key": "",
|
||||
"origin_server_ts": 1417731086799,
|
||||
"event_id": "66697273743033:example.com"
|
||||
},
|
||||
),
|
||||
);
|
||||
matrix.handleSync({
|
||||
"rooms": {
|
||||
"join": {
|
||||
"!726s6s6q:example.com": {
|
||||
"state": {
|
||||
"events": [
|
||||
{
|
||||
"sender": "@alice:example.com",
|
||||
"type": "m.room.canonical_alias",
|
||||
"content": {"alias": ""},
|
||||
"state_key": "",
|
||||
"origin_server_ts": 1417731086799,
|
||||
"event_id": "66697273743033:example.com"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
await new Future.delayed(new Duration(milliseconds: 50));
|
||||
|
||||
expect(
|
||||
matrix.roomList.getRoomByAlias(
|
||||
matrix.getRoomByAlias(
|
||||
"#famedlyContactDiscovery:${matrix.userID.split(":")[1]}"),
|
||||
null);
|
||||
final List<User> altContacts = await matrix.loadFamedlyContacts();
|
||||
|
@ -176,8 +178,8 @@ void main() {
|
|||
test('Try to get ErrorResponse', () async {
|
||||
MatrixException expectedException;
|
||||
try {
|
||||
await matrix.connection
|
||||
.jsonRequest(type: HTTPType.PUT, action: "/non/existing/path");
|
||||
await matrix.jsonRequest(
|
||||
type: HTTPType.PUT, action: "/non/existing/path");
|
||||
} on MatrixException catch (exception) {
|
||||
expectedException = exception;
|
||||
}
|
||||
|
@ -185,13 +187,13 @@ void main() {
|
|||
});
|
||||
|
||||
test('Logout', () async {
|
||||
await matrix.connection
|
||||
.jsonRequest(type: HTTPType.POST, action: "/client/r0/logout");
|
||||
await matrix.jsonRequest(
|
||||
type: HTTPType.POST, action: "/client/r0/logout");
|
||||
|
||||
Future<LoginState> loginStateFuture =
|
||||
matrix.connection.onLoginStateChanged.stream.first;
|
||||
matrix.onLoginStateChanged.stream.first;
|
||||
|
||||
matrix.connection.clear();
|
||||
matrix.clear();
|
||||
|
||||
expect(matrix.accessToken == null, true);
|
||||
expect(matrix.homeserver == null, true);
|
||||
|
@ -207,11 +209,11 @@ void main() {
|
|||
});
|
||||
|
||||
test('Room Update Test', () async {
|
||||
matrix.connection.onRoomUpdate.close();
|
||||
matrix.onRoomUpdate.close();
|
||||
|
||||
List<RoomUpdate> roomUpdateList = await roomUpdateListFuture;
|
||||
|
||||
expect(roomUpdateList.length, 2);
|
||||
expect(roomUpdateList.length, 3);
|
||||
|
||||
expect(roomUpdateList[0].id == "!726s6s6q:example.com", true);
|
||||
expect(roomUpdateList[0].membership == Membership.join, true);
|
||||
|
@ -229,7 +231,7 @@ void main() {
|
|||
});
|
||||
|
||||
test('Event Update Test', () async {
|
||||
matrix.connection.onEvent.close();
|
||||
matrix.onEvent.close();
|
||||
|
||||
List<EventUpdate> eventUpdateList = await eventUpdateListFuture;
|
||||
|
||||
|
@ -281,7 +283,7 @@ void main() {
|
|||
});
|
||||
|
||||
test('User Update Test', () async {
|
||||
matrix.connection.onUserEvent.close();
|
||||
matrix.onUserEvent.close();
|
||||
|
||||
List<UserUpdate> eventUpdateList = await userUpdateListFuture;
|
||||
|
||||
|
@ -299,11 +301,11 @@ void main() {
|
|||
|
||||
test('Login', () async {
|
||||
matrix = Client("testclient", debug: true);
|
||||
matrix.connection.httpClient = FakeMatrixApi();
|
||||
matrix.httpClient = FakeMatrixApi();
|
||||
|
||||
roomUpdateListFuture = matrix.connection.onRoomUpdate.stream.toList();
|
||||
eventUpdateListFuture = matrix.connection.onEvent.stream.toList();
|
||||
userUpdateListFuture = matrix.connection.onUserEvent.stream.toList();
|
||||
roomUpdateListFuture = matrix.onRoomUpdate.stream.toList();
|
||||
eventUpdateListFuture = matrix.onEvent.stream.toList();
|
||||
userUpdateListFuture = matrix.onUserEvent.stream.toList();
|
||||
final bool checkResp =
|
||||
await matrix.checkServer("https://fakeServer.notExisting");
|
||||
|
||||
|
@ -326,7 +328,7 @@ void main() {
|
|||
final MatrixFile testFile =
|
||||
MatrixFile(bytes: [], path: "fake/path/file.jpeg");
|
||||
|
||||
final dynamic resp = await matrix.connection.upload(testFile);
|
||||
final dynamic resp = await matrix.upload(testFile);
|
||||
expect(resp, "mxc://example.com/AQwafuaFswefuhsfAFAgsw");
|
||||
});
|
||||
|
||||
|
@ -337,23 +339,14 @@ void main() {
|
|||
});
|
||||
|
||||
test('getPushrules', () async {
|
||||
final PushrulesResponse pushrules = await matrix.getPushrules();
|
||||
final PushrulesResponse awaited_resp = PushrulesResponse.fromJson(
|
||||
FakeMatrixApi.api["GET"]["/client/r0/pushrules/"](""));
|
||||
expect(pushrules.toJson(), awaited_resp.toJson());
|
||||
final pushrules = await matrix.getPushrules();
|
||||
expect(pushrules != null, true);
|
||||
});
|
||||
|
||||
test('setPushers', () async {
|
||||
final SetPushersRequest data = SetPushersRequest(
|
||||
app_id: "com.famedly.famedlysdk",
|
||||
device_display_name: "GitLabCi",
|
||||
app_display_name: "famedlySDK",
|
||||
pushkey: "abcdefg",
|
||||
kind: "http",
|
||||
lang: "en",
|
||||
data: PusherData(
|
||||
format: "event_id_only", url: "https://examplepushserver.com"));
|
||||
await matrix.setPushers(data);
|
||||
await matrix.setPushers("abcdefg", "http", "com.famedly.famedlysdk",
|
||||
"famedlySDK", "GitLabCi", "en", "https://examplepushserver.com",
|
||||
format: "event_id_only");
|
||||
});
|
||||
|
||||
test('joinRoomById', () async {
|
||||
|
@ -388,11 +381,11 @@ void main() {
|
|||
|
||||
test('Logout when token is unknown', () async {
|
||||
Future<LoginState> loginStateFuture =
|
||||
matrix.connection.onLoginStateChanged.stream.first;
|
||||
matrix.onLoginStateChanged.stream.first;
|
||||
|
||||
try {
|
||||
await matrix.connection
|
||||
.jsonRequest(type: HTTPType.DELETE, action: "/unknown/token");
|
||||
await matrix.jsonRequest(
|
||||
type: HTTPType.DELETE, action: "/unknown/token");
|
||||
} on MatrixException catch (exception) {
|
||||
expect(exception.error, MatrixError.M_UNKNOWN_TOKEN);
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:famedlysdk/famedlysdk.dart';
|
||||
import 'package:famedlysdk/src/RoomState.dart';
|
||||
import 'package:famedlysdk/src/Event.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import 'FakeMatrixApi.dart';
|
||||
|
@ -68,7 +68,7 @@ void main() {
|
|||
expect(event.getBody(), body);
|
||||
expect(event.type, EventTypes.Text);
|
||||
jsonObj["state_key"] = "";
|
||||
RoomState state = RoomState.fromJson(jsonObj, null);
|
||||
Event state = Event.fromJson(jsonObj, null);
|
||||
expect(state.eventId, id);
|
||||
expect(state.stateKey, "");
|
||||
expect(state.timelineEvent.status, 1);
|
||||
|
@ -174,7 +174,7 @@ void main() {
|
|||
"type": "m.room.redaction",
|
||||
"unsigned": {"age": 1234}
|
||||
};
|
||||
RoomState redactedBecause = RoomState.fromJson(redactionEventJson, room);
|
||||
Event redactedBecause = Event.fromJson(redactionEventJson, room);
|
||||
Event event = Event.fromJson(jsonObj, room);
|
||||
event.setRedactionEvent(redactedBecause);
|
||||
expect(event.redacted, true);
|
||||
|
@ -196,7 +196,7 @@ void main() {
|
|||
|
||||
test("sendAgain", () async {
|
||||
Client matrix = Client("testclient", debug: true);
|
||||
matrix.connection.httpClient = FakeMatrixApi();
|
||||
matrix.httpClient = FakeMatrixApi();
|
||||
await matrix.checkServer("https://fakeServer.notExisting");
|
||||
await matrix.login("test", "1234");
|
||||
|
||||
|
|
|
@ -25,12 +25,15 @@ import 'package:test/test.dart';
|
|||
import 'package:famedlysdk/src/Client.dart';
|
||||
import 'package:famedlysdk/src/utils/MxContent.dart';
|
||||
|
||||
import 'FakeMatrixApi.dart';
|
||||
|
||||
void main() {
|
||||
/// All Tests related to the MxContent
|
||||
group("MxContent", () {
|
||||
test("Formatting", () async {
|
||||
Client client = Client("testclient");
|
||||
client.homeserver = "https://testserver.abc";
|
||||
client.httpClient = FakeMatrixApi();
|
||||
await client.checkServer("https://fakeserver.notexisting");
|
||||
final String mxc = "mxc://exampleserver.abc/abcdefghijklmn";
|
||||
final MxContent content = MxContent(mxc);
|
||||
|
||||
|
@ -45,7 +48,8 @@ void main() {
|
|||
});
|
||||
test("Not crashing if null", () async {
|
||||
Client client = Client("testclient");
|
||||
client.homeserver = "https://testserver.abc";
|
||||
client.httpClient = FakeMatrixApi();
|
||||
await client.checkServer("https://fakeserver.notexisting");
|
||||
final MxContent content = MxContent(null);
|
||||
expect(content.getDownloadLink(client),
|
||||
"${client.homeserver}/_matrix/media/r0/download/");
|
||||
|
|
185
test/PushRules_test.dart
Normal file
185
test/PushRules_test.dart
Normal file
|
@ -0,0 +1,185 @@
|
|||
/*
|
||||
* 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/utils/PushRules.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
/// All Tests related to the MxContent
|
||||
group("PushRules", () {
|
||||
test("Create", () async {
|
||||
final Map<String, dynamic> json = {
|
||||
"global": {
|
||||
"content": [
|
||||
{
|
||||
"actions": [
|
||||
"notify",
|
||||
{"set_tweak": "sound", "value": "default"},
|
||||
{"set_tweak": "highlight"}
|
||||
],
|
||||
"default": true,
|
||||
"enabled": true,
|
||||
"pattern": "alice",
|
||||
"rule_id": ".m.rule.contains_user_name"
|
||||
}
|
||||
],
|
||||
"override": [
|
||||
{
|
||||
"actions": ["dont_notify"],
|
||||
"conditions": [],
|
||||
"default": true,
|
||||
"enabled": false,
|
||||
"rule_id": ".m.rule.master"
|
||||
},
|
||||
{
|
||||
"actions": ["dont_notify"],
|
||||
"conditions": [
|
||||
{
|
||||
"key": "content.msgtype",
|
||||
"kind": "event_match",
|
||||
"pattern": "m.notice"
|
||||
}
|
||||
],
|
||||
"default": true,
|
||||
"enabled": true,
|
||||
"rule_id": ".m.rule.suppress_notices"
|
||||
}
|
||||
],
|
||||
"room": [],
|
||||
"sender": [],
|
||||
"underride": [
|
||||
{
|
||||
"actions": [
|
||||
"notify",
|
||||
{"set_tweak": "sound", "value": "ring"},
|
||||
{"set_tweak": "highlight", "value": false}
|
||||
],
|
||||
"conditions": [
|
||||
{
|
||||
"key": "type",
|
||||
"kind": "event_match",
|
||||
"pattern": "m.call.invite"
|
||||
}
|
||||
],
|
||||
"default": true,
|
||||
"enabled": true,
|
||||
"rule_id": ".m.rule.call"
|
||||
},
|
||||
{
|
||||
"actions": [
|
||||
"notify",
|
||||
{"set_tweak": "sound", "value": "default"},
|
||||
{"set_tweak": "highlight"}
|
||||
],
|
||||
"conditions": [
|
||||
{"kind": "contains_display_name"}
|
||||
],
|
||||
"default": true,
|
||||
"enabled": true,
|
||||
"rule_id": ".m.rule.contains_display_name"
|
||||
},
|
||||
{
|
||||
"actions": [
|
||||
"notify",
|
||||
{"set_tweak": "sound", "value": "default"},
|
||||
{"set_tweak": "highlight", "value": false}
|
||||
],
|
||||
"conditions": [
|
||||
{"kind": "room_member_count", "is": "2"},
|
||||
{
|
||||
"kind": "event_match",
|
||||
"key": "type",
|
||||
"pattern": "m.room.message"
|
||||
}
|
||||
],
|
||||
"default": true,
|
||||
"enabled": true,
|
||||
"rule_id": ".m.rule.room_one_to_one"
|
||||
},
|
||||
{
|
||||
"actions": [
|
||||
"notify",
|
||||
{"set_tweak": "sound", "value": "default"},
|
||||
{"set_tweak": "highlight", "value": false}
|
||||
],
|
||||
"conditions": [
|
||||
{
|
||||
"key": "type",
|
||||
"kind": "event_match",
|
||||
"pattern": "m.room.member"
|
||||
},
|
||||
{
|
||||
"key": "content.membership",
|
||||
"kind": "event_match",
|
||||
"pattern": "invite"
|
||||
},
|
||||
{
|
||||
"key": "state_key",
|
||||
"kind": "event_match",
|
||||
"pattern": "@alice:example.com"
|
||||
}
|
||||
],
|
||||
"default": true,
|
||||
"enabled": true,
|
||||
"rule_id": ".m.rule.invite_for_me"
|
||||
},
|
||||
{
|
||||
"actions": [
|
||||
"notify",
|
||||
{"set_tweak": "highlight", "value": false}
|
||||
],
|
||||
"conditions": [
|
||||
{
|
||||
"key": "type",
|
||||
"kind": "event_match",
|
||||
"pattern": "m.room.member"
|
||||
}
|
||||
],
|
||||
"default": true,
|
||||
"enabled": true,
|
||||
"rule_id": ".m.rule.member_event"
|
||||
},
|
||||
{
|
||||
"actions": [
|
||||
"notify",
|
||||
{"set_tweak": "highlight", "value": false}
|
||||
],
|
||||
"conditions": [
|
||||
{
|
||||
"key": "type",
|
||||
"kind": "event_match",
|
||||
"pattern": "m.room.message"
|
||||
}
|
||||
],
|
||||
"default": true,
|
||||
"enabled": true,
|
||||
"rule_id": ".m.rule.message"
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
expect(PushRules.fromJson(json) != null, true);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -1,281 +0,0 @@
|
|||
/*
|
||||
* 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/Client.dart';
|
||||
import 'package:famedlysdk/src/RoomList.dart';
|
||||
import 'package:famedlysdk/src/User.dart';
|
||||
import 'package:famedlysdk/src/sync/EventUpdate.dart';
|
||||
import 'package:famedlysdk/src/sync/RoomUpdate.dart';
|
||||
import 'package:famedlysdk/src/utils/ChatTime.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import 'FakeMatrixApi.dart';
|
||||
|
||||
void main() {
|
||||
/// All Tests related to the MxContent
|
||||
group("RoomList", () {
|
||||
final roomID = "!1:example.com";
|
||||
|
||||
test("Create and insert one room", () async {
|
||||
final Client client = Client("testclient", debug: true);
|
||||
client.connection.httpClient = FakeMatrixApi();
|
||||
await client.checkServer("https://fakeserver.notexisting");
|
||||
client.prevBatch = "1234";
|
||||
|
||||
int updateCount = 0;
|
||||
List<int> insertList = [];
|
||||
List<int> removeList = [];
|
||||
|
||||
RoomList roomList = RoomList(
|
||||
client: client,
|
||||
rooms: [],
|
||||
onUpdate: () {
|
||||
updateCount++;
|
||||
},
|
||||
onInsert: (int insertID) {
|
||||
insertList.add(insertID);
|
||||
},
|
||||
onRemove: (int removeID) {
|
||||
insertList.add(removeID);
|
||||
});
|
||||
|
||||
expect(roomList.eventSub != null, true);
|
||||
expect(roomList.roomSub != null, true);
|
||||
|
||||
client.connection.onRoomUpdate.add(RoomUpdate(
|
||||
id: roomID,
|
||||
membership: Membership.join,
|
||||
notification_count: 2,
|
||||
highlight_count: 1,
|
||||
limitedTimeline: false,
|
||||
prev_batch: "1234",
|
||||
));
|
||||
|
||||
await new Future.delayed(new Duration(milliseconds: 50));
|
||||
|
||||
expect(updateCount, 1);
|
||||
expect(insertList, [0]);
|
||||
expect(removeList, []);
|
||||
|
||||
expect(roomList.rooms.length, 1);
|
||||
expect(roomList.rooms[0].id, roomID);
|
||||
expect(roomList.rooms[0].membership, Membership.join);
|
||||
expect(roomList.rooms[0].notificationCount, 2);
|
||||
expect(roomList.rooms[0].highlightCount, 1);
|
||||
expect(roomList.rooms[0].prev_batch, "1234");
|
||||
expect(roomList.rooms[0].timeCreated, ChatTime.now());
|
||||
});
|
||||
|
||||
test("Restort", () async {
|
||||
final Client client = Client("testclient", debug: true);
|
||||
client.connection.httpClient = FakeMatrixApi();
|
||||
await client.checkServer("https://fakeserver.notexisting");
|
||||
client.prevBatch = "1234";
|
||||
|
||||
int updateCount = 0;
|
||||
List<int> insertList = [];
|
||||
List<int> removeList = [];
|
||||
|
||||
RoomList roomList = RoomList(
|
||||
client: client,
|
||||
rooms: [],
|
||||
onUpdate: () {
|
||||
updateCount++;
|
||||
},
|
||||
onInsert: (int insertID) {
|
||||
insertList.add(insertID);
|
||||
},
|
||||
onRemove: (int removeID) {
|
||||
insertList.add(removeID);
|
||||
});
|
||||
|
||||
client.connection.onRoomUpdate.add(RoomUpdate(
|
||||
id: "1",
|
||||
membership: Membership.join,
|
||||
notification_count: 2,
|
||||
highlight_count: 1,
|
||||
limitedTimeline: false,
|
||||
prev_batch: "1234",
|
||||
));
|
||||
client.connection.onRoomUpdate.add(RoomUpdate(
|
||||
id: "2",
|
||||
membership: Membership.join,
|
||||
notification_count: 2,
|
||||
highlight_count: 1,
|
||||
limitedTimeline: false,
|
||||
prev_batch: "1234",
|
||||
));
|
||||
client.connection.onRoomUpdate.add(RoomUpdate(
|
||||
id: "1",
|
||||
membership: Membership.join,
|
||||
notification_count: 2,
|
||||
highlight_count: 1,
|
||||
limitedTimeline: false,
|
||||
prev_batch: "12345",
|
||||
summary: RoomSummary(
|
||||
mHeroes: ["@alice:example.com"],
|
||||
mJoinedMemberCount: 1,
|
||||
mInvitedMemberCount: 1)));
|
||||
|
||||
await new Future.delayed(new Duration(milliseconds: 50));
|
||||
|
||||
expect(roomList.eventSub != null, true);
|
||||
expect(roomList.roomSub != null, true);
|
||||
expect(roomList.rooms[0].id, "1");
|
||||
expect(roomList.rooms[1].id, "2");
|
||||
expect(roomList.rooms[0].prev_batch, "12345");
|
||||
expect(roomList.rooms[0].displayname, "alice");
|
||||
expect(roomList.rooms[0].mJoinedMemberCount, 1);
|
||||
expect(roomList.rooms[0].mInvitedMemberCount, 1);
|
||||
|
||||
ChatTime now = ChatTime.now();
|
||||
|
||||
int roomUpdates = 0;
|
||||
|
||||
roomList.rooms[0].onUpdate = () {
|
||||
roomUpdates++;
|
||||
};
|
||||
roomList.rooms[1].onUpdate = () {
|
||||
roomUpdates++;
|
||||
};
|
||||
|
||||
client.connection.onEvent.add(EventUpdate(
|
||||
type: "timeline",
|
||||
roomID: "1",
|
||||
eventType: "m.room.message",
|
||||
content: {
|
||||
"type": "m.room.message",
|
||||
"content": {"msgtype": "m.text", "body": "Testcase"},
|
||||
"sender": "@alice:example.com",
|
||||
"room_id": "1",
|
||||
"status": 2,
|
||||
"event_id": "1",
|
||||
"origin_server_ts": now.toTimeStamp() - 1000
|
||||
}));
|
||||
|
||||
client.connection.onEvent.add(EventUpdate(
|
||||
type: "timeline",
|
||||
roomID: "2",
|
||||
eventType: "m.room.message",
|
||||
content: {
|
||||
"type": "m.room.message",
|
||||
"content": {"msgtype": "m.text", "body": "Testcase 2"},
|
||||
"sender": "@alice:example.com",
|
||||
"room_id": "1",
|
||||
"status": 2,
|
||||
"event_id": "2",
|
||||
"origin_server_ts": now.toTimeStamp()
|
||||
}));
|
||||
|
||||
await new Future.delayed(new Duration(milliseconds: 50));
|
||||
|
||||
expect(updateCount, 5);
|
||||
expect(roomUpdates, 3);
|
||||
expect(insertList, [0, 1]);
|
||||
expect(removeList, []);
|
||||
|
||||
expect(roomList.rooms.length, 2);
|
||||
expect(
|
||||
roomList.rooms[0].timeCreated > roomList.rooms[1].timeCreated, true);
|
||||
expect(roomList.rooms[0].id, "2");
|
||||
expect(roomList.rooms[1].id, "1");
|
||||
expect(roomList.rooms[0].lastMessage, "Testcase 2");
|
||||
expect(roomList.rooms[0].timeCreated, now);
|
||||
|
||||
client.connection.onEvent.add(EventUpdate(
|
||||
type: "timeline",
|
||||
roomID: "1",
|
||||
eventType: "m.room.redaction",
|
||||
content: {
|
||||
"content": {"reason": "Spamming"},
|
||||
"event_id": "143273582443PhrSn:example.org",
|
||||
"origin_server_ts": 1432735824653,
|
||||
"redacts": "1",
|
||||
"room_id": "1",
|
||||
"sender": "@example:example.org",
|
||||
"type": "m.room.redaction",
|
||||
"unsigned": {"age": 1234}
|
||||
}));
|
||||
|
||||
await new Future.delayed(new Duration(milliseconds: 50));
|
||||
|
||||
expect(updateCount, 6);
|
||||
expect(insertList, [0, 1]);
|
||||
expect(removeList, []);
|
||||
expect(roomList.rooms.length, 2);
|
||||
expect(roomList.rooms[1].getState("m.room.message").eventId, "1");
|
||||
expect(roomList.rooms[1].getState("m.room.message").redacted, true);
|
||||
});
|
||||
|
||||
test("onlyLeft", () async {
|
||||
final Client client = Client("testclient", debug: true);
|
||||
client.connection.httpClient = FakeMatrixApi();
|
||||
await client.checkServer("https://fakeserver.notexisting");
|
||||
client.prevBatch = "1234";
|
||||
|
||||
int updateCount = 0;
|
||||
List<int> insertList = [];
|
||||
List<int> removeList = [];
|
||||
|
||||
RoomList roomList = RoomList(
|
||||
client: client,
|
||||
onlyLeft: true,
|
||||
rooms: [],
|
||||
onUpdate: () {
|
||||
updateCount++;
|
||||
},
|
||||
onInsert: (int insertID) {
|
||||
insertList.add(insertID);
|
||||
},
|
||||
onRemove: (int removeID) {
|
||||
insertList.add(removeID);
|
||||
});
|
||||
|
||||
client.connection.onRoomUpdate.add(RoomUpdate(
|
||||
id: "1",
|
||||
membership: Membership.join,
|
||||
notification_count: 2,
|
||||
highlight_count: 1,
|
||||
limitedTimeline: false,
|
||||
prev_batch: "1234",
|
||||
));
|
||||
client.connection.onRoomUpdate.add(RoomUpdate(
|
||||
id: "2",
|
||||
membership: Membership.leave,
|
||||
notification_count: 2,
|
||||
highlight_count: 1,
|
||||
limitedTimeline: false,
|
||||
prev_batch: "1234",
|
||||
));
|
||||
|
||||
await new Future.delayed(new Duration(milliseconds: 50));
|
||||
|
||||
expect(roomList.eventSub != null, true);
|
||||
expect(roomList.roomSub != null, true);
|
||||
expect(roomList.rooms[0].id, "2");
|
||||
expect(insertList, [0]);
|
||||
expect(removeList, []);
|
||||
expect(updateCount, 2);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -24,10 +24,8 @@
|
|||
import 'package:famedlysdk/src/Client.dart';
|
||||
import 'package:famedlysdk/src/Event.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/utils/ChatTime.dart';
|
||||
import 'package:famedlysdk/src/utils/MatrixFile.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
|
@ -41,7 +39,7 @@ void main() {
|
|||
group("Room", () {
|
||||
test('Login', () async {
|
||||
matrix = Client("testclient", debug: true);
|
||||
matrix.connection.httpClient = FakeMatrixApi();
|
||||
matrix.httpClient = FakeMatrixApi();
|
||||
|
||||
final bool checkResp =
|
||||
await matrix.checkServer("https://fakeServer.notExisting");
|
||||
|
@ -86,7 +84,7 @@ void main() {
|
|||
expect(room.mHeroes, heroes);
|
||||
expect(room.displayname, "alice, bob, charley");
|
||||
|
||||
room.states["m.room.canonical_alias"] = RoomState(
|
||||
room.states["m.room.canonical_alias"] = Event(
|
||||
senderId: "@test:example.com",
|
||||
typeKey: "m.room.canonical_alias",
|
||||
roomId: room.id,
|
||||
|
@ -97,7 +95,7 @@ void main() {
|
|||
expect(room.displayname, "testalias");
|
||||
expect(room.canonicalAlias, "#testalias:example.com");
|
||||
|
||||
room.states["m.room.name"] = RoomState(
|
||||
room.states["m.room.name"] = Event(
|
||||
senderId: "@test:example.com",
|
||||
typeKey: "m.room.name",
|
||||
roomId: room.id,
|
||||
|
@ -108,7 +106,7 @@ void main() {
|
|||
expect(room.displayname, "testname");
|
||||
|
||||
expect(room.topic, "");
|
||||
room.states["m.room.topic"] = RoomState(
|
||||
room.states["m.room.topic"] = Event(
|
||||
senderId: "@test:example.com",
|
||||
typeKey: "m.room.topic",
|
||||
roomId: room.id,
|
||||
|
@ -119,7 +117,7 @@ void main() {
|
|||
expect(room.topic, "testtopic");
|
||||
|
||||
expect(room.avatar.mxc, "");
|
||||
room.states["m.room.avatar"] = RoomState(
|
||||
room.states["m.room.avatar"] = Event(
|
||||
senderId: "@test:example.com",
|
||||
typeKey: "m.room.avatar",
|
||||
roomId: room.id,
|
||||
|
@ -130,13 +128,13 @@ void main() {
|
|||
expect(room.avatar.mxc, "mxc://testurl");
|
||||
|
||||
expect(room.lastEvent, null);
|
||||
room.states["m.room.message"] = RoomState(
|
||||
room.states["m.room.message"] = Event(
|
||||
senderId: "@test:example.com",
|
||||
typeKey: "m.room.message",
|
||||
roomId: room.id,
|
||||
room: room,
|
||||
eventId: "12345",
|
||||
time: ChatTime.now(),
|
||||
time: DateTime.now(),
|
||||
content: {"msgtype": "m.text", "body": "test"},
|
||||
stateKey: "");
|
||||
expect(room.lastEvent.eventId, "12345");
|
||||
|
@ -187,7 +185,7 @@ void main() {
|
|||
});
|
||||
|
||||
test("PowerLevels", () async {
|
||||
room.states["m.room.power_levels"] = RoomState(
|
||||
room.states["m.room.power_levels"] = Event(
|
||||
senderId: "@test:example.com",
|
||||
typeKey: "m.room.power_levels",
|
||||
roomId: room.id,
|
||||
|
@ -223,7 +221,7 @@ void main() {
|
|||
expect(room.powerLevels,
|
||||
room.states["m.room.power_levels"].content["users"]);
|
||||
|
||||
room.states["m.room.power_levels"] = RoomState(
|
||||
room.states["m.room.power_levels"] = Event(
|
||||
senderId: "@test:example.com",
|
||||
typeKey: "m.room.power_levels",
|
||||
roomId: room.id,
|
||||
|
@ -264,13 +262,13 @@ void main() {
|
|||
});
|
||||
|
||||
test("getParticipants", () async {
|
||||
room.setState(RoomState(
|
||||
room.setState(Event(
|
||||
senderId: "@alice:test.abc",
|
||||
typeKey: "m.room.member",
|
||||
roomId: room.id,
|
||||
room: room,
|
||||
eventId: "12345",
|
||||
time: ChatTime.now(),
|
||||
time: DateTime.now(),
|
||||
content: {"displayname": "alice"},
|
||||
stateKey: "@alice:test.abc"));
|
||||
final List<User> userList = room.getParticipants();
|
||||
|
|
|
@ -30,7 +30,7 @@ void main() {
|
|||
group("StateKeys", () {
|
||||
test("Operator overload", () async {
|
||||
StatesMap states = StatesMap();
|
||||
states["m.room.name"] = RoomState(
|
||||
states["m.room.name"] = Event(
|
||||
eventId: "1",
|
||||
content: {"name": "test"},
|
||||
typeKey: "m.room.name",
|
||||
|
@ -38,7 +38,7 @@ void main() {
|
|||
roomId: "!test:test.test",
|
||||
senderId: "@alice:test.test");
|
||||
|
||||
states["@alice:test.test"] = RoomState(
|
||||
states["@alice:test.test"] = Event(
|
||||
eventId: "2",
|
||||
content: {"membership": "join"},
|
||||
typeKey: "m.room.name",
|
||||
|
@ -46,7 +46,7 @@ void main() {
|
|||
roomId: "!test:test.test",
|
||||
senderId: "@alice:test.test");
|
||||
|
||||
states["m.room.member"]["@bob:test.test"] = RoomState(
|
||||
states["m.room.member"]["@bob:test.test"] = Event(
|
||||
eventId: "3",
|
||||
content: {"membership": "join"},
|
||||
typeKey: "m.room.name",
|
||||
|
@ -54,7 +54,7 @@ void main() {
|
|||
roomId: "!test:test.test",
|
||||
senderId: "@bob:test.test");
|
||||
|
||||
states["com.test.custom"] = RoomState(
|
||||
states["com.test.custom"] = Event(
|
||||
eventId: "4",
|
||||
content: {"custom": "stuff"},
|
||||
typeKey: "com.test.custom",
|
||||
|
|
|
@ -27,20 +27,18 @@ import 'package:famedlysdk/src/Client.dart';
|
|||
import 'package:famedlysdk/src/Room.dart';
|
||||
import 'package:famedlysdk/src/Timeline.dart';
|
||||
import 'package:famedlysdk/src/sync/EventUpdate.dart';
|
||||
import 'package:famedlysdk/src/utils/ChatTime.dart';
|
||||
import 'FakeMatrixApi.dart';
|
||||
|
||||
void main() {
|
||||
/// All Tests related to the MxContent
|
||||
group("Timeline", () {
|
||||
final String roomID = "!1234:example.com";
|
||||
final testTimeStamp = ChatTime.now().toTimeStamp();
|
||||
final testTimeStamp = DateTime.now().millisecondsSinceEpoch;
|
||||
int updateCount = 0;
|
||||
List<int> insertList = [];
|
||||
|
||||
Client client = Client("testclient", debug: true);
|
||||
client.connection.httpClient = FakeMatrixApi();
|
||||
client.homeserver = "https://fakeServer.notExisting";
|
||||
client.httpClient = FakeMatrixApi();
|
||||
|
||||
Room room = Room(
|
||||
id: roomID, client: client, prev_batch: "1234", roomAccountData: {});
|
||||
|
@ -55,7 +53,8 @@ void main() {
|
|||
});
|
||||
|
||||
test("Create", () async {
|
||||
client.connection.onEvent.add(EventUpdate(
|
||||
await client.checkServer("https://fakeServer.notExisting");
|
||||
client.onEvent.add(EventUpdate(
|
||||
type: "timeline",
|
||||
roomID: roomID,
|
||||
eventType: "m.room.message",
|
||||
|
@ -68,7 +67,7 @@ void main() {
|
|||
"origin_server_ts": testTimeStamp
|
||||
}));
|
||||
|
||||
client.connection.onEvent.add(EventUpdate(
|
||||
client.onEvent.add(EventUpdate(
|
||||
type: "timeline",
|
||||
roomID: roomID,
|
||||
eventType: "m.room.message",
|
||||
|
@ -91,9 +90,12 @@ void main() {
|
|||
expect(timeline.events.length, 2);
|
||||
expect(timeline.events[0].eventId, "1");
|
||||
expect(timeline.events[0].sender.id, "@alice:example.com");
|
||||
expect(timeline.events[0].time.toTimeStamp(), testTimeStamp);
|
||||
expect(timeline.events[0].time.millisecondsSinceEpoch, testTimeStamp);
|
||||
expect(timeline.events[0].getBody(), "Testcase");
|
||||
expect(timeline.events[0].time > timeline.events[1].time, true);
|
||||
expect(
|
||||
timeline.events[0].time.millisecondsSinceEpoch >
|
||||
timeline.events[1].time.millisecondsSinceEpoch,
|
||||
true);
|
||||
expect(timeline.events[0].receipts, []);
|
||||
|
||||
room.roomAccountData["m.receipt"] = RoomAccountData.fromJson({
|
||||
|
@ -112,7 +114,7 @@ void main() {
|
|||
expect(timeline.events[0].receipts.length, 1);
|
||||
expect(timeline.events[0].receipts[0].user.id, "@alice:example.com");
|
||||
|
||||
client.connection.onEvent.add(EventUpdate(
|
||||
client.onEvent.add(EventUpdate(
|
||||
type: "timeline",
|
||||
roomID: roomID,
|
||||
eventType: "m.room.redaction",
|
||||
|
@ -145,7 +147,7 @@ void main() {
|
|||
expect(timeline.events[0].eventId, "42");
|
||||
expect(timeline.events[0].status, 1);
|
||||
|
||||
client.connection.onEvent.add(EventUpdate(
|
||||
client.onEvent.add(EventUpdate(
|
||||
type: "timeline",
|
||||
roomID: roomID,
|
||||
eventType: "m.room.message",
|
||||
|
@ -169,7 +171,7 @@ void main() {
|
|||
});
|
||||
|
||||
test("Send message with error", () async {
|
||||
client.connection.onEvent.add(EventUpdate(
|
||||
client.onEvent.add(EventUpdate(
|
||||
type: "timeline",
|
||||
roomID: roomID,
|
||||
eventType: "m.room.message",
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
* along with famedlysdk. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'package:famedlysdk/src/RoomState.dart';
|
||||
import 'package:famedlysdk/src/Event.dart';
|
||||
import 'package:famedlysdk/src/User.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
|
@ -49,7 +49,7 @@ void main() {
|
|||
"state_key": id
|
||||
};
|
||||
|
||||
User user = RoomState.fromJson(jsonObj, null).asUser;
|
||||
User user = Event.fromJson(jsonObj, null).asUser;
|
||||
|
||||
expect(user.id, id);
|
||||
expect(user.membership, membership);
|
||||
|
|
Loading…
Reference in a new issue