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:
|
3. Connect to a Matrix Homeserver and listen to the streams:
|
||||||
|
|
||||||
```dart
|
```dart
|
||||||
matrix.connection.onLoginStateChanged.stream.listen((bool loginState){
|
matrix.onLoginStateChanged.stream.listen((bool loginState){
|
||||||
print("LoginState: ${loginState.toString()}");
|
print("LoginState: ${loginState.toString()}");
|
||||||
});
|
});
|
||||||
|
|
||||||
matrix.connection.onEvent.stream.listen((EventUpdate eventUpdate){
|
matrix.onEvent.stream.listen((EventUpdate eventUpdate){
|
||||||
print("New event update!");
|
print("New event update!");
|
||||||
});
|
});
|
||||||
|
|
||||||
matrix.connection.onRoomUpdate.stream.listen((RoomUpdate eventUpdate){
|
matrix.onRoomUpdate.stream.listen((RoomUpdate eventUpdate){
|
||||||
print("New room update!");
|
print("New room update!");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -59,7 +59,7 @@ final bool loginValid = await matrix.login("username", "password");
|
||||||
4. Send a message to a Room:
|
4. Send a message to a Room:
|
||||||
|
|
||||||
```dart
|
```dart
|
||||||
final resp = await matrix.connection.jsonRequest(
|
final resp = await matrix.jsonRequest(
|
||||||
type: "PUT",
|
type: "PUT",
|
||||||
action: "/r0/rooms/!fjd823j:example.com/send/m.room.message/$txnId",
|
action: "/r0/rooms/!fjd823j:example.com/send/m.room.message/$txnId",
|
||||||
data: {
|
data: {
|
||||||
|
|
|
@ -23,25 +23,21 @@
|
||||||
|
|
||||||
library famedlysdk;
|
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/RoomUpdate.dart';
|
||||||
export 'package:famedlysdk/src/sync/EventUpdate.dart';
|
export 'package:famedlysdk/src/sync/EventUpdate.dart';
|
||||||
export 'package:famedlysdk/src/sync/UserUpdate.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/MatrixException.dart';
|
||||||
export 'package:famedlysdk/src/utils/MatrixFile.dart';
|
export 'package:famedlysdk/src/utils/MatrixFile.dart';
|
||||||
export 'package:famedlysdk/src/utils/MxContent.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/utils/StatesMap.dart';
|
||||||
export 'package:famedlysdk/src/AccountData.dart';
|
export 'package:famedlysdk/src/AccountData.dart';
|
||||||
export 'package:famedlysdk/src/Client.dart';
|
export 'package:famedlysdk/src/Client.dart';
|
||||||
export 'package:famedlysdk/src/Connection.dart';
|
|
||||||
export 'package:famedlysdk/src/Event.dart';
|
export 'package:famedlysdk/src/Event.dart';
|
||||||
export 'package:famedlysdk/src/Presence.dart';
|
export 'package:famedlysdk/src/Presence.dart';
|
||||||
export 'package:famedlysdk/src/Room.dart';
|
export 'package:famedlysdk/src/Room.dart';
|
||||||
export 'package:famedlysdk/src/RoomAccountData.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/StoreAPI.dart';
|
||||||
export 'package:famedlysdk/src/Timeline.dart';
|
export 'package:famedlysdk/src/Timeline.dart';
|
||||||
export 'package:famedlysdk/src/User.dart';
|
export 'package:famedlysdk/src/User.dart';
|
||||||
|
|
|
@ -21,8 +21,9 @@
|
||||||
* along with famedlysdk. If not, see <http://www.gnu.org/licenses/>.
|
* along with famedlysdk. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import 'package:famedlysdk/src/RoomState.dart';
|
import 'package:famedlysdk/famedlysdk.dart';
|
||||||
|
|
||||||
|
/// The global private data created by this user.
|
||||||
class AccountData {
|
class AccountData {
|
||||||
/// The json payload of the content. The content highly depends on the type.
|
/// The json payload of the content. The content highly depends on the type.
|
||||||
final Map<String, dynamic> content;
|
final Map<String, dynamic> content;
|
||||||
|
@ -35,7 +36,7 @@ class AccountData {
|
||||||
/// Get a State event from a table row or from the event stream.
|
/// Get a State event from a table row or from the event stream.
|
||||||
factory AccountData.fromJson(Map<String, dynamic> jsonPayload) {
|
factory AccountData.fromJson(Map<String, dynamic> jsonPayload) {
|
||||||
final Map<String, dynamic> content =
|
final Map<String, dynamic> content =
|
||||||
RoomState.getMapFromPayload(jsonPayload['content']);
|
Event.getMapFromPayload(jsonPayload['content']);
|
||||||
return AccountData(content: content, typeKey: jsonPayload['type']);
|
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/StoreAPI.dart';
|
||||||
import 'package:famedlysdk/src/sync/UserUpdate.dart';
|
import 'package:famedlysdk/src/sync/UserUpdate.dart';
|
||||||
import 'package:famedlysdk/src/utils/MatrixFile.dart';
|
import 'package:famedlysdk/src/utils/MatrixFile.dart';
|
||||||
|
|
||||||
import 'Connection.dart';
|
|
||||||
import 'Room.dart';
|
import 'Room.dart';
|
||||||
import 'RoomList.dart';
|
import 'Event.dart';
|
||||||
//import 'Store.dart';
|
|
||||||
import 'RoomState.dart';
|
|
||||||
import 'User.dart';
|
import 'User.dart';
|
||||||
import 'requests/SetPushersRequest.dart';
|
|
||||||
import 'responses/PushrulesResponse.dart';
|
|
||||||
import 'utils/Profile.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 AccountDataEventCB = void Function(AccountData accountData);
|
||||||
typedef PresenceCB = void Function(Presence presence);
|
typedef PresenceCB = void Function(Presence presence);
|
||||||
|
|
||||||
|
enum HTTPType { GET, POST, PUT, DELETE }
|
||||||
|
|
||||||
|
enum LoginState { logged, loggedOut }
|
||||||
|
|
||||||
/// Represents a Matrix client to communicate with a
|
/// Represents a Matrix client to communicate with a
|
||||||
/// [Matrix](https://matrix.org) homeserver and is the entry point for this
|
/// [Matrix](https://matrix.org) homeserver and is the entry point for this
|
||||||
/// SDK.
|
/// SDK.
|
||||||
class Client {
|
class Client {
|
||||||
/// Handles the connection for this client.
|
/// Handles the connection for this client.
|
||||||
Connection connection;
|
@deprecated
|
||||||
|
Client get connection => this;
|
||||||
|
|
||||||
/// Optional persistent store for all data.
|
/// Optional persistent store for all data.
|
||||||
StoreAPI store;
|
StoreAPI store;
|
||||||
|
|
||||||
Client(this.clientName, {this.debug = false, this.store}) {
|
Client(this.clientName, {this.debug = false, this.store}) {
|
||||||
connection = Connection(this);
|
|
||||||
|
|
||||||
if (this.clientName != "testclient") store = null; //Store(this);
|
if (this.clientName != "testclient") store = null; //Store(this);
|
||||||
connection.onLoginStateChanged.stream.listen((loginState) {
|
this.onLoginStateChanged.stream.listen((loginState) {
|
||||||
print("LoginState: ${loginState.toString()}");
|
print("LoginState: ${loginState.toString()}");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -70,35 +75,43 @@ class Client {
|
||||||
final String clientName;
|
final String clientName;
|
||||||
|
|
||||||
/// The homeserver this client is communicating with.
|
/// The homeserver this client is communicating with.
|
||||||
String homeserver;
|
String get homeserver => _homeserver;
|
||||||
|
String _homeserver;
|
||||||
|
|
||||||
/// The Matrix ID of the current logged user.
|
/// 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
|
/// This is the access token for the matrix client. When it is undefined, then
|
||||||
/// the user needs to sign in first.
|
/// the user needs to sign in first.
|
||||||
String accessToken;
|
String get accessToken => _accessToken;
|
||||||
|
String _accessToken;
|
||||||
|
|
||||||
/// This points to the position in the synchronization history.
|
/// This points to the position in the synchronization history.
|
||||||
String prevBatch;
|
String prevBatch;
|
||||||
|
|
||||||
/// The device ID is an unique identifier for this device.
|
/// 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.
|
/// 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?
|
/// 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.
|
/// Wheither the server supports lazy load members.
|
||||||
bool lazyLoadMembers = false;
|
bool get lazyLoadMembers => _lazyLoadMembers;
|
||||||
|
bool _lazyLoadMembers = false;
|
||||||
|
|
||||||
/// Returns the current login state.
|
/// Returns the current login state.
|
||||||
bool isLogged() => accessToken != null;
|
bool isLogged() => accessToken != null;
|
||||||
|
|
||||||
/// A list of all rooms the user is participating or invited.
|
/// 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.
|
/// Key/Value store of account data.
|
||||||
Map<String, AccountData> accountData = {};
|
Map<String, AccountData> accountData = {};
|
||||||
|
@ -112,6 +125,20 @@ class Client {
|
||||||
/// Callback will be called on presences.
|
/// Callback will be called on presences.
|
||||||
PresenceCB onPresence;
|
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) {
|
void handleUserUpdate(UserUpdate userUpdate) {
|
||||||
if (userUpdate.type == "account_data") {
|
if (userUpdate.type == "account_data") {
|
||||||
AccountData newAccountData = AccountData.fromJson(userUpdate.content);
|
AccountData newAccountData = AccountData.fromJson(userUpdate.content);
|
||||||
|
@ -134,21 +161,21 @@ class Client {
|
||||||
if (accountData["m.direct"] != null &&
|
if (accountData["m.direct"] != null &&
|
||||||
accountData["m.direct"].content[userId] is List<dynamic> &&
|
accountData["m.direct"].content[userId] is List<dynamic> &&
|
||||||
accountData["m.direct"].content[userId].length > 0) {
|
accountData["m.direct"].content[userId].length > 0) {
|
||||||
if (roomList.getRoomById(accountData["m.direct"].content[userId][0]) !=
|
if (getRoomById(accountData["m.direct"].content[userId][0]) != null)
|
||||||
null) return accountData["m.direct"].content[userId][0];
|
return accountData["m.direct"].content[userId][0];
|
||||||
(accountData["m.direct"].content[userId] as List<dynamic>)
|
(accountData["m.direct"].content[userId] as List<dynamic>)
|
||||||
.remove(accountData["m.direct"].content[userId][0]);
|
.remove(accountData["m.direct"].content[userId][0]);
|
||||||
connection.jsonRequest(
|
this.jsonRequest(
|
||||||
type: HTTPType.PUT,
|
type: HTTPType.PUT,
|
||||||
action: "/client/r0/user/${userID}/account_data/m.direct",
|
action: "/client/r0/user/${userID}/account_data/m.direct",
|
||||||
data: directChats);
|
data: directChats);
|
||||||
return getDirectChatFromUserId(userId);
|
return getDirectChatFromUserId(userId);
|
||||||
}
|
}
|
||||||
for (int i = 0; i < roomList.rooms.length; i++)
|
for (int i = 0; i < this.rooms.length; i++)
|
||||||
if (roomList.rooms[i].membership == Membership.invite &&
|
if (this.rooms[i].membership == Membership.invite &&
|
||||||
roomList.rooms[i].states[userID]?.senderId == userId &&
|
this.rooms[i].states[userID]?.senderId == userId &&
|
||||||
roomList.rooms[i].states[userID].content["is_direct"] == true)
|
this.rooms[i].states[userID].content["is_direct"] == true)
|
||||||
return roomList.rooms[i].id;
|
return this.rooms[i].id;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -158,9 +185,9 @@ class Client {
|
||||||
/// Throws FormatException, TimeoutException and MatrixException on error.
|
/// Throws FormatException, TimeoutException and MatrixException on error.
|
||||||
Future<bool> checkServer(serverUrl) async {
|
Future<bool> checkServer(serverUrl) async {
|
||||||
try {
|
try {
|
||||||
homeserver = serverUrl;
|
_homeserver = serverUrl;
|
||||||
final versionResp = await connection.jsonRequest(
|
final versionResp = await this
|
||||||
type: HTTPType.GET, action: "/client/versions");
|
.jsonRequest(type: HTTPType.GET, action: "/client/versions");
|
||||||
|
|
||||||
final List<String> versions = List<String>.from(versionResp["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") &&
|
if (versionResp.containsKey("unstable_features") &&
|
||||||
versionResp["unstable_features"].containsKey("m.lazy_load_members")) {
|
versionResp["unstable_features"].containsKey("m.lazy_load_members")) {
|
||||||
lazyLoadMembers = versionResp["unstable_features"]
|
_lazyLoadMembers = versionResp["unstable_features"]
|
||||||
["m.lazy_load_members"]
|
["m.lazy_load_members"]
|
||||||
? true
|
? true
|
||||||
: false;
|
: false;
|
||||||
}
|
}
|
||||||
|
|
||||||
final loginResp = await connection.jsonRequest(
|
final loginResp = await this
|
||||||
type: HTTPType.GET, action: "/client/r0/login");
|
.jsonRequest(type: HTTPType.GET, action: "/client/r0/login");
|
||||||
|
|
||||||
final List<dynamic> flows = loginResp["flows"];
|
final List<dynamic> flows = loginResp["flows"];
|
||||||
|
|
||||||
|
@ -197,7 +224,7 @@ class Client {
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
this.homeserver = this.matrixVersions = null;
|
this._homeserver = this._matrixVersions = null;
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -206,17 +233,19 @@ class Client {
|
||||||
/// authentication. Returns false if the login was not successful. Throws
|
/// authentication. Returns false if the login was not successful. Throws
|
||||||
/// MatrixException if login was not successful.
|
/// MatrixException if login was not successful.
|
||||||
Future<bool> login(String username, String password) async {
|
Future<bool> login(String username, String password) async {
|
||||||
final loginResp = await connection
|
final loginResp = await jsonRequest(
|
||||||
.jsonRequest(type: HTTPType.POST, action: "/client/r0/login", data: {
|
type: HTTPType.POST,
|
||||||
"type": "m.login.password",
|
action: "/client/r0/login",
|
||||||
"user": username,
|
data: {
|
||||||
"identifier": {
|
"type": "m.login.password",
|
||||||
"type": "m.id.user",
|
"user": username,
|
||||||
"user": username,
|
"identifier": {
|
||||||
},
|
"type": "m.id.user",
|
||||||
"password": password,
|
"user": username,
|
||||||
"initial_device_display_name": "Famedly Talk"
|
},
|
||||||
});
|
"password": password,
|
||||||
|
"initial_device_display_name": "Famedly Talk"
|
||||||
|
});
|
||||||
|
|
||||||
final userID = loginResp["user_id"];
|
final userID = loginResp["user_id"];
|
||||||
final accessToken = loginResp["access_token"];
|
final accessToken = loginResp["access_token"];
|
||||||
|
@ -224,7 +253,7 @@ class Client {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
await connection.connect(
|
await this.connect(
|
||||||
newToken: accessToken,
|
newToken: accessToken,
|
||||||
newUserID: userID,
|
newUserID: userID,
|
||||||
newHomeserver: homeserver,
|
newHomeserver: homeserver,
|
||||||
|
@ -239,12 +268,11 @@ class Client {
|
||||||
/// including all persistent data from the store.
|
/// including all persistent data from the store.
|
||||||
Future<void> logout() async {
|
Future<void> logout() async {
|
||||||
try {
|
try {
|
||||||
await connection.jsonRequest(
|
await this.jsonRequest(type: HTTPType.POST, action: "/client/r0/logout");
|
||||||
type: HTTPType.POST, action: "/client/r0/logout");
|
|
||||||
} catch (exception) {
|
} catch (exception) {
|
||||||
rethrow;
|
rethrow;
|
||||||
} finally {
|
} 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
|
/// fetch the user's own profile information or other users; either locally
|
||||||
/// or on remote homeservers.
|
/// or on remote homeservers.
|
||||||
Future<Profile> getProfileFromUserId(String userId) async {
|
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}");
|
type: HTTPType.GET, action: "/client/r0/profile/${userId}");
|
||||||
return Profile.fromJson(resp);
|
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 {
|
Future<List<Room>> get archive async {
|
||||||
List<Room> archiveList = [];
|
List<Room> archiveList = [];
|
||||||
String syncFilters =
|
String syncFilters =
|
||||||
'{"room":{"include_leave":true,"timeline":{"limit":10}}}';
|
'{"room":{"include_leave":true,"timeline":{"limit":10}}}';
|
||||||
String action = "/client/r0/sync?filter=$syncFilters&timeout=0";
|
String action = "/client/r0/sync?filter=$syncFilters&timeout=0";
|
||||||
final sync =
|
final sync = await this.jsonRequest(type: HTTPType.GET, action: action);
|
||||||
await connection.jsonRequest(type: HTTPType.GET, action: action);
|
|
||||||
if (sync["rooms"]["leave"] is Map<String, dynamic>) {
|
if (sync["rooms"]["leave"] is Map<String, dynamic>) {
|
||||||
for (var entry in sync["rooms"]["leave"].entries) {
|
for (var entry in sync["rooms"]["leave"].entries) {
|
||||||
final String id = entry.key;
|
final String id = entry.key;
|
||||||
|
@ -301,13 +313,13 @@ class Client {
|
||||||
if (room["timeline"] is Map<String, dynamic> &&
|
if (room["timeline"] is Map<String, dynamic> &&
|
||||||
room["timeline"]["events"] is List<dynamic>) {
|
room["timeline"]["events"] is List<dynamic>) {
|
||||||
for (dynamic event in room["timeline"]["events"]) {
|
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> &&
|
if (room["state"] is Map<String, dynamic> &&
|
||||||
room["state"]["events"] is List<dynamic>) {
|
room["state"]["events"] is List<dynamic>) {
|
||||||
for (dynamic event in room["state"]["events"]) {
|
for (dynamic event in room["state"]["events"]) {
|
||||||
leftRoom.setState(RoomState.fromJson(event, leftRoom));
|
leftRoom.setState(Event.fromJson(event, leftRoom));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
archiveList.add(leftRoom);
|
archiveList.add(leftRoom);
|
||||||
|
@ -316,12 +328,9 @@ class Client {
|
||||||
return archiveList;
|
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 {
|
Future<dynamic> joinRoomById(String id) async {
|
||||||
return await connection.jsonRequest(
|
return await this
|
||||||
type: HTTPType.POST, action: "/client/r0/join/$id");
|
.jsonRequest(type: HTTPType.POST, action: "/client/r0/join/$id");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Loads the contact list for this user excluding the user itself.
|
/// 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.
|
/// defined by the autojoin room feature in Synapse.
|
||||||
Future<List<User>> loadFamedlyContacts() async {
|
Future<List<User>> loadFamedlyContacts() async {
|
||||||
List<User> contacts = [];
|
List<User> contacts = [];
|
||||||
Room contactDiscoveryRoom = roomList
|
Room contactDiscoveryRoom =
|
||||||
.getRoomByAlias("#famedlyContactDiscovery:${userID.split(":")[1]}");
|
this.getRoomByAlias("#famedlyContactDiscovery:${userID.split(":")[1]}");
|
||||||
if (contactDiscoveryRoom != null)
|
if (contactDiscoveryRoom != null)
|
||||||
contacts = await contactDiscoveryRoom.requestParticipants();
|
contacts = await contactDiscoveryRoom.requestParticipants();
|
||||||
else {
|
else {
|
||||||
Map<String, bool> userMap = {};
|
Map<String, bool> userMap = {};
|
||||||
for (int i = 0; i < roomList.rooms.length; i++) {
|
for (int i = 0; i < this.rooms.length; i++) {
|
||||||
List<User> roomUsers = roomList.rooms[i].getParticipants();
|
List<User> roomUsers = this.rooms[i].getParticipants();
|
||||||
for (int j = 0; j < roomUsers.length; j++) {
|
for (int j = 0; j < roomUsers.length; j++) {
|
||||||
if (userMap[roomUsers[j].id] != true) contacts.add(roomUsers[j]);
|
if (userMap[roomUsers[j].id] != true) contacts.add(roomUsers[j]);
|
||||||
userMap[roomUsers[j].id] = true;
|
userMap[roomUsers[j].id] = true;
|
||||||
|
@ -361,7 +370,7 @@ class Client {
|
||||||
for (int i = 0; i < invite.length; i++) inviteIDs.add(invite[i].id);
|
for (int i = 0; i < invite.length; i++) inviteIDs.add(invite[i].id);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final dynamic resp = await connection.jsonRequest(
|
final dynamic resp = await this.jsonRequest(
|
||||||
type: HTTPType.POST,
|
type: HTTPType.POST,
|
||||||
action: "/client/r0/createRoom",
|
action: "/client/r0/createRoom",
|
||||||
data: params == null
|
data: params == null
|
||||||
|
@ -377,8 +386,8 @@ class Client {
|
||||||
|
|
||||||
/// Uploads a new user avatar for this user.
|
/// Uploads a new user avatar for this user.
|
||||||
Future<void> setAvatar(MatrixFile file) async {
|
Future<void> setAvatar(MatrixFile file) async {
|
||||||
final uploadResp = await connection.upload(file);
|
final uploadResp = await this.upload(file);
|
||||||
await connection.jsonRequest(
|
await this.jsonRequest(
|
||||||
type: HTTPType.PUT,
|
type: HTTPType.PUT,
|
||||||
action: "/client/r0/profile/$userID/avatar_url",
|
action: "/client/r0/profile/$userID/avatar_url",
|
||||||
data: {"avatar_url": uploadResp});
|
data: {"avatar_url": uploadResp});
|
||||||
|
@ -387,22 +396,584 @@ class Client {
|
||||||
|
|
||||||
/// Fetches the pushrules for the logged in user.
|
/// Fetches the pushrules for the logged in user.
|
||||||
/// These are needed for notifications on Android
|
/// These are needed for notifications on Android
|
||||||
Future<PushrulesResponse> getPushrules() async {
|
Future<PushRules> getPushrules() async {
|
||||||
final dynamic resp = await connection.jsonRequest(
|
final dynamic resp = await this.jsonRequest(
|
||||||
type: HTTPType.GET,
|
type: HTTPType.GET,
|
||||||
action: "/client/r0/pushrules/",
|
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.
|
/// This endpoint allows the creation, modification and deletion of pushers for this user ID.
|
||||||
Future<void> setPushers(SetPushersRequest data) async {
|
Future<void> setPushers(String pushKey, String kind, String appId,
|
||||||
await connection.jsonRequest(
|
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,
|
type: HTTPType.POST,
|
||||||
action: "/client/r0/pushers/set",
|
action: "/client/r0/pushers/set",
|
||||||
data: data.toJson(),
|
data: data,
|
||||||
);
|
);
|
||||||
return;
|
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/>.
|
* along with famedlysdk. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import 'package:famedlysdk/src/RoomState.dart';
|
import 'dart:convert';
|
||||||
import 'package:famedlysdk/src/sync/EventUpdate.dart';
|
import 'package:famedlysdk/famedlysdk.dart';
|
||||||
import 'package:famedlysdk/src/utils/ChatTime.dart';
|
|
||||||
import 'package:famedlysdk/src/utils/Receipt.dart';
|
import 'package:famedlysdk/src/utils/Receipt.dart';
|
||||||
|
|
||||||
import './Room.dart';
|
import './Room.dart';
|
||||||
|
|
||||||
/// Defines a timeline event for a room.
|
class Event {
|
||||||
class Event extends 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 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.
|
/// The status of this event.
|
||||||
/// -1=ERROR
|
/// -1=ERROR
|
||||||
/// 0=SENDING
|
/// 0=SENDING
|
||||||
/// 1=SENT
|
/// 1=SENT
|
||||||
/// 2=RECEIVED
|
/// 2=TIMELINE
|
||||||
|
/// 3=ROOM_STATE
|
||||||
int status;
|
int status;
|
||||||
|
|
||||||
static const int defaultStatus = 2;
|
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(
|
Event(
|
||||||
{this.status = defaultStatus,
|
{this.status = defaultStatus,
|
||||||
dynamic content,
|
this.content,
|
||||||
String typeKey,
|
this.typeKey,
|
||||||
String eventId,
|
this.eventId,
|
||||||
String roomId,
|
this.roomId,
|
||||||
String senderId,
|
this.senderId,
|
||||||
ChatTime time,
|
this.time,
|
||||||
dynamic unsigned,
|
this.unsigned,
|
||||||
dynamic prevContent,
|
this.prevContent,
|
||||||
String stateKey,
|
this.stateKey,
|
||||||
Room room,
|
this.room});
|
||||||
Event redactedBecause})
|
|
||||||
: super(
|
static Map<String, dynamic> getMapFromPayload(dynamic payload) {
|
||||||
content: content,
|
if (payload is String)
|
||||||
typeKey: typeKey,
|
try {
|
||||||
eventId: eventId,
|
return json.decode(payload);
|
||||||
roomId: roomId,
|
} catch (e) {
|
||||||
senderId: senderId,
|
return {};
|
||||||
time: time,
|
}
|
||||||
unsigned: unsigned,
|
if (payload is Map<String, dynamic>) return payload;
|
||||||
prevContent: prevContent,
|
return {};
|
||||||
stateKey: stateKey,
|
}
|
||||||
room: room);
|
|
||||||
|
|
||||||
/// Get a State event from a table row or from the event stream.
|
/// Get a State event from a table row or from the event stream.
|
||||||
factory Event.fromJson(Map<String, dynamic> jsonPayload, Room room) {
|
factory Event.fromJson(Map<String, dynamic> jsonPayload, Room room) {
|
||||||
final Map<String, dynamic> content =
|
final Map<String, dynamic> content =
|
||||||
RoomState.getMapFromPayload(jsonPayload['content']);
|
Event.getMapFromPayload(jsonPayload['content']);
|
||||||
final Map<String, dynamic> unsigned =
|
final Map<String, dynamic> unsigned =
|
||||||
RoomState.getMapFromPayload(jsonPayload['unsigned']);
|
Event.getMapFromPayload(jsonPayload['unsigned']);
|
||||||
final Map<String, dynamic> prevContent =
|
final Map<String, dynamic> prevContent =
|
||||||
RoomState.getMapFromPayload(jsonPayload['prev_content']);
|
Event.getMapFromPayload(jsonPayload['prev_content']);
|
||||||
Event redactedBecause = null;
|
|
||||||
if (unsigned.containsKey("redacted_because"))
|
|
||||||
redactedBecause = Event.fromJson(unsigned["redacted_because"], room);
|
|
||||||
return Event(
|
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,
|
content: content,
|
||||||
typeKey: jsonPayload['type'],
|
typeKey: typeKey,
|
||||||
eventId: jsonPayload['event_id'],
|
eventId: eventId,
|
||||||
roomId: jsonPayload['room_id'],
|
|
||||||
senderId: jsonPayload['sender'],
|
|
||||||
time: ChatTime(jsonPayload['origin_server_ts']),
|
|
||||||
unsigned: unsigned,
|
|
||||||
prevContent: prevContent,
|
|
||||||
stateKey: jsonPayload['state_key'],
|
|
||||||
room: room,
|
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.
|
/// Returns the body of this event if it has a body.
|
||||||
|
@ -110,8 +305,8 @@ class Event extends RoomState {
|
||||||
List<Receipt> receiptsList = [];
|
List<Receipt> receiptsList = [];
|
||||||
for (var entry in room.roomAccountData["m.receipt"].content.entries) {
|
for (var entry in room.roomAccountData["m.receipt"].content.entries) {
|
||||||
if (entry.value["event_id"] == eventId)
|
if (entry.value["event_id"] == eventId)
|
||||||
receiptsList.add(Receipt(
|
receiptsList.add(Receipt(room.getUserByMXIDSync(entry.key),
|
||||||
room.getUserByMXIDSync(entry.key), ChatTime(entry.value["ts"])));
|
DateTime.fromMillisecondsSinceEpoch(entry.value["ts"])));
|
||||||
}
|
}
|
||||||
return receiptsList;
|
return receiptsList;
|
||||||
}
|
}
|
||||||
|
@ -123,7 +318,7 @@ class Event extends RoomState {
|
||||||
if (room.client.store != null)
|
if (room.client.store != null)
|
||||||
await room.client.store.removeEvent(eventId);
|
await room.client.store.removeEvent(eventId);
|
||||||
|
|
||||||
room.client.connection.onEvent.add(EventUpdate(
|
room.client.onEvent.add(EventUpdate(
|
||||||
roomID: room.id,
|
roomID: room.id,
|
||||||
type: "timeline",
|
type: "timeline",
|
||||||
eventType: typeKey,
|
eventType: typeKey,
|
||||||
|
@ -152,3 +347,28 @@ class Event extends RoomState {
|
||||||
Future<dynamic> redact({String reason, String txid}) =>
|
Future<dynamic> redact({String reason, String txid}) =>
|
||||||
room.redactEvent(eventId, reason: reason, txid: 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)
|
Presence.fromJson(Map<String, dynamic> json)
|
||||||
: sender = json['sender'],
|
: sender = json['sender'],
|
||||||
displayname = json['content']['avatar_url'],
|
displayname = json['content']['displayname'],
|
||||||
avatarUrl = MxContent(json['content']['avatar_url']),
|
avatarUrl = MxContent(json['content']['avatar_url'] ?? ""),
|
||||||
currentlyActive = json['content']['currently_active'],
|
currentlyActive = json['content']['currently_active'],
|
||||||
lastActiveAgo = json['content']['last_active_ago'],
|
lastActiveAgo = json['content']['last_active_ago'],
|
||||||
presence = PresenceType.values.firstWhere(
|
presence = PresenceType.values.firstWhere(
|
||||||
|
|
|
@ -24,10 +24,8 @@
|
||||||
import 'package:famedlysdk/src/Client.dart';
|
import 'package:famedlysdk/src/Client.dart';
|
||||||
import 'package:famedlysdk/src/Event.dart';
|
import 'package:famedlysdk/src/Event.dart';
|
||||||
import 'package:famedlysdk/src/RoomAccountData.dart';
|
import 'package:famedlysdk/src/RoomAccountData.dart';
|
||||||
import 'package:famedlysdk/src/RoomState.dart';
|
|
||||||
import 'package:famedlysdk/src/sync/EventUpdate.dart';
|
import 'package:famedlysdk/src/sync/EventUpdate.dart';
|
||||||
import 'package:famedlysdk/src/sync/RoomUpdate.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/MatrixException.dart';
|
||||||
import 'package:famedlysdk/src/utils/MatrixFile.dart';
|
import 'package:famedlysdk/src/utils/MatrixFile.dart';
|
||||||
import 'package:famedlysdk/src/utils/MxContent.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 'package:mime_type/mime_type.dart';
|
||||||
|
|
||||||
import './User.dart';
|
import './User.dart';
|
||||||
import 'Connection.dart';
|
|
||||||
import 'Timeline.dart';
|
import 'Timeline.dart';
|
||||||
import 'utils/StatesMap.dart';
|
import 'utils/StatesMap.dart';
|
||||||
|
|
||||||
|
@ -76,14 +73,14 @@ class Room {
|
||||||
/// Key-Value store for private account data only visible for this user.
|
/// Key-Value store for private account data only visible for this user.
|
||||||
Map<String, RoomAccountData> roomAccountData = {};
|
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.
|
/// 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;
|
states.states[typeKey] != null ? states.states[typeKey][stateKey] : null;
|
||||||
|
|
||||||
/// Adds the [state] to this room and overwrites a state with the same
|
/// Adds the [state] to this room and overwrites a state with the same
|
||||||
/// typeKey/stateKey key pair if there is one.
|
/// typeKey/stateKey key pair if there is one.
|
||||||
void setState(RoomState state) {
|
void setState(Event state) {
|
||||||
if (!states.states.containsKey(state.typeKey))
|
if (!states.states.containsKey(state.typeKey))
|
||||||
states.states[state.typeKey] = {};
|
states.states[state.typeKey] = {};
|
||||||
states.states[state.typeKey][state.stateKey ?? ""] = state;
|
states.states[state.typeKey][state.stateKey ?? ""] = state;
|
||||||
|
@ -150,13 +147,15 @@ class Room {
|
||||||
String notificationSettings;
|
String notificationSettings;
|
||||||
|
|
||||||
Event get lastEvent {
|
Event get lastEvent {
|
||||||
ChatTime lastTime = ChatTime(0);
|
DateTime lastTime = DateTime.fromMillisecondsSinceEpoch(0);
|
||||||
Event lastEvent = getState("m.room.message")?.timelineEvent;
|
Event lastEvent = getState("m.room.message")?.timelineEvent;
|
||||||
if (lastEvent == null)
|
if (lastEvent == null)
|
||||||
states.forEach((final String key, final entry) {
|
states.forEach((final String key, final entry) {
|
||||||
if (!entry.containsKey("")) return;
|
if (!entry.containsKey("")) return;
|
||||||
final RoomState state = entry[""];
|
final Event state = entry[""];
|
||||||
if (state.time != null && state.time > lastTime) {
|
if (state.time != null &&
|
||||||
|
state.time.millisecondsSinceEpoch >
|
||||||
|
lastTime.millisecondsSinceEpoch) {
|
||||||
lastTime = state.time;
|
lastTime = state.time;
|
||||||
lastEvent = state.timelineEvent;
|
lastEvent = state.timelineEvent;
|
||||||
}
|
}
|
||||||
|
@ -211,7 +210,7 @@ class Room {
|
||||||
} else {
|
} else {
|
||||||
if (states["m.room.member"] is Map<String, dynamic>) {
|
if (states["m.room.member"] is Map<String, dynamic>) {
|
||||||
for (var entry in states["m.room.member"].entries) {
|
for (var entry in states["m.room.member"].entries) {
|
||||||
RoomState state = entry.value;
|
Event state = entry.value;
|
||||||
if (state.type == EventTypes.RoomMember &&
|
if (state.type == EventTypes.RoomMember &&
|
||||||
state.stateKey != client?.userID) heroes.add(state.stateKey);
|
state.stateKey != client?.userID) heroes.add(state.stateKey);
|
||||||
}
|
}
|
||||||
|
@ -241,17 +240,17 @@ class Room {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// When the last message received.
|
/// When the last message received.
|
||||||
ChatTime get timeCreated {
|
DateTime get timeCreated {
|
||||||
if (lastEvent != null)
|
if (lastEvent != null)
|
||||||
return lastEvent.time;
|
return lastEvent.time;
|
||||||
else
|
else
|
||||||
return ChatTime.now();
|
return DateTime.now();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Call the Matrix API to change the name of this room. Returns the event ID of the
|
/// Call the Matrix API to change the name of this room. Returns the event ID of the
|
||||||
/// new m.room.name event.
|
/// new m.room.name event.
|
||||||
Future<String> setName(String newName) async {
|
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,
|
type: HTTPType.PUT,
|
||||||
action: "/client/r0/rooms/${id}/state/m.room.name",
|
action: "/client/r0/rooms/${id}/state/m.room.name",
|
||||||
data: {"name": newName});
|
data: {"name": newName});
|
||||||
|
@ -260,7 +259,7 @@ class Room {
|
||||||
|
|
||||||
/// Call the Matrix API to change the topic of this room.
|
/// Call the Matrix API to change the topic of this room.
|
||||||
Future<String> setDescription(String newName) async {
|
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,
|
type: HTTPType.PUT,
|
||||||
action: "/client/r0/rooms/${id}/state/m.room.topic",
|
action: "/client/r0/rooms/${id}/state/m.room.topic",
|
||||||
data: {"topic": newName});
|
data: {"topic": newName});
|
||||||
|
@ -270,7 +269,7 @@ class Room {
|
||||||
Future<String> _sendRawEventNow(Map<String, dynamic> content,
|
Future<String> _sendRawEventNow(Map<String, dynamic> content,
|
||||||
{String txid = null}) async {
|
{String txid = null}) async {
|
||||||
if (txid == null) txid = "txid${DateTime.now().millisecondsSinceEpoch}";
|
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,
|
type: HTTPType.PUT,
|
||||||
action: "/client/r0/rooms/${id}/send/m.room.message/$txid",
|
action: "/client/r0/rooms/${id}/send/m.room.message/$txid",
|
||||||
data: content);
|
data: content);
|
||||||
|
@ -289,7 +288,7 @@ class Room {
|
||||||
if (msgType == "m.video") return sendAudioEvent(file);
|
if (msgType == "m.video") return sendAudioEvent(file);
|
||||||
String fileName = file.path.split("/").last;
|
String fileName = file.path.split("/").last;
|
||||||
|
|
||||||
final String uploadResp = await client.connection.upload(file);
|
final String uploadResp = await client.upload(file);
|
||||||
|
|
||||||
// Send event
|
// Send event
|
||||||
Map<String, dynamic> content = {
|
Map<String, dynamic> content = {
|
||||||
|
@ -308,7 +307,7 @@ class Room {
|
||||||
Future<String> sendAudioEvent(MatrixFile file,
|
Future<String> sendAudioEvent(MatrixFile file,
|
||||||
{String txid = null, int width, int height}) async {
|
{String txid = null, int width, int height}) async {
|
||||||
String fileName = file.path.split("/").last;
|
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 = {
|
Map<String, dynamic> content = {
|
||||||
"msgtype": "m.audio",
|
"msgtype": "m.audio",
|
||||||
"body": fileName,
|
"body": fileName,
|
||||||
|
@ -325,7 +324,7 @@ class Room {
|
||||||
Future<String> sendImageEvent(MatrixFile file,
|
Future<String> sendImageEvent(MatrixFile file,
|
||||||
{String txid = null, int width, int height}) async {
|
{String txid = null, int width, int height}) async {
|
||||||
String fileName = file.path.split("/").last;
|
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 = {
|
Map<String, dynamic> content = {
|
||||||
"msgtype": "m.image",
|
"msgtype": "m.image",
|
||||||
"body": fileName,
|
"body": fileName,
|
||||||
|
@ -349,7 +348,7 @@ class Room {
|
||||||
int thumbnailWidth,
|
int thumbnailWidth,
|
||||||
int thumbnailHeight}) async {
|
int thumbnailHeight}) async {
|
||||||
String fileName = file.path.split("/").last;
|
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 = {
|
Map<String, dynamic> content = {
|
||||||
"msgtype": "m.video",
|
"msgtype": "m.video",
|
||||||
"body": fileName,
|
"body": fileName,
|
||||||
|
@ -370,7 +369,7 @@ class Room {
|
||||||
}
|
}
|
||||||
if (thumbnail != null) {
|
if (thumbnail != null) {
|
||||||
String thumbnailName = file.path.split("/").last;
|
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_url"] = thumbnailUploadResp;
|
||||||
content["info"]["thumbnail_info"] = {
|
content["info"]["thumbnail_info"] = {
|
||||||
"size": thumbnail.size,
|
"size": thumbnail.size,
|
||||||
|
@ -408,7 +407,7 @@ class Room {
|
||||||
"origin_server_ts": now,
|
"origin_server_ts": now,
|
||||||
"content": content
|
"content": content
|
||||||
});
|
});
|
||||||
client.connection.onEvent.add(eventUpdate);
|
client.onEvent.add(eventUpdate);
|
||||||
await client.store?.transaction(() {
|
await client.store?.transaction(() {
|
||||||
client.store.storeEventUpdate(eventUpdate);
|
client.store.storeEventUpdate(eventUpdate);
|
||||||
return;
|
return;
|
||||||
|
@ -420,7 +419,7 @@ class Room {
|
||||||
eventUpdate.content["status"] = 1;
|
eventUpdate.content["status"] = 1;
|
||||||
eventUpdate.content["unsigned"] = {"transaction_id": messageID};
|
eventUpdate.content["unsigned"] = {"transaction_id": messageID};
|
||||||
eventUpdate.content["event_id"] = res;
|
eventUpdate.content["event_id"] = res;
|
||||||
client.connection.onEvent.add(eventUpdate);
|
client.onEvent.add(eventUpdate);
|
||||||
await client.store?.transaction(() {
|
await client.store?.transaction(() {
|
||||||
client.store.storeEventUpdate(eventUpdate);
|
client.store.storeEventUpdate(eventUpdate);
|
||||||
return;
|
return;
|
||||||
|
@ -430,7 +429,7 @@ class Room {
|
||||||
// On error, set status to -1
|
// On error, set status to -1
|
||||||
eventUpdate.content["status"] = -1;
|
eventUpdate.content["status"] = -1;
|
||||||
eventUpdate.content["unsigned"] = {"transaction_id": messageID};
|
eventUpdate.content["unsigned"] = {"transaction_id": messageID};
|
||||||
client.connection.onEvent.add(eventUpdate);
|
client.onEvent.add(eventUpdate);
|
||||||
await client.store?.transaction(() {
|
await client.store?.transaction(() {
|
||||||
client.store.storeEventUpdate(eventUpdate);
|
client.store.storeEventUpdate(eventUpdate);
|
||||||
return;
|
return;
|
||||||
|
@ -444,7 +443,7 @@ class Room {
|
||||||
/// automatically be set.
|
/// automatically be set.
|
||||||
Future<void> join() async {
|
Future<void> join() async {
|
||||||
try {
|
try {
|
||||||
await client.connection.jsonRequest(
|
await client.jsonRequest(
|
||||||
type: HTTPType.POST, action: "/client/r0/rooms/${id}/join");
|
type: HTTPType.POST, action: "/client/r0/rooms/${id}/join");
|
||||||
if (states.containsKey(client.userID) &&
|
if (states.containsKey(client.userID) &&
|
||||||
states[client.userID].content["is_direct"] is bool &&
|
states[client.userID].content["is_direct"] is bool &&
|
||||||
|
@ -453,7 +452,7 @@ class Room {
|
||||||
} on MatrixException catch (exception) {
|
} on MatrixException catch (exception) {
|
||||||
if (exception.errorMessage == "No known servers") {
|
if (exception.errorMessage == "No known servers") {
|
||||||
client.store?.forgetRoom(id);
|
client.store?.forgetRoom(id);
|
||||||
client.connection.onRoomUpdate.add(
|
client.onRoomUpdate.add(
|
||||||
RoomUpdate(
|
RoomUpdate(
|
||||||
id: id,
|
id: id,
|
||||||
membership: Membership.leave,
|
membership: Membership.leave,
|
||||||
|
@ -469,7 +468,7 @@ class Room {
|
||||||
/// chat, this will be removed too.
|
/// chat, this will be removed too.
|
||||||
Future<void> leave() async {
|
Future<void> leave() async {
|
||||||
if (directChatMatrixID != "") await removeFromDirectChat();
|
if (directChatMatrixID != "") await removeFromDirectChat();
|
||||||
await client.connection.jsonRequest(
|
await client.jsonRequest(
|
||||||
type: HTTPType.POST, action: "/client/r0/rooms/${id}/leave");
|
type: HTTPType.POST, action: "/client/r0/rooms/${id}/leave");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -477,14 +476,14 @@ class Room {
|
||||||
/// Call the Matrix API to forget this room if you already left it.
|
/// Call the Matrix API to forget this room if you already left it.
|
||||||
Future<void> forget() async {
|
Future<void> forget() async {
|
||||||
client.store.forgetRoom(id);
|
client.store.forgetRoom(id);
|
||||||
await client.connection.jsonRequest(
|
await client.jsonRequest(
|
||||||
type: HTTPType.POST, action: "/client/r0/rooms/${id}/forget");
|
type: HTTPType.POST, action: "/client/r0/rooms/${id}/forget");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Call the Matrix API to kick a user from this room.
|
/// Call the Matrix API to kick a user from this room.
|
||||||
Future<void> kick(String userID) async {
|
Future<void> kick(String userID) async {
|
||||||
await client.connection.jsonRequest(
|
await client.jsonRequest(
|
||||||
type: HTTPType.POST,
|
type: HTTPType.POST,
|
||||||
action: "/client/r0/rooms/${id}/kick",
|
action: "/client/r0/rooms/${id}/kick",
|
||||||
data: {"user_id": userID});
|
data: {"user_id": userID});
|
||||||
|
@ -493,7 +492,7 @@ class Room {
|
||||||
|
|
||||||
/// Call the Matrix API to ban a user from this room.
|
/// Call the Matrix API to ban a user from this room.
|
||||||
Future<void> ban(String userID) async {
|
Future<void> ban(String userID) async {
|
||||||
await client.connection.jsonRequest(
|
await client.jsonRequest(
|
||||||
type: HTTPType.POST,
|
type: HTTPType.POST,
|
||||||
action: "/client/r0/rooms/${id}/ban",
|
action: "/client/r0/rooms/${id}/ban",
|
||||||
data: {"user_id": userID});
|
data: {"user_id": userID});
|
||||||
|
@ -502,7 +501,7 @@ class Room {
|
||||||
|
|
||||||
/// Call the Matrix API to unban a banned user from this room.
|
/// Call the Matrix API to unban a banned user from this room.
|
||||||
Future<void> unban(String userID) async {
|
Future<void> unban(String userID) async {
|
||||||
await client.connection.jsonRequest(
|
await client.jsonRequest(
|
||||||
type: HTTPType.POST,
|
type: HTTPType.POST,
|
||||||
action: "/client/r0/rooms/${id}/unban",
|
action: "/client/r0/rooms/${id}/unban",
|
||||||
data: {"user_id": userID});
|
data: {"user_id": userID});
|
||||||
|
@ -519,7 +518,7 @@ class Room {
|
||||||
if (powerMap["users"] == null) powerMap["users"] = {};
|
if (powerMap["users"] == null) powerMap["users"] = {};
|
||||||
powerMap["users"][userID] = power;
|
powerMap["users"][userID] = power;
|
||||||
|
|
||||||
final Map<String, dynamic> resp = await client.connection.jsonRequest(
|
final Map<String, dynamic> resp = await client.jsonRequest(
|
||||||
type: HTTPType.PUT,
|
type: HTTPType.PUT,
|
||||||
action: "/client/r0/rooms/$id/state/m.room.power_levels",
|
action: "/client/r0/rooms/$id/state/m.room.power_levels",
|
||||||
data: powerMap);
|
data: powerMap);
|
||||||
|
@ -528,7 +527,7 @@ class Room {
|
||||||
|
|
||||||
/// Call the Matrix API to invite a user to this room.
|
/// Call the Matrix API to invite a user to this room.
|
||||||
Future<void> invite(String userID) async {
|
Future<void> invite(String userID) async {
|
||||||
await client.connection.jsonRequest(
|
await client.jsonRequest(
|
||||||
type: HTTPType.POST,
|
type: HTTPType.POST,
|
||||||
action: "/client/r0/rooms/${id}/invite",
|
action: "/client/r0/rooms/${id}/invite",
|
||||||
data: {"user_id": userID});
|
data: {"user_id": userID});
|
||||||
|
@ -540,10 +539,10 @@ class Room {
|
||||||
/// the historical events will be published in the onEvent stream.
|
/// the historical events will be published in the onEvent stream.
|
||||||
Future<void> requestHistory(
|
Future<void> requestHistory(
|
||||||
{int historyCount = DefaultHistoryCount, onHistoryReceived}) async {
|
{int historyCount = DefaultHistoryCount, onHistoryReceived}) async {
|
||||||
final dynamic resp = await client.connection.jsonRequest(
|
final dynamic resp = await client.jsonRequest(
|
||||||
type: HTTPType.GET,
|
type: HTTPType.GET,
|
||||||
action:
|
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();
|
if (onHistoryReceived != null) onHistoryReceived();
|
||||||
prev_batch = resp["end"];
|
prev_batch = resp["end"];
|
||||||
|
@ -562,7 +561,7 @@ class Room {
|
||||||
eventType: resp["state"][i]["type"],
|
eventType: resp["state"][i]["type"],
|
||||||
content: resp["state"][i],
|
content: resp["state"][i],
|
||||||
);
|
);
|
||||||
client.connection.onEvent.add(eventUpdate);
|
client.onEvent.add(eventUpdate);
|
||||||
client.store.storeEventUpdate(eventUpdate);
|
client.store.storeEventUpdate(eventUpdate);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
@ -575,7 +574,7 @@ class Room {
|
||||||
eventType: resp["state"][i]["type"],
|
eventType: resp["state"][i]["type"],
|
||||||
content: resp["state"][i],
|
content: resp["state"][i],
|
||||||
);
|
);
|
||||||
client.connection.onEvent.add(eventUpdate);
|
client.onEvent.add(eventUpdate);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -589,7 +588,7 @@ class Room {
|
||||||
eventType: history[i]["type"],
|
eventType: history[i]["type"],
|
||||||
content: history[i],
|
content: history[i],
|
||||||
);
|
);
|
||||||
client.connection.onEvent.add(eventUpdate);
|
client.onEvent.add(eventUpdate);
|
||||||
client.store.storeEventUpdate(eventUpdate);
|
client.store.storeEventUpdate(eventUpdate);
|
||||||
client.store.txn.rawUpdate(
|
client.store.txn.rawUpdate(
|
||||||
"UPDATE Rooms SET prev_batch=? WHERE room_id=?", [resp["end"], id]);
|
"UPDATE Rooms SET prev_batch=? WHERE room_id=?", [resp["end"], id]);
|
||||||
|
@ -604,10 +603,10 @@ class Room {
|
||||||
eventType: history[i]["type"],
|
eventType: history[i]["type"],
|
||||||
content: history[i],
|
content: history[i],
|
||||||
);
|
);
|
||||||
client.connection.onEvent.add(eventUpdate);
|
client.onEvent.add(eventUpdate);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
client.connection.onRoomUpdate.add(
|
client.onRoomUpdate.add(
|
||||||
RoomUpdate(
|
RoomUpdate(
|
||||||
id: id,
|
id: id,
|
||||||
membership: membership,
|
membership: membership,
|
||||||
|
@ -628,7 +627,7 @@ class Room {
|
||||||
else
|
else
|
||||||
directChats[userID] = [id];
|
directChats[userID] = [id];
|
||||||
|
|
||||||
await client.connection.jsonRequest(
|
await client.jsonRequest(
|
||||||
type: HTTPType.PUT,
|
type: HTTPType.PUT,
|
||||||
action: "/client/r0/user/${client.userID}/account_data/m.direct",
|
action: "/client/r0/user/${client.userID}/account_data/m.direct",
|
||||||
data: directChats);
|
data: directChats);
|
||||||
|
@ -644,7 +643,7 @@ class Room {
|
||||||
else
|
else
|
||||||
return; // Nothing to do here
|
return; // Nothing to do here
|
||||||
|
|
||||||
await client.connection.jsonRequest(
|
await client.jsonRequest(
|
||||||
type: HTTPType.PUT,
|
type: HTTPType.PUT,
|
||||||
action: "/client/r0/user/${client.userID}/account_data/m.direct",
|
action: "/client/r0/user/${client.userID}/account_data/m.direct",
|
||||||
data: directChats);
|
data: directChats);
|
||||||
|
@ -655,7 +654,7 @@ class Room {
|
||||||
Future<void> sendReadReceipt(String eventID) async {
|
Future<void> sendReadReceipt(String eventID) async {
|
||||||
this.notificationCount = 0;
|
this.notificationCount = 0;
|
||||||
client?.store?.resetNotificationCount(this.id);
|
client?.store?.resetNotificationCount(this.id);
|
||||||
client.connection.jsonRequest(
|
client.jsonRequest(
|
||||||
type: HTTPType.POST,
|
type: HTTPType.POST,
|
||||||
action: "/client/r0/rooms/$id/read_markers",
|
action: "/client/r0/rooms/$id/read_markers",
|
||||||
data: {
|
data: {
|
||||||
|
@ -689,7 +688,7 @@ class Room {
|
||||||
if (states != null) {
|
if (states != null) {
|
||||||
List<Map<String, dynamic>> rawStates = await states;
|
List<Map<String, dynamic>> rawStates = await states;
|
||||||
for (int i = 0; i < rawStates.length; i++) {
|
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);
|
newRoom.setState(newState);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -740,7 +739,7 @@ class Room {
|
||||||
List<User> userList = [];
|
List<User> userList = [];
|
||||||
if (states["m.room.member"] is Map<String, dynamic>) {
|
if (states["m.room.member"] is Map<String, dynamic>) {
|
||||||
for (var entry in states["m.room.member"].entries) {
|
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);
|
if (state.type == EventTypes.RoomMember) userList.add(state.asUser);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -752,11 +751,11 @@ class Room {
|
||||||
Future<List<User>> requestParticipants() async {
|
Future<List<User>> requestParticipants() async {
|
||||||
List<User> participants = [];
|
List<User> participants = [];
|
||||||
|
|
||||||
dynamic res = await client.connection.jsonRequest(
|
dynamic res = await client.jsonRequest(
|
||||||
type: HTTPType.GET, action: "/client/r0/rooms/${id}/members");
|
type: HTTPType.GET, action: "/client/r0/rooms/${id}/members");
|
||||||
|
|
||||||
for (num i = 0; i < res["chunk"].length; i++) {
|
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);
|
if (newUser.membership != Membership.leave) participants.add(newUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -791,7 +790,7 @@ class Room {
|
||||||
if (mxID == null || !_requestingMatrixIds.add(mxID)) return null;
|
if (mxID == null || !_requestingMatrixIds.add(mxID)) return null;
|
||||||
Map<String, dynamic> resp;
|
Map<String, dynamic> resp;
|
||||||
try {
|
try {
|
||||||
resp = await client.connection.jsonRequest(
|
resp = await client.jsonRequest(
|
||||||
type: HTTPType.GET,
|
type: HTTPType.GET,
|
||||||
action: "/client/r0/rooms/$id/state/m.room.member/$mxID");
|
action: "/client/r0/rooms/$id/state/m.room.member/$mxID");
|
||||||
} catch (exception) {
|
} catch (exception) {
|
||||||
|
@ -821,7 +820,7 @@ class Room {
|
||||||
|
|
||||||
/// Searches for the event on the server. Returns null if not found.
|
/// Searches for the event on the server. Returns null if not found.
|
||||||
Future<Event> getEventById(String eventID) async {
|
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");
|
type: HTTPType.GET, action: "/client/r0/rooms/$id/event/$eventID");
|
||||||
return Event.fromJson(resp, this);
|
return Event.fromJson(resp, this);
|
||||||
}
|
}
|
||||||
|
@ -829,7 +828,7 @@ class Room {
|
||||||
/// Returns the power level of the given user ID.
|
/// Returns the power level of the given user ID.
|
||||||
int getPowerLevelByUserId(String userId) {
|
int getPowerLevelByUserId(String userId) {
|
||||||
int powerLevel = 0;
|
int powerLevel = 0;
|
||||||
RoomState powerLevelState = states["m.room.power_levels"];
|
Event powerLevelState = states["m.room.power_levels"];
|
||||||
if (powerLevelState == null) return powerLevel;
|
if (powerLevelState == null) return powerLevel;
|
||||||
if (powerLevelState.content["users_default"] is int)
|
if (powerLevelState.content["users_default"] is int)
|
||||||
powerLevel = powerLevelState.content["users_default"];
|
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.
|
/// Returns the power levels from all users for this room or null if not given.
|
||||||
Map<String, int> get powerLevels {
|
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>)
|
if (powerLevelState.content["users"] is Map<String, int>)
|
||||||
return powerLevelState.content["users"];
|
return powerLevelState.content["users"];
|
||||||
return null;
|
return null;
|
||||||
|
@ -853,12 +852,11 @@ class Room {
|
||||||
/// Uploads a new user avatar for this room. Returns the event ID of the new
|
/// Uploads a new user avatar for this room. Returns the event ID of the new
|
||||||
/// m.room.avatar event.
|
/// m.room.avatar event.
|
||||||
Future<String> setAvatar(MatrixFile file) async {
|
Future<String> setAvatar(MatrixFile file) async {
|
||||||
final String uploadResp = await client.connection.upload(file);
|
final String uploadResp = await client.upload(file);
|
||||||
final Map<String, dynamic> setAvatarResp = await client.connection
|
final Map<String, dynamic> setAvatarResp = await client.jsonRequest(
|
||||||
.jsonRequest(
|
type: HTTPType.PUT,
|
||||||
type: HTTPType.PUT,
|
action: "/client/r0/rooms/$id/state/m.room.avatar/",
|
||||||
action: "/client/r0/rooms/$id/state/m.room.avatar/",
|
data: {"url": uploadResp});
|
||||||
data: {"url": uploadResp});
|
|
||||||
return setAvatarResp["event_id"];
|
return setAvatarResp["event_id"];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -946,12 +944,12 @@ class Room {
|
||||||
// All push notifications should be sent to the user
|
// All push notifications should be sent to the user
|
||||||
case PushRuleState.notify:
|
case PushRuleState.notify:
|
||||||
if (pushRuleState == PushRuleState.dont_notify)
|
if (pushRuleState == PushRuleState.dont_notify)
|
||||||
resp = await client.connection.jsonRequest(
|
resp = await client.jsonRequest(
|
||||||
type: HTTPType.DELETE,
|
type: HTTPType.DELETE,
|
||||||
action: "/client/r0/pushrules/global/override/$id",
|
action: "/client/r0/pushrules/global/override/$id",
|
||||||
data: {});
|
data: {});
|
||||||
else if (pushRuleState == PushRuleState.mentions_only)
|
else if (pushRuleState == PushRuleState.mentions_only)
|
||||||
resp = await client.connection.jsonRequest(
|
resp = await client.jsonRequest(
|
||||||
type: HTTPType.DELETE,
|
type: HTTPType.DELETE,
|
||||||
action: "/client/r0/pushrules/global/room/$id",
|
action: "/client/r0/pushrules/global/room/$id",
|
||||||
data: {});
|
data: {});
|
||||||
|
@ -959,18 +957,18 @@ class Room {
|
||||||
// Only when someone mentions the user, a push notification should be sent
|
// Only when someone mentions the user, a push notification should be sent
|
||||||
case PushRuleState.mentions_only:
|
case PushRuleState.mentions_only:
|
||||||
if (pushRuleState == PushRuleState.dont_notify) {
|
if (pushRuleState == PushRuleState.dont_notify) {
|
||||||
resp = await client.connection.jsonRequest(
|
resp = await client.jsonRequest(
|
||||||
type: HTTPType.DELETE,
|
type: HTTPType.DELETE,
|
||||||
action: "/client/r0/pushrules/global/override/$id",
|
action: "/client/r0/pushrules/global/override/$id",
|
||||||
data: {});
|
data: {});
|
||||||
resp = await client.connection.jsonRequest(
|
resp = await client.jsonRequest(
|
||||||
type: HTTPType.PUT,
|
type: HTTPType.PUT,
|
||||||
action: "/client/r0/pushrules/global/room/$id",
|
action: "/client/r0/pushrules/global/room/$id",
|
||||||
data: {
|
data: {
|
||||||
"actions": ["dont_notify"]
|
"actions": ["dont_notify"]
|
||||||
});
|
});
|
||||||
} else if (pushRuleState == PushRuleState.notify)
|
} else if (pushRuleState == PushRuleState.notify)
|
||||||
resp = await client.connection.jsonRequest(
|
resp = await client.jsonRequest(
|
||||||
type: HTTPType.PUT,
|
type: HTTPType.PUT,
|
||||||
action: "/client/r0/pushrules/global/room/$id",
|
action: "/client/r0/pushrules/global/room/$id",
|
||||||
data: {
|
data: {
|
||||||
|
@ -980,12 +978,12 @@ class Room {
|
||||||
// No push notification should be ever sent for this room.
|
// No push notification should be ever sent for this room.
|
||||||
case PushRuleState.dont_notify:
|
case PushRuleState.dont_notify:
|
||||||
if (pushRuleState == PushRuleState.mentions_only) {
|
if (pushRuleState == PushRuleState.mentions_only) {
|
||||||
resp = await client.connection.jsonRequest(
|
resp = await client.jsonRequest(
|
||||||
type: HTTPType.DELETE,
|
type: HTTPType.DELETE,
|
||||||
action: "/client/r0/pushrules/global/room/$id",
|
action: "/client/r0/pushrules/global/room/$id",
|
||||||
data: {});
|
data: {});
|
||||||
}
|
}
|
||||||
resp = await client.connection.jsonRequest(
|
resp = await client.jsonRequest(
|
||||||
type: HTTPType.PUT,
|
type: HTTPType.PUT,
|
||||||
action: "/client/r0/pushrules/global/override/$id",
|
action: "/client/r0/pushrules/global/override/$id",
|
||||||
data: {
|
data: {
|
||||||
|
@ -1010,7 +1008,7 @@ class Room {
|
||||||
messageID = txid;
|
messageID = txid;
|
||||||
Map<String, dynamic> data = {};
|
Map<String, dynamic> data = {};
|
||||||
if (reason != null) data["reason"] = reason;
|
if (reason != null) data["reason"] = reason;
|
||||||
final dynamic resp = await client.connection.jsonRequest(
|
final dynamic resp = await client.jsonRequest(
|
||||||
type: HTTPType.PUT,
|
type: HTTPType.PUT,
|
||||||
action: "/client/r0/rooms/$id/redact/$eventId/$messageID",
|
action: "/client/r0/rooms/$id/redact/$eventId/$messageID",
|
||||||
data: data);
|
data: data);
|
||||||
|
@ -1022,7 +1020,7 @@ class Room {
|
||||||
"typing": isTyping,
|
"typing": isTyping,
|
||||||
};
|
};
|
||||||
if (timeout != null) data["timeout"] = timeout;
|
if (timeout != null) data["timeout"] = timeout;
|
||||||
return client.connection.jsonRequest(
|
return client.jsonRequest(
|
||||||
type: HTTPType.PUT,
|
type: HTTPType.PUT,
|
||||||
action: "/client/r0/rooms/${this.id}/typing/${client.userID}",
|
action: "/client/r0/rooms/${this.id}/typing/${client.userID}",
|
||||||
data: data,
|
data: data,
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
|
|
||||||
import 'package:famedlysdk/famedlysdk.dart';
|
import 'package:famedlysdk/famedlysdk.dart';
|
||||||
import 'package:famedlysdk/src/AccountData.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.
|
/// Stripped down events for account data and ephemrals of a room.
|
||||||
class RoomAccountData extends AccountData {
|
class RoomAccountData extends AccountData {
|
||||||
|
@ -40,7 +40,7 @@ class RoomAccountData extends AccountData {
|
||||||
factory RoomAccountData.fromJson(
|
factory RoomAccountData.fromJson(
|
||||||
Map<String, dynamic> jsonPayload, Room room) {
|
Map<String, dynamic> jsonPayload, Room room) {
|
||||||
final Map<String, dynamic> content =
|
final Map<String, dynamic> content =
|
||||||
RoomState.getMapFromPayload(jsonPayload['content']);
|
Event.getMapFromPayload(jsonPayload['content']);
|
||||||
return RoomAccountData(
|
return RoomAccountData(
|
||||||
content: content,
|
content: content,
|
||||||
typeKey: jsonPayload['type'],
|
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}) {
|
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}) {
|
int _findEvent({String event_id, String unsigned_txid}) {
|
||||||
|
@ -148,8 +148,8 @@ class Timeline {
|
||||||
sort() {
|
sort() {
|
||||||
if (sortLock || events.length < 2) return;
|
if (sortLock || events.length < 2) return;
|
||||||
sortLock = true;
|
sortLock = true;
|
||||||
events
|
events?.sort((a, b) =>
|
||||||
?.sort((a, b) => b.time.toTimeStamp().compareTo(a.time.toTimeStamp()));
|
b.time.millisecondsSinceEpoch.compareTo(a.time.millisecondsSinceEpoch));
|
||||||
sortLock = false;
|
sortLock = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,16 +23,13 @@
|
||||||
|
|
||||||
import 'package:famedlysdk/famedlysdk.dart';
|
import 'package:famedlysdk/famedlysdk.dart';
|
||||||
import 'package:famedlysdk/src/Room.dart';
|
import 'package:famedlysdk/src/Room.dart';
|
||||||
import 'package:famedlysdk/src/RoomState.dart';
|
import 'package:famedlysdk/src/Event.dart';
|
||||||
import 'package:famedlysdk/src/utils/ChatTime.dart';
|
|
||||||
import 'package:famedlysdk/src/utils/MxContent.dart';
|
import 'package:famedlysdk/src/utils/MxContent.dart';
|
||||||
|
|
||||||
import 'Connection.dart';
|
|
||||||
|
|
||||||
enum Membership { join, invite, leave, ban }
|
enum Membership { join, invite, leave, ban }
|
||||||
|
|
||||||
/// Represents a Matrix User which may be a participant in a Matrix Room.
|
/// Represents a Matrix User which may be a participant in a Matrix Room.
|
||||||
class User extends RoomState {
|
class User extends Event {
|
||||||
factory User(
|
factory User(
|
||||||
String id, {
|
String id, {
|
||||||
String membership,
|
String membership,
|
||||||
|
@ -50,7 +47,7 @@ class User extends RoomState {
|
||||||
typeKey: "m.room.member",
|
typeKey: "m.room.member",
|
||||||
roomId: room?.id,
|
roomId: room?.id,
|
||||||
room: room,
|
room: room,
|
||||||
time: ChatTime.now(),
|
time: DateTime.now(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,7 +59,7 @@ class User extends RoomState {
|
||||||
String eventId,
|
String eventId,
|
||||||
String roomId,
|
String roomId,
|
||||||
String senderId,
|
String senderId,
|
||||||
ChatTime time,
|
DateTime time,
|
||||||
dynamic unsigned,
|
dynamic unsigned,
|
||||||
Room room})
|
Room room})
|
||||||
: super(
|
: super(
|
||||||
|
@ -131,7 +128,7 @@ class User extends RoomState {
|
||||||
if (roomID != null) return roomID;
|
if (roomID != null) return roomID;
|
||||||
|
|
||||||
// Start a new direct chat
|
// Start a new direct chat
|
||||||
final dynamic resp = await room.client.connection.jsonRequest(
|
final dynamic resp = await room.client.jsonRequest(
|
||||||
type: HTTPType.POST,
|
type: HTTPType.POST,
|
||||||
action: "/client/r0/createRoom",
|
action: "/client/r0/createRoom",
|
||||||
data: {
|
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 {
|
class MatrixFile {
|
||||||
List<int> bytes;
|
List<int> bytes;
|
||||||
String path;
|
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';
|
import '../User.dart';
|
||||||
|
|
||||||
/// Represents a receipt.
|
/// Represents a receipt.
|
||||||
/// This [user] has read an event at the given [time].
|
/// This [user] has read an event at the given [time].
|
||||||
class Receipt {
|
class Receipt {
|
||||||
final User user;
|
final User user;
|
||||||
final ChatTime time;
|
final DateTime time;
|
||||||
|
|
||||||
const Receipt(this.user, this.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
|
/// Matrix room states are addressed by a tuple of the [type] and an
|
||||||
/// optional [stateKey].
|
/// optional [stateKey].
|
||||||
class StatesMap {
|
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.
|
/// If you just enter a MatrixID, it will try to return the corresponding m.room.member event.
|
||||||
dynamic operator [](String key) {
|
dynamic operator [](String key) {
|
||||||
//print("[Warning] This method will be depracated in the future!");
|
//print("[Warning] This method will be depracated in the future!");
|
||||||
|
@ -14,7 +14,7 @@ class StatesMap {
|
||||||
return states["m.room.member"][key];
|
return states["m.room.member"][key];
|
||||||
}
|
}
|
||||||
if (!states.containsKey(key)) states[key] = {};
|
if (!states.containsKey(key)) states[key] = {};
|
||||||
if (states[key][""] is RoomState)
|
if (states[key][""] is Event)
|
||||||
return states[key][""];
|
return states[key][""];
|
||||||
else if (states[key].length == 0)
|
else if (states[key].length == 0)
|
||||||
return null;
|
return null;
|
||||||
|
@ -22,7 +22,7 @@ class StatesMap {
|
||||||
return states[key];
|
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!");
|
//print("[Warning] This method will be depracated in the future!");
|
||||||
if (key.startsWith("@") && key.contains(":")) {
|
if (key.startsWith("@") && key.contains(":")) {
|
||||||
if (!states.containsKey("m.room.member")) states["m.room.member"] = {};
|
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/AccountData.dart';
|
||||||
import 'package:famedlysdk/src/Client.dart';
|
import 'package:famedlysdk/src/Client.dart';
|
||||||
import 'package:famedlysdk/src/Connection.dart';
|
|
||||||
import 'package:famedlysdk/src/Presence.dart';
|
import 'package:famedlysdk/src/Presence.dart';
|
||||||
import 'package:famedlysdk/src/Room.dart';
|
import 'package:famedlysdk/src/Room.dart';
|
||||||
import 'package:famedlysdk/src/User.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/EventUpdate.dart';
|
||||||
import 'package:famedlysdk/src/sync/RoomUpdate.dart';
|
import 'package:famedlysdk/src/sync/RoomUpdate.dart';
|
||||||
import 'package:famedlysdk/src/sync/UserUpdate.dart';
|
import 'package:famedlysdk/src/sync/UserUpdate.dart';
|
||||||
|
@ -53,11 +50,11 @@ void main() {
|
||||||
/// Check if all Elements get created
|
/// Check if all Elements get created
|
||||||
|
|
||||||
matrix = Client("testclient", debug: true);
|
matrix = Client("testclient", debug: true);
|
||||||
matrix.connection.httpClient = FakeMatrixApi();
|
matrix.httpClient = FakeMatrixApi();
|
||||||
|
|
||||||
roomUpdateListFuture = matrix.connection.onRoomUpdate.stream.toList();
|
roomUpdateListFuture = matrix.onRoomUpdate.stream.toList();
|
||||||
eventUpdateListFuture = matrix.connection.onEvent.stream.toList();
|
eventUpdateListFuture = matrix.onEvent.stream.toList();
|
||||||
userUpdateListFuture = matrix.connection.onUserEvent.stream.toList();
|
userUpdateListFuture = matrix.onUserEvent.stream.toList();
|
||||||
|
|
||||||
test('Login', () async {
|
test('Login', () async {
|
||||||
int presenceCounter = 0;
|
int presenceCounter = 0;
|
||||||
|
@ -82,7 +79,7 @@ void main() {
|
||||||
expect(matrix.matrixVersions,
|
expect(matrix.matrixVersions,
|
||||||
["r0.0.1", "r0.1.0", "r0.2.0", "r0.3.0", "r0.4.0", "r0.5.0"]);
|
["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: {
|
.jsonRequest(type: HTTPType.POST, action: "/client/r0/login", data: {
|
||||||
"type": "m.login.password",
|
"type": "m.login.password",
|
||||||
"user": "test",
|
"user": "test",
|
||||||
|
@ -91,11 +88,11 @@ void main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
Future<LoginState> loginStateFuture =
|
Future<LoginState> loginStateFuture =
|
||||||
matrix.connection.onLoginStateChanged.stream.first;
|
matrix.onLoginStateChanged.stream.first;
|
||||||
Future<bool> firstSyncFuture = matrix.connection.onFirstSync.stream.first;
|
Future<bool> firstSyncFuture = matrix.onFirstSync.stream.first;
|
||||||
Future<dynamic> syncFuture = matrix.connection.onSync.stream.first;
|
Future<dynamic> syncFuture = matrix.onSync.stream.first;
|
||||||
|
|
||||||
matrix.connection.connect(
|
matrix.connect(
|
||||||
newToken: resp["access_token"],
|
newToken: resp["access_token"],
|
||||||
newUserID: resp["user_id"],
|
newUserID: resp["user_id"],
|
||||||
newHomeserver: matrix.homeserver,
|
newHomeserver: matrix.homeserver,
|
||||||
|
@ -121,23 +118,23 @@ void main() {
|
||||||
expect(matrix.accountData.length, 3);
|
expect(matrix.accountData.length, 3);
|
||||||
expect(matrix.getDirectChatFromUserId("@bob:example.com"),
|
expect(matrix.getDirectChatFromUserId("@bob:example.com"),
|
||||||
"!726s6s6q: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.directChats, matrix.accountData["m.direct"].content);
|
||||||
expect(matrix.presences.length, 1);
|
expect(matrix.presences.length, 1);
|
||||||
expect(matrix.roomList.rooms[1].ephemerals.length, 2);
|
expect(matrix.rooms[1].ephemerals.length, 2);
|
||||||
expect(matrix.roomList.rooms[1].typingUsers.length, 1);
|
expect(matrix.rooms[1].typingUsers.length, 1);
|
||||||
expect(matrix.roomList.rooms[1].typingUsers[0].id, "@alice:example.com");
|
expect(matrix.rooms[1].typingUsers[0].id, "@alice:example.com");
|
||||||
expect(matrix.roomList.rooms[1].roomAccountData.length, 3);
|
expect(matrix.rooms[1].roomAccountData.length, 3);
|
||||||
expect(
|
expect(
|
||||||
matrix.roomList.rooms[1].roomAccountData["m.receipt"]
|
matrix.rooms[1].roomAccountData["m.receipt"]
|
||||||
.content["@alice:example.com"]["ts"],
|
.content["@alice:example.com"]["ts"],
|
||||||
1436451550453);
|
1436451550453);
|
||||||
expect(
|
expect(
|
||||||
matrix.roomList.rooms[1].roomAccountData["m.receipt"]
|
matrix.rooms[1].roomAccountData["m.receipt"]
|
||||||
.content["@alice:example.com"]["event_id"],
|
.content["@alice:example.com"]["event_id"],
|
||||||
"7365636s6r6432:example.com");
|
"7365636s6r6432:example.com");
|
||||||
expect(matrix.roomList.rooms.length, 2);
|
expect(matrix.rooms.length, 2);
|
||||||
expect(matrix.roomList.rooms[1].canonicalAlias,
|
expect(matrix.rooms[1].canonicalAlias,
|
||||||
"#famedlyContactDiscovery:${matrix.userID.split(":")[1]}");
|
"#famedlyContactDiscovery:${matrix.userID.split(":")[1]}");
|
||||||
final List<User> contacts = await matrix.loadFamedlyContacts();
|
final List<User> contacts = await matrix.loadFamedlyContacts();
|
||||||
expect(contacts.length, 1);
|
expect(contacts.length, 1);
|
||||||
|
@ -147,25 +144,30 @@ void main() {
|
||||||
expect(presenceCounter, 1);
|
expect(presenceCounter, 1);
|
||||||
expect(accountDataCounter, 3);
|
expect(accountDataCounter, 3);
|
||||||
|
|
||||||
matrix.connection.onEvent.add(
|
matrix.handleSync({
|
||||||
EventUpdate(
|
"rooms": {
|
||||||
roomID: "!726s6s6q:example.com",
|
"join": {
|
||||||
type: "state",
|
"!726s6s6q:example.com": {
|
||||||
eventType: "m.room.canonical_alias",
|
"state": {
|
||||||
content: {
|
"events": [
|
||||||
"sender": "@alice:example.com",
|
{
|
||||||
"type": "m.room.canonical_alias",
|
"sender": "@alice:example.com",
|
||||||
"content": {"alias": ""},
|
"type": "m.room.canonical_alias",
|
||||||
"state_key": "",
|
"content": {"alias": ""},
|
||||||
"origin_server_ts": 1417731086799,
|
"state_key": "",
|
||||||
"event_id": "66697273743033:example.com"
|
"origin_server_ts": 1417731086799,
|
||||||
},
|
"event_id": "66697273743033:example.com"
|
||||||
),
|
}
|
||||||
);
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
await new Future.delayed(new Duration(milliseconds: 50));
|
await new Future.delayed(new Duration(milliseconds: 50));
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
matrix.roomList.getRoomByAlias(
|
matrix.getRoomByAlias(
|
||||||
"#famedlyContactDiscovery:${matrix.userID.split(":")[1]}"),
|
"#famedlyContactDiscovery:${matrix.userID.split(":")[1]}"),
|
||||||
null);
|
null);
|
||||||
final List<User> altContacts = await matrix.loadFamedlyContacts();
|
final List<User> altContacts = await matrix.loadFamedlyContacts();
|
||||||
|
@ -176,8 +178,8 @@ void main() {
|
||||||
test('Try to get ErrorResponse', () async {
|
test('Try to get ErrorResponse', () async {
|
||||||
MatrixException expectedException;
|
MatrixException expectedException;
|
||||||
try {
|
try {
|
||||||
await matrix.connection
|
await matrix.jsonRequest(
|
||||||
.jsonRequest(type: HTTPType.PUT, action: "/non/existing/path");
|
type: HTTPType.PUT, action: "/non/existing/path");
|
||||||
} on MatrixException catch (exception) {
|
} on MatrixException catch (exception) {
|
||||||
expectedException = exception;
|
expectedException = exception;
|
||||||
}
|
}
|
||||||
|
@ -185,13 +187,13 @@ void main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Logout', () async {
|
test('Logout', () async {
|
||||||
await matrix.connection
|
await matrix.jsonRequest(
|
||||||
.jsonRequest(type: HTTPType.POST, action: "/client/r0/logout");
|
type: HTTPType.POST, action: "/client/r0/logout");
|
||||||
|
|
||||||
Future<LoginState> loginStateFuture =
|
Future<LoginState> loginStateFuture =
|
||||||
matrix.connection.onLoginStateChanged.stream.first;
|
matrix.onLoginStateChanged.stream.first;
|
||||||
|
|
||||||
matrix.connection.clear();
|
matrix.clear();
|
||||||
|
|
||||||
expect(matrix.accessToken == null, true);
|
expect(matrix.accessToken == null, true);
|
||||||
expect(matrix.homeserver == null, true);
|
expect(matrix.homeserver == null, true);
|
||||||
|
@ -207,11 +209,11 @@ void main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Room Update Test', () async {
|
test('Room Update Test', () async {
|
||||||
matrix.connection.onRoomUpdate.close();
|
matrix.onRoomUpdate.close();
|
||||||
|
|
||||||
List<RoomUpdate> roomUpdateList = await roomUpdateListFuture;
|
List<RoomUpdate> roomUpdateList = await roomUpdateListFuture;
|
||||||
|
|
||||||
expect(roomUpdateList.length, 2);
|
expect(roomUpdateList.length, 3);
|
||||||
|
|
||||||
expect(roomUpdateList[0].id == "!726s6s6q:example.com", true);
|
expect(roomUpdateList[0].id == "!726s6s6q:example.com", true);
|
||||||
expect(roomUpdateList[0].membership == Membership.join, true);
|
expect(roomUpdateList[0].membership == Membership.join, true);
|
||||||
|
@ -229,7 +231,7 @@ void main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Event Update Test', () async {
|
test('Event Update Test', () async {
|
||||||
matrix.connection.onEvent.close();
|
matrix.onEvent.close();
|
||||||
|
|
||||||
List<EventUpdate> eventUpdateList = await eventUpdateListFuture;
|
List<EventUpdate> eventUpdateList = await eventUpdateListFuture;
|
||||||
|
|
||||||
|
@ -281,7 +283,7 @@ void main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('User Update Test', () async {
|
test('User Update Test', () async {
|
||||||
matrix.connection.onUserEvent.close();
|
matrix.onUserEvent.close();
|
||||||
|
|
||||||
List<UserUpdate> eventUpdateList = await userUpdateListFuture;
|
List<UserUpdate> eventUpdateList = await userUpdateListFuture;
|
||||||
|
|
||||||
|
@ -299,11 +301,11 @@ void main() {
|
||||||
|
|
||||||
test('Login', () async {
|
test('Login', () async {
|
||||||
matrix = Client("testclient", debug: true);
|
matrix = Client("testclient", debug: true);
|
||||||
matrix.connection.httpClient = FakeMatrixApi();
|
matrix.httpClient = FakeMatrixApi();
|
||||||
|
|
||||||
roomUpdateListFuture = matrix.connection.onRoomUpdate.stream.toList();
|
roomUpdateListFuture = matrix.onRoomUpdate.stream.toList();
|
||||||
eventUpdateListFuture = matrix.connection.onEvent.stream.toList();
|
eventUpdateListFuture = matrix.onEvent.stream.toList();
|
||||||
userUpdateListFuture = matrix.connection.onUserEvent.stream.toList();
|
userUpdateListFuture = matrix.onUserEvent.stream.toList();
|
||||||
final bool checkResp =
|
final bool checkResp =
|
||||||
await matrix.checkServer("https://fakeServer.notExisting");
|
await matrix.checkServer("https://fakeServer.notExisting");
|
||||||
|
|
||||||
|
@ -326,7 +328,7 @@ void main() {
|
||||||
final MatrixFile testFile =
|
final MatrixFile testFile =
|
||||||
MatrixFile(bytes: [], path: "fake/path/file.jpeg");
|
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");
|
expect(resp, "mxc://example.com/AQwafuaFswefuhsfAFAgsw");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -337,23 +339,14 @@ void main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('getPushrules', () async {
|
test('getPushrules', () async {
|
||||||
final PushrulesResponse pushrules = await matrix.getPushrules();
|
final pushrules = await matrix.getPushrules();
|
||||||
final PushrulesResponse awaited_resp = PushrulesResponse.fromJson(
|
expect(pushrules != null, true);
|
||||||
FakeMatrixApi.api["GET"]["/client/r0/pushrules/"](""));
|
|
||||||
expect(pushrules.toJson(), awaited_resp.toJson());
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('setPushers', () async {
|
test('setPushers', () async {
|
||||||
final SetPushersRequest data = SetPushersRequest(
|
await matrix.setPushers("abcdefg", "http", "com.famedly.famedlysdk",
|
||||||
app_id: "com.famedly.famedlysdk",
|
"famedlySDK", "GitLabCi", "en", "https://examplepushserver.com",
|
||||||
device_display_name: "GitLabCi",
|
format: "event_id_only");
|
||||||
app_display_name: "famedlySDK",
|
|
||||||
pushkey: "abcdefg",
|
|
||||||
kind: "http",
|
|
||||||
lang: "en",
|
|
||||||
data: PusherData(
|
|
||||||
format: "event_id_only", url: "https://examplepushserver.com"));
|
|
||||||
await matrix.setPushers(data);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('joinRoomById', () async {
|
test('joinRoomById', () async {
|
||||||
|
@ -388,11 +381,11 @@ void main() {
|
||||||
|
|
||||||
test('Logout when token is unknown', () async {
|
test('Logout when token is unknown', () async {
|
||||||
Future<LoginState> loginStateFuture =
|
Future<LoginState> loginStateFuture =
|
||||||
matrix.connection.onLoginStateChanged.stream.first;
|
matrix.onLoginStateChanged.stream.first;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await matrix.connection
|
await matrix.jsonRequest(
|
||||||
.jsonRequest(type: HTTPType.DELETE, action: "/unknown/token");
|
type: HTTPType.DELETE, action: "/unknown/token");
|
||||||
} on MatrixException catch (exception) {
|
} on MatrixException catch (exception) {
|
||||||
expect(exception.error, MatrixError.M_UNKNOWN_TOKEN);
|
expect(exception.error, MatrixError.M_UNKNOWN_TOKEN);
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:famedlysdk/famedlysdk.dart';
|
import 'package:famedlysdk/famedlysdk.dart';
|
||||||
import 'package:famedlysdk/src/RoomState.dart';
|
import 'package:famedlysdk/src/Event.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
import 'FakeMatrixApi.dart';
|
import 'FakeMatrixApi.dart';
|
||||||
|
@ -68,7 +68,7 @@ void main() {
|
||||||
expect(event.getBody(), body);
|
expect(event.getBody(), body);
|
||||||
expect(event.type, EventTypes.Text);
|
expect(event.type, EventTypes.Text);
|
||||||
jsonObj["state_key"] = "";
|
jsonObj["state_key"] = "";
|
||||||
RoomState state = RoomState.fromJson(jsonObj, null);
|
Event state = Event.fromJson(jsonObj, null);
|
||||||
expect(state.eventId, id);
|
expect(state.eventId, id);
|
||||||
expect(state.stateKey, "");
|
expect(state.stateKey, "");
|
||||||
expect(state.timelineEvent.status, 1);
|
expect(state.timelineEvent.status, 1);
|
||||||
|
@ -174,7 +174,7 @@ void main() {
|
||||||
"type": "m.room.redaction",
|
"type": "m.room.redaction",
|
||||||
"unsigned": {"age": 1234}
|
"unsigned": {"age": 1234}
|
||||||
};
|
};
|
||||||
RoomState redactedBecause = RoomState.fromJson(redactionEventJson, room);
|
Event redactedBecause = Event.fromJson(redactionEventJson, room);
|
||||||
Event event = Event.fromJson(jsonObj, room);
|
Event event = Event.fromJson(jsonObj, room);
|
||||||
event.setRedactionEvent(redactedBecause);
|
event.setRedactionEvent(redactedBecause);
|
||||||
expect(event.redacted, true);
|
expect(event.redacted, true);
|
||||||
|
@ -196,7 +196,7 @@ void main() {
|
||||||
|
|
||||||
test("sendAgain", () async {
|
test("sendAgain", () async {
|
||||||
Client matrix = Client("testclient", debug: true);
|
Client matrix = Client("testclient", debug: true);
|
||||||
matrix.connection.httpClient = FakeMatrixApi();
|
matrix.httpClient = FakeMatrixApi();
|
||||||
await matrix.checkServer("https://fakeServer.notExisting");
|
await matrix.checkServer("https://fakeServer.notExisting");
|
||||||
await matrix.login("test", "1234");
|
await matrix.login("test", "1234");
|
||||||
|
|
||||||
|
|
|
@ -25,12 +25,15 @@ import 'package:test/test.dart';
|
||||||
import 'package:famedlysdk/src/Client.dart';
|
import 'package:famedlysdk/src/Client.dart';
|
||||||
import 'package:famedlysdk/src/utils/MxContent.dart';
|
import 'package:famedlysdk/src/utils/MxContent.dart';
|
||||||
|
|
||||||
|
import 'FakeMatrixApi.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
/// All Tests related to the MxContent
|
/// All Tests related to the MxContent
|
||||||
group("MxContent", () {
|
group("MxContent", () {
|
||||||
test("Formatting", () async {
|
test("Formatting", () async {
|
||||||
Client client = Client("testclient");
|
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 String mxc = "mxc://exampleserver.abc/abcdefghijklmn";
|
||||||
final MxContent content = MxContent(mxc);
|
final MxContent content = MxContent(mxc);
|
||||||
|
|
||||||
|
@ -45,7 +48,8 @@ void main() {
|
||||||
});
|
});
|
||||||
test("Not crashing if null", () async {
|
test("Not crashing if null", () async {
|
||||||
Client client = Client("testclient");
|
Client client = Client("testclient");
|
||||||
client.homeserver = "https://testserver.abc";
|
client.httpClient = FakeMatrixApi();
|
||||||
|
await client.checkServer("https://fakeserver.notexisting");
|
||||||
final MxContent content = MxContent(null);
|
final MxContent content = MxContent(null);
|
||||||
expect(content.getDownloadLink(client),
|
expect(content.getDownloadLink(client),
|
||||||
"${client.homeserver}/_matrix/media/r0/download/");
|
"${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/Client.dart';
|
||||||
import 'package:famedlysdk/src/Event.dart';
|
import 'package:famedlysdk/src/Event.dart';
|
||||||
import 'package:famedlysdk/src/Room.dart';
|
import 'package:famedlysdk/src/Room.dart';
|
||||||
import 'package:famedlysdk/src/RoomState.dart';
|
|
||||||
import 'package:famedlysdk/src/Timeline.dart';
|
import 'package:famedlysdk/src/Timeline.dart';
|
||||||
import 'package:famedlysdk/src/User.dart';
|
import 'package:famedlysdk/src/User.dart';
|
||||||
import 'package:famedlysdk/src/utils/ChatTime.dart';
|
|
||||||
import 'package:famedlysdk/src/utils/MatrixFile.dart';
|
import 'package:famedlysdk/src/utils/MatrixFile.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
@ -41,7 +39,7 @@ void main() {
|
||||||
group("Room", () {
|
group("Room", () {
|
||||||
test('Login', () async {
|
test('Login', () async {
|
||||||
matrix = Client("testclient", debug: true);
|
matrix = Client("testclient", debug: true);
|
||||||
matrix.connection.httpClient = FakeMatrixApi();
|
matrix.httpClient = FakeMatrixApi();
|
||||||
|
|
||||||
final bool checkResp =
|
final bool checkResp =
|
||||||
await matrix.checkServer("https://fakeServer.notExisting");
|
await matrix.checkServer("https://fakeServer.notExisting");
|
||||||
|
@ -86,7 +84,7 @@ void main() {
|
||||||
expect(room.mHeroes, heroes);
|
expect(room.mHeroes, heroes);
|
||||||
expect(room.displayname, "alice, bob, charley");
|
expect(room.displayname, "alice, bob, charley");
|
||||||
|
|
||||||
room.states["m.room.canonical_alias"] = RoomState(
|
room.states["m.room.canonical_alias"] = Event(
|
||||||
senderId: "@test:example.com",
|
senderId: "@test:example.com",
|
||||||
typeKey: "m.room.canonical_alias",
|
typeKey: "m.room.canonical_alias",
|
||||||
roomId: room.id,
|
roomId: room.id,
|
||||||
|
@ -97,7 +95,7 @@ void main() {
|
||||||
expect(room.displayname, "testalias");
|
expect(room.displayname, "testalias");
|
||||||
expect(room.canonicalAlias, "#testalias:example.com");
|
expect(room.canonicalAlias, "#testalias:example.com");
|
||||||
|
|
||||||
room.states["m.room.name"] = RoomState(
|
room.states["m.room.name"] = Event(
|
||||||
senderId: "@test:example.com",
|
senderId: "@test:example.com",
|
||||||
typeKey: "m.room.name",
|
typeKey: "m.room.name",
|
||||||
roomId: room.id,
|
roomId: room.id,
|
||||||
|
@ -108,7 +106,7 @@ void main() {
|
||||||
expect(room.displayname, "testname");
|
expect(room.displayname, "testname");
|
||||||
|
|
||||||
expect(room.topic, "");
|
expect(room.topic, "");
|
||||||
room.states["m.room.topic"] = RoomState(
|
room.states["m.room.topic"] = Event(
|
||||||
senderId: "@test:example.com",
|
senderId: "@test:example.com",
|
||||||
typeKey: "m.room.topic",
|
typeKey: "m.room.topic",
|
||||||
roomId: room.id,
|
roomId: room.id,
|
||||||
|
@ -119,7 +117,7 @@ void main() {
|
||||||
expect(room.topic, "testtopic");
|
expect(room.topic, "testtopic");
|
||||||
|
|
||||||
expect(room.avatar.mxc, "");
|
expect(room.avatar.mxc, "");
|
||||||
room.states["m.room.avatar"] = RoomState(
|
room.states["m.room.avatar"] = Event(
|
||||||
senderId: "@test:example.com",
|
senderId: "@test:example.com",
|
||||||
typeKey: "m.room.avatar",
|
typeKey: "m.room.avatar",
|
||||||
roomId: room.id,
|
roomId: room.id,
|
||||||
|
@ -130,13 +128,13 @@ void main() {
|
||||||
expect(room.avatar.mxc, "mxc://testurl");
|
expect(room.avatar.mxc, "mxc://testurl");
|
||||||
|
|
||||||
expect(room.lastEvent, null);
|
expect(room.lastEvent, null);
|
||||||
room.states["m.room.message"] = RoomState(
|
room.states["m.room.message"] = Event(
|
||||||
senderId: "@test:example.com",
|
senderId: "@test:example.com",
|
||||||
typeKey: "m.room.message",
|
typeKey: "m.room.message",
|
||||||
roomId: room.id,
|
roomId: room.id,
|
||||||
room: room,
|
room: room,
|
||||||
eventId: "12345",
|
eventId: "12345",
|
||||||
time: ChatTime.now(),
|
time: DateTime.now(),
|
||||||
content: {"msgtype": "m.text", "body": "test"},
|
content: {"msgtype": "m.text", "body": "test"},
|
||||||
stateKey: "");
|
stateKey: "");
|
||||||
expect(room.lastEvent.eventId, "12345");
|
expect(room.lastEvent.eventId, "12345");
|
||||||
|
@ -187,7 +185,7 @@ void main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test("PowerLevels", () async {
|
test("PowerLevels", () async {
|
||||||
room.states["m.room.power_levels"] = RoomState(
|
room.states["m.room.power_levels"] = Event(
|
||||||
senderId: "@test:example.com",
|
senderId: "@test:example.com",
|
||||||
typeKey: "m.room.power_levels",
|
typeKey: "m.room.power_levels",
|
||||||
roomId: room.id,
|
roomId: room.id,
|
||||||
|
@ -223,7 +221,7 @@ void main() {
|
||||||
expect(room.powerLevels,
|
expect(room.powerLevels,
|
||||||
room.states["m.room.power_levels"].content["users"]);
|
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",
|
senderId: "@test:example.com",
|
||||||
typeKey: "m.room.power_levels",
|
typeKey: "m.room.power_levels",
|
||||||
roomId: room.id,
|
roomId: room.id,
|
||||||
|
@ -264,13 +262,13 @@ void main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test("getParticipants", () async {
|
test("getParticipants", () async {
|
||||||
room.setState(RoomState(
|
room.setState(Event(
|
||||||
senderId: "@alice:test.abc",
|
senderId: "@alice:test.abc",
|
||||||
typeKey: "m.room.member",
|
typeKey: "m.room.member",
|
||||||
roomId: room.id,
|
roomId: room.id,
|
||||||
room: room,
|
room: room,
|
||||||
eventId: "12345",
|
eventId: "12345",
|
||||||
time: ChatTime.now(),
|
time: DateTime.now(),
|
||||||
content: {"displayname": "alice"},
|
content: {"displayname": "alice"},
|
||||||
stateKey: "@alice:test.abc"));
|
stateKey: "@alice:test.abc"));
|
||||||
final List<User> userList = room.getParticipants();
|
final List<User> userList = room.getParticipants();
|
||||||
|
|
|
@ -30,7 +30,7 @@ void main() {
|
||||||
group("StateKeys", () {
|
group("StateKeys", () {
|
||||||
test("Operator overload", () async {
|
test("Operator overload", () async {
|
||||||
StatesMap states = StatesMap();
|
StatesMap states = StatesMap();
|
||||||
states["m.room.name"] = RoomState(
|
states["m.room.name"] = Event(
|
||||||
eventId: "1",
|
eventId: "1",
|
||||||
content: {"name": "test"},
|
content: {"name": "test"},
|
||||||
typeKey: "m.room.name",
|
typeKey: "m.room.name",
|
||||||
|
@ -38,7 +38,7 @@ void main() {
|
||||||
roomId: "!test:test.test",
|
roomId: "!test:test.test",
|
||||||
senderId: "@alice:test.test");
|
senderId: "@alice:test.test");
|
||||||
|
|
||||||
states["@alice:test.test"] = RoomState(
|
states["@alice:test.test"] = Event(
|
||||||
eventId: "2",
|
eventId: "2",
|
||||||
content: {"membership": "join"},
|
content: {"membership": "join"},
|
||||||
typeKey: "m.room.name",
|
typeKey: "m.room.name",
|
||||||
|
@ -46,7 +46,7 @@ void main() {
|
||||||
roomId: "!test:test.test",
|
roomId: "!test:test.test",
|
||||||
senderId: "@alice:test.test");
|
senderId: "@alice:test.test");
|
||||||
|
|
||||||
states["m.room.member"]["@bob:test.test"] = RoomState(
|
states["m.room.member"]["@bob:test.test"] = Event(
|
||||||
eventId: "3",
|
eventId: "3",
|
||||||
content: {"membership": "join"},
|
content: {"membership": "join"},
|
||||||
typeKey: "m.room.name",
|
typeKey: "m.room.name",
|
||||||
|
@ -54,7 +54,7 @@ void main() {
|
||||||
roomId: "!test:test.test",
|
roomId: "!test:test.test",
|
||||||
senderId: "@bob:test.test");
|
senderId: "@bob:test.test");
|
||||||
|
|
||||||
states["com.test.custom"] = RoomState(
|
states["com.test.custom"] = Event(
|
||||||
eventId: "4",
|
eventId: "4",
|
||||||
content: {"custom": "stuff"},
|
content: {"custom": "stuff"},
|
||||||
typeKey: "com.test.custom",
|
typeKey: "com.test.custom",
|
||||||
|
|
|
@ -27,20 +27,18 @@ import 'package:famedlysdk/src/Client.dart';
|
||||||
import 'package:famedlysdk/src/Room.dart';
|
import 'package:famedlysdk/src/Room.dart';
|
||||||
import 'package:famedlysdk/src/Timeline.dart';
|
import 'package:famedlysdk/src/Timeline.dart';
|
||||||
import 'package:famedlysdk/src/sync/EventUpdate.dart';
|
import 'package:famedlysdk/src/sync/EventUpdate.dart';
|
||||||
import 'package:famedlysdk/src/utils/ChatTime.dart';
|
|
||||||
import 'FakeMatrixApi.dart';
|
import 'FakeMatrixApi.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
/// All Tests related to the MxContent
|
/// All Tests related to the MxContent
|
||||||
group("Timeline", () {
|
group("Timeline", () {
|
||||||
final String roomID = "!1234:example.com";
|
final String roomID = "!1234:example.com";
|
||||||
final testTimeStamp = ChatTime.now().toTimeStamp();
|
final testTimeStamp = DateTime.now().millisecondsSinceEpoch;
|
||||||
int updateCount = 0;
|
int updateCount = 0;
|
||||||
List<int> insertList = [];
|
List<int> insertList = [];
|
||||||
|
|
||||||
Client client = Client("testclient", debug: true);
|
Client client = Client("testclient", debug: true);
|
||||||
client.connection.httpClient = FakeMatrixApi();
|
client.httpClient = FakeMatrixApi();
|
||||||
client.homeserver = "https://fakeServer.notExisting";
|
|
||||||
|
|
||||||
Room room = Room(
|
Room room = Room(
|
||||||
id: roomID, client: client, prev_batch: "1234", roomAccountData: {});
|
id: roomID, client: client, prev_batch: "1234", roomAccountData: {});
|
||||||
|
@ -55,7 +53,8 @@ void main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Create", () async {
|
test("Create", () async {
|
||||||
client.connection.onEvent.add(EventUpdate(
|
await client.checkServer("https://fakeServer.notExisting");
|
||||||
|
client.onEvent.add(EventUpdate(
|
||||||
type: "timeline",
|
type: "timeline",
|
||||||
roomID: roomID,
|
roomID: roomID,
|
||||||
eventType: "m.room.message",
|
eventType: "m.room.message",
|
||||||
|
@ -68,7 +67,7 @@ void main() {
|
||||||
"origin_server_ts": testTimeStamp
|
"origin_server_ts": testTimeStamp
|
||||||
}));
|
}));
|
||||||
|
|
||||||
client.connection.onEvent.add(EventUpdate(
|
client.onEvent.add(EventUpdate(
|
||||||
type: "timeline",
|
type: "timeline",
|
||||||
roomID: roomID,
|
roomID: roomID,
|
||||||
eventType: "m.room.message",
|
eventType: "m.room.message",
|
||||||
|
@ -91,9 +90,12 @@ void main() {
|
||||||
expect(timeline.events.length, 2);
|
expect(timeline.events.length, 2);
|
||||||
expect(timeline.events[0].eventId, "1");
|
expect(timeline.events[0].eventId, "1");
|
||||||
expect(timeline.events[0].sender.id, "@alice:example.com");
|
expect(timeline.events[0].sender.id, "@alice:example.com");
|
||||||
expect(timeline.events[0].time.toTimeStamp(), testTimeStamp);
|
expect(timeline.events[0].time.millisecondsSinceEpoch, testTimeStamp);
|
||||||
expect(timeline.events[0].getBody(), "Testcase");
|
expect(timeline.events[0].getBody(), "Testcase");
|
||||||
expect(timeline.events[0].time > timeline.events[1].time, true);
|
expect(
|
||||||
|
timeline.events[0].time.millisecondsSinceEpoch >
|
||||||
|
timeline.events[1].time.millisecondsSinceEpoch,
|
||||||
|
true);
|
||||||
expect(timeline.events[0].receipts, []);
|
expect(timeline.events[0].receipts, []);
|
||||||
|
|
||||||
room.roomAccountData["m.receipt"] = RoomAccountData.fromJson({
|
room.roomAccountData["m.receipt"] = RoomAccountData.fromJson({
|
||||||
|
@ -112,7 +114,7 @@ void main() {
|
||||||
expect(timeline.events[0].receipts.length, 1);
|
expect(timeline.events[0].receipts.length, 1);
|
||||||
expect(timeline.events[0].receipts[0].user.id, "@alice:example.com");
|
expect(timeline.events[0].receipts[0].user.id, "@alice:example.com");
|
||||||
|
|
||||||
client.connection.onEvent.add(EventUpdate(
|
client.onEvent.add(EventUpdate(
|
||||||
type: "timeline",
|
type: "timeline",
|
||||||
roomID: roomID,
|
roomID: roomID,
|
||||||
eventType: "m.room.redaction",
|
eventType: "m.room.redaction",
|
||||||
|
@ -145,7 +147,7 @@ void main() {
|
||||||
expect(timeline.events[0].eventId, "42");
|
expect(timeline.events[0].eventId, "42");
|
||||||
expect(timeline.events[0].status, 1);
|
expect(timeline.events[0].status, 1);
|
||||||
|
|
||||||
client.connection.onEvent.add(EventUpdate(
|
client.onEvent.add(EventUpdate(
|
||||||
type: "timeline",
|
type: "timeline",
|
||||||
roomID: roomID,
|
roomID: roomID,
|
||||||
eventType: "m.room.message",
|
eventType: "m.room.message",
|
||||||
|
@ -169,7 +171,7 @@ void main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Send message with error", () async {
|
test("Send message with error", () async {
|
||||||
client.connection.onEvent.add(EventUpdate(
|
client.onEvent.add(EventUpdate(
|
||||||
type: "timeline",
|
type: "timeline",
|
||||||
roomID: roomID,
|
roomID: roomID,
|
||||||
eventType: "m.room.message",
|
eventType: "m.room.message",
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
* along with famedlysdk. If not, see <http://www.gnu.org/licenses/>.
|
* along with famedlysdk. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import 'package:famedlysdk/src/RoomState.dart';
|
import 'package:famedlysdk/src/Event.dart';
|
||||||
import 'package:famedlysdk/src/User.dart';
|
import 'package:famedlysdk/src/User.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
@ -49,7 +49,7 @@ void main() {
|
||||||
"state_key": id
|
"state_key": id
|
||||||
};
|
};
|
||||||
|
|
||||||
User user = RoomState.fromJson(jsonObj, null).asUser;
|
User user = Event.fromJson(jsonObj, null).asUser;
|
||||||
|
|
||||||
expect(user.id, id);
|
expect(user.id, id);
|
||||||
expect(user.membership, membership);
|
expect(user.membership, membership);
|
||||||
|
|
Loading…
Reference in a new issue