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,8 +233,10 @@ 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, | ||||||
|  |         action: "/client/r0/login", | ||||||
|  |         data: { | ||||||
|           "type": "m.login.password", |           "type": "m.login.password", | ||||||
|           "user": username, |           "user": username, | ||||||
|           "identifier": { |           "identifier": { | ||||||
|  | @ -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,38 +21,161 @@ | ||||||
|  * 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) { | ||||||
|  |     if (payload is String) | ||||||
|  |       try { | ||||||
|  |         return json.decode(payload); | ||||||
|  |       } catch (e) { | ||||||
|  |         return {}; | ||||||
|  |       } | ||||||
|  |     if (payload is Map<String, dynamic>) return payload; | ||||||
|  |     return {}; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /// Get a State event from a table row or from the event stream. | ||||||
|  |   factory Event.fromJson(Map<String, dynamic> jsonPayload, Room room) { | ||||||
|  |     final Map<String, dynamic> content = | ||||||
|  |         Event.getMapFromPayload(jsonPayload['content']); | ||||||
|  |     final Map<String, dynamic> unsigned = | ||||||
|  |         Event.getMapFromPayload(jsonPayload['unsigned']); | ||||||
|  |     final Map<String, dynamic> prevContent = | ||||||
|  |         Event.getMapFromPayload(jsonPayload['prev_content']); | ||||||
|  |     return Event( | ||||||
|  |       status: jsonPayload['status'] ?? defaultStatus, | ||||||
|  |       stateKey: jsonPayload['state_key'], | ||||||
|  |       prevContent: prevContent, | ||||||
|  |       content: content, | ||||||
|  |       typeKey: jsonPayload['type'], | ||||||
|  |       eventId: jsonPayload['event_id'], | ||||||
|  |       roomId: jsonPayload['room_id'], | ||||||
|  |       senderId: jsonPayload['sender'], | ||||||
|  |       time: jsonPayload.containsKey('origin_server_ts') | ||||||
|  |           ? DateTime.fromMillisecondsSinceEpoch(jsonPayload['origin_server_ts']) | ||||||
|  |           : DateTime.now(), | ||||||
|  |       unsigned: unsigned, | ||||||
|  |       room: room, | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   Map<String, dynamic> toJson() { | ||||||
|  |     final Map<String, dynamic> data = new Map<String, dynamic>(); | ||||||
|  |     if (this.stateKey != null) data['state_key'] = this.stateKey; | ||||||
|  |     if (this.prevContent != null && this.prevContent.isNotEmpty) | ||||||
|  |       data['prev_content'] = this.prevContent; | ||||||
|  |     data['content'] = this.content; | ||||||
|  |     data['type'] = this.typeKey; | ||||||
|  |     data['event_id'] = this.eventId; | ||||||
|  |     data['room_id'] = this.roomId; | ||||||
|  |     data['sender'] = this.senderId; | ||||||
|  |     data['origin_server_ts'] = this.time.millisecondsSinceEpoch; | ||||||
|  |     if (this.unsigned != null && this.unsigned.isNotEmpty) | ||||||
|  |       data['unsigned'] = this.unsigned; | ||||||
|  |     return data; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   Event get timelineEvent => Event( | ||||||
|  |         content: content, | ||||||
|  |         typeKey: 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, |       content: content, | ||||||
|       typeKey: typeKey, |       typeKey: typeKey, | ||||||
|       eventId: eventId, |       eventId: eventId, | ||||||
|  | @ -60,34 +183,106 @@ class Event extends RoomState { | ||||||
|       senderId: senderId, |       senderId: senderId, | ||||||
|       time: time, |       time: time, | ||||||
|       unsigned: unsigned, |       unsigned: unsigned, | ||||||
|             prevContent: prevContent, |  | ||||||
|             stateKey: stateKey, |  | ||||||
|       room: room); |       room: room); | ||||||
| 
 | 
 | ||||||
|   /// Get a State event from a table row or from the event stream. |   /// Get the real type. | ||||||
|   factory Event.fromJson(Map<String, dynamic> jsonPayload, Room room) { |   EventTypes get type { | ||||||
|     final Map<String, dynamic> content = |     switch (typeKey) { | ||||||
|         RoomState.getMapFromPayload(jsonPayload['content']); |       case "m.room.avatar": | ||||||
|     final Map<String, dynamic> unsigned = |         return EventTypes.RoomAvatar; | ||||||
|         RoomState.getMapFromPayload(jsonPayload['unsigned']); |       case "m.room.name": | ||||||
|     final Map<String, dynamic> prevContent = |         return EventTypes.RoomName; | ||||||
|         RoomState.getMapFromPayload(jsonPayload['prev_content']); |       case "m.room.topic": | ||||||
|     Event redactedBecause = null; |         return EventTypes.RoomTopic; | ||||||
|     if (unsigned.containsKey("redacted_because")) |       case "m.room.Aliases": | ||||||
|       redactedBecause = Event.fromJson(unsigned["redacted_because"], room); |         return EventTypes.RoomAliases; | ||||||
|     return Event( |       case "m.room.canonical_alias": | ||||||
|         status: jsonPayload['status'] ?? defaultStatus, |         return EventTypes.RoomCanonicalAlias; | ||||||
|         content: content, |       case "m.room.create": | ||||||
|         typeKey: jsonPayload['type'], |         return EventTypes.RoomCreate; | ||||||
|         eventId: jsonPayload['event_id'], |       case "m.room.redaction": | ||||||
|         roomId: jsonPayload['room_id'], |         return EventTypes.Redaction; | ||||||
|         senderId: jsonPayload['sender'], |       case "m.room.join_rules": | ||||||
|         time: ChatTime(jsonPayload['origin_server_ts']), |         return EventTypes.RoomJoinRules; | ||||||
|         unsigned: unsigned, |       case "m.room.member": | ||||||
|         prevContent: prevContent, |         return EventTypes.RoomMember; | ||||||
|         stateKey: jsonPayload['state_key'], |       case "m.room.power_levels": | ||||||
|         room: room, |         return EventTypes.RoomPowerLevels; | ||||||
|         redactedBecause: redactedBecause); |       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,9 +852,8 @@ 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}); | ||||||
|  | @ -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; |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  | @ -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", |                     "sender": "@alice:example.com", | ||||||
|                     "type": "m.room.canonical_alias", |                     "type": "m.room.canonical_alias", | ||||||
|                     "content": {"alias": ""}, |                     "content": {"alias": ""}, | ||||||
|                     "state_key": "", |                     "state_key": "", | ||||||
|                     "origin_server_ts": 1417731086799, |                     "origin_server_ts": 1417731086799, | ||||||
|                     "event_id": "66697273743033:example.com" |                     "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/"); | ||||||
|  |  | ||||||
|  | @ -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 New Issue