Skip to content

Commit 77be610

Browse files
committedJun 9, 2019
Initial commit
0 parents  commit 77be610

22 files changed

+2591
-0
lines changed
 

‎.gitignore

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# Miscellaneous
2+
*.class
3+
*.log
4+
*.pyc
5+
*.swp
6+
.DS_Store
7+
.atom/
8+
.buildlog/
9+
.history
10+
.svn/
11+
12+
# IntelliJ related
13+
*.iml
14+
*.ipr
15+
*.iws
16+
.idea/
17+
18+
# Visual Studio Code related
19+
.vscode/
20+
21+
# Flutter/Dart/Pub related
22+
**/doc/api/
23+
.dart_tool/
24+
.flutter-plugins
25+
.packages
26+
.pub-cache/
27+
.pub/
28+
build/
29+
30+
# Android related
31+
**/android/**/gradle-wrapper.jar
32+
**/android/.gradle
33+
**/android/captures/
34+
**/android/gradlew
35+
**/android/gradlew.bat
36+
**/android/local.properties
37+
**/android/**/GeneratedPluginRegistrant.java
38+
39+
# iOS/XCode related
40+
**/ios/**/*.mode1v3
41+
**/ios/**/*.mode2v3
42+
**/ios/**/*.moved-aside
43+
**/ios/**/*.pbxuser
44+
**/ios/**/*.perspectivev3
45+
**/ios/**/*sync/
46+
**/ios/**/.sconsign.dblite
47+
**/ios/**/.tags*
48+
**/ios/**/.vagrant/
49+
**/ios/**/DerivedData/
50+
**/ios/**/Icon?
51+
**/ios/**/Pods/
52+
**/ios/**/.symlinks/
53+
**/ios/**/profile
54+
**/ios/**/xcuserdata
55+
**/ios/.generated/
56+
**/ios/Flutter/App.framework
57+
**/ios/Flutter/Flutter.framework
58+
**/ios/Flutter/Generated.xcconfig
59+
**/ios/Flutter/app.flx
60+
**/ios/Flutter/app.zip
61+
**/ios/Flutter/flutter_assets/
62+
**/ios/ServiceDefinitions.json
63+
**/ios/Runner/GeneratedPluginRegistrant.*
64+
65+
# Exceptions to above rules.
66+
!**/ios/**/default.mode1v3
67+
!**/ios/**/default.mode2v3
68+
!**/ios/**/default.pbxuser
69+
!**/ios/**/default.perspectivev3
70+
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages

‎.gitlab-ci.yml

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
image: cirrusci/flutter
2+
3+
stages:
4+
- coverage
5+
6+
variables:
7+
LC_ALL: "en_US.UTF-8"
8+
LANG: "en_US.UTF-8"
9+
10+
coverage:
11+
stage: coverage
12+
coverage: '/^\s+lines.+: (\d+.\d*%)/'
13+
dependencies: []
14+
script:
15+
- sudo apt-get update -qq && sudo apt-get install -qq apt-transport-https curl gnupg lcov git
16+
- ./scripts/test.sh
17+
- ./scripts/coverage.sh
18+
- flutter pub pub publish --dry-run

‎.metadata

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# This file tracks properties of this Flutter project.
2+
# Used by Flutter tool to assess capabilities and perform upgrades etc.
3+
#
4+
# This file should be version controlled and should not be manually edited.
5+
6+
version:
7+
revision: 7a4c33425ddd78c54aba07d86f3f9a4a0051769b
8+
channel: stable
9+
10+
project_type: package

‎CHANGELOG.md

+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# fluffyfluttermatrix
2+
3+
Dead simple Flutter widget to use Matrix.org in your Flutter app.
4+
5+
## How to use this
6+
7+
1. Use the Matrix widget as root for your widget tree:
8+
9+
```dart
10+
import 'package:flutter/material.dart';
11+
import 'package:fluffyfluttermatrix/fluffyfluttermatrix.dart';
12+
13+
void main() => runApp(MyApp());
14+
15+
class MyApp extends StatelessWidget {
16+
@override
17+
Widget build(BuildContext context) {
18+
return FluffyMatrix(
19+
child: MaterialApp(
20+
title: 'Welcome to Flutter'
21+
),
22+
);
23+
}
24+
}
25+
26+
```
27+
28+
2. Access the MatrixState object by calling Matrix.of with your current BuildContext:
29+
30+
```dart
31+
Client matrix = Matrix.of(context);
32+
```
33+
34+
3. Connect to a Matrix Homeserver and listen to the streams:
35+
36+
```dart
37+
matrix.homeserver = "https://yourhomeserveraddress";
38+
39+
matrix.onLoginStateChanged.stream.listen((bool loginState){
40+
print("LoginState: ${loginState.toString()}");
41+
});
42+
43+
matrix.onEvent.stream.listen((EventUpdate eventUpdate){
44+
print("New event update!");
45+
});
46+
47+
matrix.onRoomUpdate.stream.listen((RoomUpdate eventUpdate){
48+
print("New room update!");
49+
});
50+
51+
final loginResp = await matrix.jsonRequest(
52+
type: "POST",
53+
action: "/client/r0/login",
54+
data: {
55+
"type": "m.login.password",
56+
"user": _usernameController.text,
57+
"password": _passwordController.text,
58+
"initial_device_display_name": "Fluffy Matrix Client"
59+
}
60+
);
61+
62+
matrix.connect(
63+
newToken: loginResp["token"],
64+
newUserID: loginResp["user_id"],
65+
newHomeserver: matrix.homeserver,
66+
newDeviceName: "Fluffy Matrix Client",
67+
newDeviceID: loginResp["device_id"],
68+
newMatrixVersions: ["r0.4.0"],
69+
newLazyLoadMembers: false
70+
);
71+
```
72+
73+
4. Send a message to a Room:
74+
75+
```dart
76+
final resp = await jsonRequest(
77+
type: "PUT",
78+
action: "/r0/rooms/!fjd823j:example.com/send/m.room.message/$txnId",
79+
data: {
80+
"msgtype": "m.text",
81+
"body": "hello"
82+
}
83+
);
84+
```

‎LICENSE

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
TODO: Add your license here.

‎README.md

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# famedlysdk
2+
3+
A new Flutter package.
4+
5+
## Getting Started
6+
7+
This project is a starting point for a Dart
8+
[package](https://flutter.dev/developing-packages/),
9+
a library module containing code that can be shared easily across
10+
multiple Flutter or Dart projects.
11+
12+
For help getting started with Flutter, view our
13+
[online documentation](https://flutter.dev/docs), which offers tutorials,
14+
samples, guidance on mobile development, and a full API reference.

‎lib/famedlysdk.dart

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
library famedlysdk;
2+
3+
export 'package:famedlysdk/src/responses/ErrorResponse.dart';
4+
export 'package:famedlysdk/src/sync/RoomUpdate.dart';
5+
export 'package:famedlysdk/src/sync/EventUpdate.dart';
6+
export 'package:famedlysdk/src/utils/ChatTime.dart';
7+
export 'package:famedlysdk/src/utils/MxContent.dart';
8+
export 'package:famedlysdk/src/Client.dart';
9+
export 'package:famedlysdk/src/Connection.dart';
10+
export 'package:famedlysdk/src/Event.dart';
11+
export 'package:famedlysdk/src/Room.dart';
12+
export 'package:famedlysdk/src/Store.dart';
13+
export 'package:famedlysdk/src/User.dart';

‎lib/src/Client.dart

+169
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
import 'dart:async';
2+
import 'dart:convert';
3+
import 'dart:core';
4+
import 'package:flutter/material.dart';
5+
import 'responses/ErrorResponse.dart';
6+
import 'Connection.dart';
7+
import 'Store.dart';
8+
9+
/// Represents a Matrix connection to communicate with a
10+
/// [Matrix](https://matrix.org) homeserver and is the entry point for this
11+
/// SDK.
12+
class Client {
13+
14+
/// Handles the connection for this client.
15+
Connection connection;
16+
17+
/// Optional persistent store for all data.
18+
Store store;
19+
20+
Client(this.clientName) {
21+
connection = Connection(this);
22+
23+
if (this.clientName != "testclient")
24+
store = Store(this);
25+
connection.onLoginStateChanged.stream.listen((loginState) {
26+
print("LoginState: ${loginState.toString()}");
27+
});
28+
}
29+
30+
/// The required name for this client.
31+
final String clientName;
32+
33+
/// The homeserver this client is communicating with.
34+
String homeserver;
35+
36+
/// The Matrix ID of the current logged user.
37+
String userID;
38+
39+
/// This is the access token for the matrix client. When it is undefined, then
40+
/// the user needs to sign in first.
41+
String accessToken;
42+
43+
/// This points to the position in the synchronization history.
44+
String prevBatch;
45+
46+
/// The device ID is an unique identifier for this device.
47+
String deviceID;
48+
49+
/// The device name is a human readable identifier for this device.
50+
String deviceName;
51+
52+
/// Which version of the matrix specification does this server support?
53+
List<String> matrixVersions;
54+
55+
/// Wheither the server supports lazy load members.
56+
bool lazyLoadMembers = false;
57+
58+
/// Returns the current login state.
59+
bool isLogged() => accessToken != null;
60+
61+
/// Checks the supported versions of the Matrix protocol and the supported
62+
/// login types. Returns false if the server is not compatible with the
63+
/// client. Automatically sets [matrixVersions] and [lazyLoadMembers].
64+
Future<bool> checkServer(serverUrl) async {
65+
homeserver = serverUrl;
66+
67+
final versionResp =
68+
await connection.jsonRequest(type: "GET", action: "/client/versions");
69+
if (versionResp is ErrorResponse) {
70+
connection.onError.add(ErrorResponse(errcode: "NO_RESPONSE", error: ""));
71+
return false;
72+
}
73+
74+
final List<String> versions = List<String>.from(versionResp["versions"]);
75+
76+
if (versions == null) {
77+
connection.onError.add(ErrorResponse(errcode: "NO_RESPONSE", error: ""));
78+
return false;
79+
}
80+
81+
for (int i = 0; i < versions.length; i++) {
82+
if (versions[i] == "r0.4.0")
83+
break;
84+
else if (i == versions.length - 1) {
85+
connection.onError.add(ErrorResponse(errcode: "NO_SUPPORT", error: ""));
86+
return false;
87+
}
88+
}
89+
90+
matrixVersions = versions;
91+
92+
if (versionResp.containsKey("unstable_features") &&
93+
versionResp["unstable_features"].containsKey("m.lazy_load_members")) {
94+
lazyLoadMembers = versionResp["unstable_features"]["m.lazy_load_members"]
95+
? true
96+
: false;
97+
}
98+
99+
final loginResp =
100+
await connection.jsonRequest(type: "GET", action: "/client/r0/login");
101+
if (loginResp is ErrorResponse) {
102+
connection.onError.add(loginResp);
103+
return false;
104+
}
105+
106+
final List<dynamic> flows = loginResp["flows"];
107+
108+
for (int i = 0; i < flows.length; i++) {
109+
if (flows[i].containsKey("type") &&
110+
flows[i]["type"] == "m.login.password")
111+
break;
112+
else if (i == flows.length - 1) {
113+
connection.onError.add(ErrorResponse(errcode: "NO_SUPPORT", error: ""));
114+
return false;
115+
}
116+
}
117+
118+
return true;
119+
}
120+
121+
/// Handles the login and allows the client to call all APIs which require
122+
/// authentication. Returns false if the login was not successful.
123+
Future<bool> login(String username, String password) async {
124+
125+
final loginResp =
126+
await connection.jsonRequest(type: "POST", action: "/client/r0/login", data: {
127+
"type": "m.login.password",
128+
"user": username,
129+
"identifier": {
130+
"type": "m.id.user",
131+
"user": username,
132+
},
133+
"password": password,
134+
"initial_device_display_name": "Famedly Talk"
135+
});
136+
137+
if (loginResp is ErrorResponse) {
138+
connection.onError.add(loginResp);
139+
return false;
140+
}
141+
142+
final userID = loginResp["user_id"];
143+
final accessToken = loginResp["access_token"];
144+
if (userID == null || accessToken == null) {
145+
connection.onError.add(ErrorResponse(errcode: "NO_SUPPORT", error: ""));
146+
}
147+
148+
await connection.connect(
149+
newToken: accessToken,
150+
newUserID: userID,
151+
newHomeserver: homeserver,
152+
newDeviceName: "",
153+
newDeviceID: "",
154+
newMatrixVersions: matrixVersions,
155+
newLazyLoadMembers: lazyLoadMembers);
156+
return true;
157+
}
158+
159+
/// Sends a logout command to the homeserver and clears all local data,
160+
/// including all persistent data from the store.
161+
Future<void> logout() async {
162+
final dynamic resp =
163+
await connection.jsonRequest(type: "POST", action: "/client/r0/logout/all");
164+
if (resp == null) return;
165+
166+
await connection.clear();
167+
}
168+
169+
}

‎lib/src/Connection.dart

+415
Large diffs are not rendered by default.

‎lib/src/Event.dart

+120
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import 'dart:convert';
2+
import './User.dart';
3+
import 'package:famedlysdk/src/utils/ChatTime.dart';
4+
import 'package:famedlysdk/src/Client.dart';
5+
6+
class Event {
7+
final String id;
8+
final String roomID;
9+
final ChatTime time;
10+
final User sender;
11+
final User stateKey;
12+
final String environment;
13+
final String text;
14+
final String formattedText;
15+
final int status;
16+
final Map<String,dynamic> content;
17+
18+
const Event(this.id, this.sender, this.time,{
19+
this.roomID,
20+
this.stateKey,
21+
this.text,
22+
this.formattedText,
23+
this.status = 2,
24+
this.environment = "timeline",
25+
this.content,
26+
});
27+
28+
String getBody () => formattedText ?? text ?? "*** Unable to parse Content ***";
29+
30+
EventTypes get type {
31+
switch (environment) {
32+
case "m.room.avatar": return EventTypes.RoomAvatar;
33+
case "m.room.name": return EventTypes.RoomName;
34+
case "m.room.topic": return EventTypes.RoomTopic;
35+
case "m.room.Aliases": return EventTypes.RoomAliases;
36+
case "m.room.canonical_alias": return EventTypes.RoomCanonicalAlias;
37+
case "m.room.create": return EventTypes.RoomCreate;
38+
case "m.room.join_rules": return EventTypes.RoomJoinRules;
39+
case "m.room.member": return EventTypes.RoomMember;
40+
case "m.room.power_levels": return EventTypes.RoomPowerLevels;
41+
case "m.room.message":
42+
switch(content["msgtype"] ?? "m.text") {
43+
case "m.text": return EventTypes.Text;
44+
case "m.notice": return EventTypes.Notice;
45+
case "m.emote": return EventTypes.Emote;
46+
case "m.image": return EventTypes.Image;
47+
case "m.video": return EventTypes.Video;
48+
case "m.audio": return EventTypes.Audio;
49+
case "m.file": return EventTypes.File;
50+
case "m.location": return EventTypes.Location;
51+
}
52+
}
53+
54+
}
55+
56+
static Event fromJson(Map<String, dynamic> jsonObj) {
57+
Map<String,dynamic> content;
58+
try {
59+
content = json.decode(jsonObj["content_json"]);
60+
} catch(e) {
61+
print("jsonObj decode of event content failed: ${e.toString()}");
62+
content = {};
63+
}
64+
return Event(
65+
jsonObj["id"],
66+
User.fromJson(jsonObj),
67+
ChatTime(jsonObj["origin_server_ts"]),
68+
stateKey: User(jsonObj["state_key"]),
69+
environment: jsonObj["type"],
70+
text: jsonObj["content_body"],
71+
status: jsonObj["status"],
72+
content: content,
73+
);
74+
}
75+
76+
static Future<List<Event>> getEventList(Client matrix, String roomID) async{
77+
List<Map<String, dynamic>> eventRes = await matrix.store.db.rawQuery(
78+
"SELECT * " +
79+
" FROM Events events, Memberships memberships " +
80+
" WHERE events.chat_id=?" +
81+
" AND events.sender=memberships.matrix_id " +
82+
" GROUP BY events.id " +
83+
" ORDER BY origin_server_ts DESC",
84+
[roomID]);
85+
86+
List<Event> eventList = [];
87+
88+
for (num i = 0; i < eventRes.length; i++)
89+
eventList.add(Event.fromJson(eventRes[i]));
90+
return eventList;
91+
}
92+
93+
}
94+
95+
enum EventTypes {
96+
Text,
97+
Emote,
98+
Notice,
99+
Image,
100+
Video,
101+
Audio,
102+
File,
103+
Location,
104+
RoomAliases,
105+
RoomCanonicalAlias,
106+
RoomCreate,
107+
RoomJoinRules,
108+
RoomMember,
109+
RoomPowerLevels,
110+
RoomName,
111+
RoomTopic,
112+
RoomAvatar,
113+
}
114+
115+
final Map<String,int> StatusTypes = {
116+
"ERROR": -1,
117+
"SENDING": 0,
118+
"SENT": 1,
119+
"RECEIVED": 2,
120+
};

‎lib/src/Room.dart

+197
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
import 'dart:convert';
2+
import 'package:famedlysdk/src/Client.dart';
3+
import 'package:famedlysdk/src/utils/ChatTime.dart';
4+
import 'package:famedlysdk/src/utils/MxContent.dart';
5+
import 'package:famedlysdk/src/responses/ErrorResponse.dart';
6+
import './User.dart';
7+
import 'package:famedlysdk/src/Event.dart';
8+
9+
/// FIXME use actual Matrix Stuff. This is a placeholder
10+
class Room {
11+
final String roomID;
12+
String name;
13+
String lastMessage;
14+
MxContent avatar;
15+
ChatTime timeCreated;
16+
int notificationCount;
17+
int highlightCount;
18+
String topic;
19+
User user;
20+
final Client matrix;
21+
List<Event> events = [];
22+
23+
Room({
24+
this.roomID,
25+
this.name,
26+
this.lastMessage,
27+
this.avatar,
28+
this.timeCreated,
29+
this.notificationCount,
30+
this.highlightCount,
31+
this.topic,
32+
this.user,
33+
this.matrix,
34+
this.events,
35+
});
36+
37+
String get status {
38+
if (this.user != null) {
39+
return this.user.status;
40+
}
41+
return this.topic;
42+
}
43+
44+
Future<dynamic> setName(String newName) async{
45+
dynamic res = await matrix.connection.jsonRequest(
46+
type: "PUT",
47+
action:
48+
"/client/r0/rooms/${roomID}/send/m.room.name/${new DateTime.now()}",
49+
data: {"name": newName});
50+
if (res is ErrorResponse) matrix.connection.onError.add(res);
51+
return res;
52+
}
53+
54+
Future<dynamic> setDescription(String newName) async{
55+
dynamic res = await matrix.connection.jsonRequest(
56+
type: "PUT",
57+
action:
58+
"/client/r0/rooms/${roomID}/send/m.room.topic/${new DateTime.now()}",
59+
data: {"topic": newName});
60+
if (res is ErrorResponse) matrix.connection.onError.add(res);
61+
return res;
62+
}
63+
64+
Stream<List<Event>> get eventsStream {
65+
return Stream<List<Event>>.fromIterable(Iterable<List<Event>>.generate(
66+
this.events.length, (int index) => this.events)).asBroadcastStream();
67+
}
68+
69+
Future<void> sendText(String message) async {
70+
dynamic res = await matrix.connection.jsonRequest(
71+
type: "PUT",
72+
action:
73+
"/client/r0/rooms/${roomID}/send/m.room.message/${new DateTime.now()}",
74+
data: {"msgtype": "m.text", "body": message});
75+
if (res["errcode"] == "M_LIMIT_EXCEEDED") matrix.connection.onError.add(res["error"]);
76+
}
77+
78+
Future<dynamic> leave() async {
79+
dynamic res = await matrix.connection.jsonRequest(
80+
type: "POST",
81+
action:
82+
"/client/r0/rooms/${roomID}/leave");
83+
if (res is ErrorResponse) matrix.connection.onError.add(res);
84+
return res;
85+
}
86+
87+
Future<dynamic> forget() async {
88+
dynamic res = await matrix.connection.jsonRequest(
89+
type: "POST",
90+
action:
91+
"/client/r0/rooms/${roomID}/forget");
92+
if (res is ErrorResponse) matrix.connection.onError.add(res);
93+
return res;
94+
}
95+
96+
Future<dynamic> kick(String userID) async {
97+
dynamic res = await matrix.connection.jsonRequest(
98+
type: "POST",
99+
action:
100+
"/client/r0/rooms/${roomID}/kick",
101+
data: {"user_id": userID});
102+
if (res is ErrorResponse) matrix.connection.onError.add(res);
103+
return res;
104+
}
105+
106+
Future<dynamic> ban(String userID) async {
107+
dynamic res = await matrix.connection.jsonRequest(
108+
type: "POST",
109+
action:
110+
"/client/r0/rooms/${roomID}/ban",
111+
data: {"user_id": userID});
112+
if (res is ErrorResponse) matrix.connection.onError.add(res);
113+
return res;
114+
}
115+
116+
Future<dynamic> unban(String userID) async {
117+
dynamic res = await matrix.connection.jsonRequest(
118+
type: "POST",
119+
action:
120+
"/client/r0/rooms/${roomID}/unban",
121+
data: {"user_id": userID});
122+
if (res is ErrorResponse) matrix.connection.onError.add(res);
123+
return res;
124+
}
125+
126+
Future<dynamic> invite(String userID) async {
127+
dynamic res = await matrix.connection.jsonRequest(
128+
type: "POST",
129+
action:
130+
"/client/r0/rooms/${roomID}/invite",
131+
data: {"user_id": userID});
132+
if (res is ErrorResponse) matrix.connection.onError.add(res);
133+
return res;
134+
}
135+
136+
static Future<Room> getRoomFromTableRow(
137+
Map<String, dynamic> row, Client matrix) async {
138+
String name = row["topic"];
139+
if (name == "") name = await matrix.store.getChatNameFromMemberNames(row["id"]);
140+
141+
String content_body = row["content_body"];
142+
if (content_body == null || content_body == "")
143+
content_body = "Keine vorhergehenden Nachrichten";
144+
145+
String avatarMxcUrl = row["avatar_url"];
146+
147+
if (avatarMxcUrl == "")
148+
avatarMxcUrl = await matrix.store.getAvatarFromSingleChat(row["id"]);
149+
150+
return Room(
151+
roomID: row["id"],
152+
name: name,
153+
lastMessage: content_body,
154+
avatar: MxContent(avatarMxcUrl),
155+
timeCreated: ChatTime(row["origin_server_ts"]),
156+
notificationCount: row["notification_count"],
157+
highlightCount: row["highlight_count"],
158+
topic: "",
159+
matrix: matrix,
160+
events: [],
161+
);
162+
}
163+
164+
static Future<Room> getRoomById(String id, Client matrix) async {
165+
List<Map<String, dynamic>> res =
166+
await matrix.store.db.rawQuery("SELECT * FROM Chats WHERE id=?", [id]);
167+
if (res.length != 1) return null;
168+
return getRoomFromTableRow(res[0], matrix);
169+
}
170+
171+
static Future<Room> loadRoomEvents(String id, Client matrix) async {
172+
Room room = await Room.getRoomById(id, matrix);
173+
room.events = await Event.getEventList(matrix, id);
174+
return room;
175+
}
176+
177+
Future<List<User>> requestParticipants(Client matrix) async {
178+
List<User> participants = [];
179+
180+
dynamic res = await matrix.connection.jsonRequest(
181+
type: "GET", action: "/client/r0/rooms/${roomID}/members");
182+
if (res is ErrorResponse || !(res["chunk"] is List<dynamic>))
183+
return participants;
184+
185+
for (num i = 0; i < res["chunk"].length; i++) {
186+
User newUser = User(res["chunk"][i]["state_key"],
187+
displayName: res["chunk"][i]["content"]["displayname"] ?? "",
188+
status: res["chunk"][i]["content"]["membership"] ?? "",
189+
directChatRoomId: "",
190+
avatar_url:
191+
MxContent(res["chunk"][i]["content"]["avatar_url"] ?? ""));
192+
if (newUser.status != "leave") participants.add(newUser);
193+
}
194+
195+
return participants;
196+
}
197+
}

‎lib/src/Store.dart

+516
Large diffs are not rendered by default.

‎lib/src/User.dart

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import 'package:famedlysdk/src/Client.dart';
2+
import 'package:famedlysdk/src/utils/MxContent.dart';
3+
import 'package:famedlysdk/src/Room.dart';
4+
5+
class User {
6+
final String status;
7+
final String mxid;
8+
final String displayName;
9+
final MxContent avatar_url;
10+
final String directChatRoomId;
11+
final Room room;
12+
13+
const User(
14+
this.mxid, {
15+
this.status,
16+
this.displayName,
17+
this.avatar_url,
18+
this.directChatRoomId,
19+
this.room,
20+
});
21+
22+
String calcDisplayname() => displayName.isEmpty
23+
? mxid.replaceFirst("@", "").split(":")[0]
24+
: displayName;
25+
26+
static User fromJson(Map<String, dynamic> json) {
27+
return User(json['matrix_id'],
28+
displayName: json['displayname'],
29+
avatar_url: MxContent(json['avatar_url']),
30+
status: "",
31+
directChatRoomId: "");
32+
}
33+
}

‎lib/src/responses/ErrorResponse.dart

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/// Represents a special response from the Homeserver for errors.
2+
class ErrorResponse {
3+
4+
/// The unique identifier for this error.
5+
String errcode;
6+
7+
/// A human readable error description.
8+
String error;
9+
10+
ErrorResponse({this.errcode, this.error});
11+
12+
ErrorResponse.fromJson(Map<String, dynamic> json) {
13+
errcode = json['errcode'];
14+
error = json['error'] ?? "";
15+
}
16+
17+
Map<String, dynamic> toJson() {
18+
final Map<String, dynamic> data = new Map<String, dynamic>();
19+
data['errcode'] = this.errcode;
20+
data['error'] = this.error;
21+
return data;
22+
}
23+
}

‎lib/src/sync/EventUpdate.dart

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/// Represents a new event (e.g. a message in a room) or an update for an
2+
/// already known event.
3+
class EventUpdate {
4+
5+
/// Usually 'timeline', 'state' or whatever.
6+
final String eventType;
7+
8+
/// Most events belong to a room. If not, this equals to eventType.
9+
final String roomID;
10+
11+
/// See (Matrix Room Events)[https://matrix.org/docs/spec/client_server/r0.4.0.html#room-events]
12+
/// and (Matrix Events)[https://matrix.org/docs/spec/client_server/r0.4.0.html#id89] for more
13+
/// informations.
14+
final String type;
15+
16+
// The json payload of the content of this event.
17+
final dynamic content;
18+
19+
EventUpdate({this.eventType, this.roomID, this.type, this.content});
20+
}

‎lib/src/sync/RoomUpdate.dart

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/// Represents a new room or an update for an
2+
/// already known room.
3+
class RoomUpdate {
4+
5+
/// All rooms have an idea in the format: !uniqueid:server.abc
6+
final String id;
7+
8+
/// The current membership state of the user in this room.
9+
final String membership;
10+
11+
/// Represents the number of unead notifications. This probably doesn't fit the number
12+
/// of unread messages.
13+
final num notification_count;
14+
15+
// The number of unread highlighted notifications.
16+
final num highlight_count;
17+
18+
/// If there are too much new messages, the [homeserver] will only send the
19+
/// last X (default is 10) messages and set the [limitedTimelinbe] flag to true.
20+
final bool limitedTimeline;
21+
22+
/// Represents the current position of the client in the room history.
23+
final String prev_batch;
24+
25+
RoomUpdate({
26+
this.id,
27+
this.membership,
28+
this.notification_count,
29+
this.highlight_count,
30+
this.limitedTimeline,
31+
this.prev_batch,
32+
});
33+
}

‎lib/src/utils/ChatTime.dart

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import 'package:intl/intl.dart';
2+
3+
class ChatTime {
4+
DateTime dateTime = DateTime.now();
5+
6+
ChatTime(num ts) {
7+
if (ts != null)
8+
dateTime = DateTime.fromMicrosecondsSinceEpoch(ts * 1000);
9+
}
10+
11+
ChatTime.now() {
12+
dateTime = DateTime.now();
13+
}
14+
15+
String toString() {
16+
DateTime now = DateTime.now();
17+
18+
bool sameYear = now.year == dateTime.year;
19+
20+
bool sameDay =
21+
sameYear && now.month == dateTime.month && now.day == dateTime.day;
22+
23+
bool sameWeek = sameYear && !sameDay && now.millisecondsSinceEpoch - dateTime.millisecondsSinceEpoch < 1000*60*60*24*7;
24+
25+
if (sameDay) {
26+
return toTimeString();
27+
} else if (sameWeek) {
28+
switch (dateTime.weekday) { // TODO: Needs localization
29+
case 1:
30+
return "Montag";
31+
case 2:
32+
return "Dienstag";
33+
case 3:
34+
return "Mittwoch";
35+
case 4:
36+
return "Donnerstag";
37+
case 5:
38+
return "Freitag";
39+
case 6:
40+
return "Samstag";
41+
case 7:
42+
return "Sonntag";
43+
}
44+
} else if (sameYear) {
45+
return DateFormat('dd.MM').format(dateTime);
46+
} else {
47+
return DateFormat('dd.MM.yyyy').format(dateTime);
48+
}
49+
}
50+
51+
num toTimeStamp() {
52+
return dateTime.microsecondsSinceEpoch;
53+
}
54+
55+
bool sameEnvironment(ChatTime prevTime) {
56+
return toTimeStamp() - prevTime.toTimeStamp() < 1000*60*5;
57+
}
58+
59+
String toTimeString() {
60+
return DateFormat('HH:mm').format(dateTime);
61+
}
62+
63+
String toEventTimeString() {
64+
DateTime now = DateTime.now();
65+
66+
bool sameYear = now.year == dateTime.year;
67+
68+
bool sameDay =
69+
sameYear && now.month == dateTime.month && now.day == dateTime.day;
70+
71+
if (sameDay) return toTimeString();
72+
return "${toString()}, ${DateFormat('HH:mm').format(dateTime)}";
73+
}
74+
}

‎lib/src/utils/MxContent.dart

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import 'package:famedlysdk/src/Client.dart';
2+
import 'dart:core';
3+
4+
class MxContent {
5+
6+
final String _mxc;
7+
8+
MxContent(this._mxc);
9+
10+
get mxc => _mxc;
11+
12+
getDownloadLink (Client matrix) => "https://${matrix.homeserver}/_matrix/media/r0/download/${_mxc.replaceFirst("mxc://","")}/";
13+
14+
getThumbnail (Client matrix, {num width, num height, ThumbnailMethod method}) {
15+
String methodStr = "crop";
16+
if (method == ThumbnailMethod.scale) methodStr = "scale";
17+
width = width.round();
18+
height = height.round();
19+
return "${matrix.homeserver}/_matrix/media/r0/thumbnail/${_mxc.replaceFirst("mxc://","")}?width=$width&height=$height&method=$methodStr";
20+
}
21+
22+
}
23+
24+
enum ThumbnailMethod {crop, scale}

‎pubspec.lock

+287
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,287 @@
1+
# Generated by pub
2+
# See https://www.dartlang.org/tools/pub/glossary#lockfile
3+
packages:
4+
analyzer:
5+
dependency: transitive
6+
description:
7+
name: analyzer
8+
url: "https://pub.dartlang.org"
9+
source: hosted
10+
version: "0.36.3"
11+
args:
12+
dependency: transitive
13+
description:
14+
name: args
15+
url: "https://pub.dartlang.org"
16+
source: hosted
17+
version: "1.5.2"
18+
async:
19+
dependency: transitive
20+
description:
21+
name: async
22+
url: "https://pub.dartlang.org"
23+
source: hosted
24+
version: "2.1.0"
25+
boolean_selector:
26+
dependency: transitive
27+
description:
28+
name: boolean_selector
29+
url: "https://pub.dartlang.org"
30+
source: hosted
31+
version: "1.0.4"
32+
charcode:
33+
dependency: transitive
34+
description:
35+
name: charcode
36+
url: "https://pub.dartlang.org"
37+
source: hosted
38+
version: "1.1.2"
39+
collection:
40+
dependency: transitive
41+
description:
42+
name: collection
43+
url: "https://pub.dartlang.org"
44+
source: hosted
45+
version: "1.14.11"
46+
convert:
47+
dependency: transitive
48+
description:
49+
name: convert
50+
url: "https://pub.dartlang.org"
51+
source: hosted
52+
version: "2.1.1"
53+
crypto:
54+
dependency: transitive
55+
description:
56+
name: crypto
57+
url: "https://pub.dartlang.org"
58+
source: hosted
59+
version: "2.0.6"
60+
csslib:
61+
dependency: transitive
62+
description:
63+
name: csslib
64+
url: "https://pub.dartlang.org"
65+
source: hosted
66+
version: "0.16.0"
67+
dart_style:
68+
dependency: transitive
69+
description:
70+
name: dart_style
71+
url: "https://pub.dartlang.org"
72+
source: hosted
73+
version: "1.2.7"
74+
flutter:
75+
dependency: "direct main"
76+
description: flutter
77+
source: sdk
78+
version: "0.0.0"
79+
flutter_test:
80+
dependency: "direct dev"
81+
description: flutter
82+
source: sdk
83+
version: "0.0.0"
84+
front_end:
85+
dependency: transitive
86+
description:
87+
name: front_end
88+
url: "https://pub.dartlang.org"
89+
source: hosted
90+
version: "0.1.18"
91+
glob:
92+
dependency: transitive
93+
description:
94+
name: glob
95+
url: "https://pub.dartlang.org"
96+
source: hosted
97+
version: "1.1.7"
98+
html:
99+
dependency: transitive
100+
description:
101+
name: html
102+
url: "https://pub.dartlang.org"
103+
source: hosted
104+
version: "0.14.0+2"
105+
http:
106+
dependency: "direct main"
107+
description:
108+
name: http
109+
url: "https://pub.dartlang.org"
110+
source: hosted
111+
version: "0.12.0+2"
112+
http_parser:
113+
dependency: transitive
114+
description:
115+
name: http_parser
116+
url: "https://pub.dartlang.org"
117+
source: hosted
118+
version: "3.1.3"
119+
intl:
120+
dependency: transitive
121+
description:
122+
name: intl
123+
url: "https://pub.dartlang.org"
124+
source: hosted
125+
version: "0.15.8"
126+
intl_translation:
127+
dependency: "direct main"
128+
description:
129+
name: intl_translation
130+
url: "https://pub.dartlang.org"
131+
source: hosted
132+
version: "0.17.5"
133+
kernel:
134+
dependency: transitive
135+
description:
136+
name: kernel
137+
url: "https://pub.dartlang.org"
138+
source: hosted
139+
version: "0.3.18"
140+
matcher:
141+
dependency: transitive
142+
description:
143+
name: matcher
144+
url: "https://pub.dartlang.org"
145+
source: hosted
146+
version: "0.12.5"
147+
meta:
148+
dependency: transitive
149+
description:
150+
name: meta
151+
url: "https://pub.dartlang.org"
152+
source: hosted
153+
version: "1.1.6"
154+
package_config:
155+
dependency: transitive
156+
description:
157+
name: package_config
158+
url: "https://pub.dartlang.org"
159+
source: hosted
160+
version: "1.0.5"
161+
path:
162+
dependency: transitive
163+
description:
164+
name: path
165+
url: "https://pub.dartlang.org"
166+
source: hosted
167+
version: "1.6.2"
168+
pedantic:
169+
dependency: transitive
170+
description:
171+
name: pedantic
172+
url: "https://pub.dartlang.org"
173+
source: hosted
174+
version: "1.5.0"
175+
petitparser:
176+
dependency: transitive
177+
description:
178+
name: petitparser
179+
url: "https://pub.dartlang.org"
180+
source: hosted
181+
version: "2.2.1"
182+
pub_semver:
183+
dependency: transitive
184+
description:
185+
name: pub_semver
186+
url: "https://pub.dartlang.org"
187+
source: hosted
188+
version: "1.4.2"
189+
quiver:
190+
dependency: transitive
191+
description:
192+
name: quiver
193+
url: "https://pub.dartlang.org"
194+
source: hosted
195+
version: "2.0.2"
196+
sky_engine:
197+
dependency: transitive
198+
description: flutter
199+
source: sdk
200+
version: "0.0.99"
201+
source_span:
202+
dependency: transitive
203+
description:
204+
name: source_span
205+
url: "https://pub.dartlang.org"
206+
source: hosted
207+
version: "1.5.5"
208+
sqflite:
209+
dependency: "direct main"
210+
description:
211+
name: sqflite
212+
url: "https://pub.dartlang.org"
213+
source: hosted
214+
version: "1.1.5"
215+
stack_trace:
216+
dependency: transitive
217+
description:
218+
name: stack_trace
219+
url: "https://pub.dartlang.org"
220+
source: hosted
221+
version: "1.9.3"
222+
stream_channel:
223+
dependency: transitive
224+
description:
225+
name: stream_channel
226+
url: "https://pub.dartlang.org"
227+
source: hosted
228+
version: "2.0.0"
229+
string_scanner:
230+
dependency: transitive
231+
description:
232+
name: string_scanner
233+
url: "https://pub.dartlang.org"
234+
source: hosted
235+
version: "1.0.4"
236+
synchronized:
237+
dependency: transitive
238+
description:
239+
name: synchronized
240+
url: "https://pub.dartlang.org"
241+
source: hosted
242+
version: "2.1.0"
243+
term_glyph:
244+
dependency: transitive
245+
description:
246+
name: term_glyph
247+
url: "https://pub.dartlang.org"
248+
source: hosted
249+
version: "1.1.0"
250+
test_api:
251+
dependency: transitive
252+
description:
253+
name: test_api
254+
url: "https://pub.dartlang.org"
255+
source: hosted
256+
version: "0.2.4"
257+
typed_data:
258+
dependency: transitive
259+
description:
260+
name: typed_data
261+
url: "https://pub.dartlang.org"
262+
source: hosted
263+
version: "1.1.6"
264+
vector_math:
265+
dependency: transitive
266+
description:
267+
name: vector_math
268+
url: "https://pub.dartlang.org"
269+
source: hosted
270+
version: "2.0.8"
271+
watcher:
272+
dependency: transitive
273+
description:
274+
name: watcher
275+
url: "https://pub.dartlang.org"
276+
source: hosted
277+
version: "0.9.7+10"
278+
yaml:
279+
dependency: transitive
280+
description:
281+
name: yaml
282+
url: "https://pub.dartlang.org"
283+
source: hosted
284+
version: "2.1.15"
285+
sdks:
286+
dart: ">=2.2.0 <3.0.0"
287+
flutter: ">=1.2.1 <2.0.0"

‎pubspec.yaml

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
name: famedlysdk
2+
description: Matrix SDK for the famedly talk app written in dart.
3+
version: 0.0.1
4+
author: famedly
5+
homepage: https://famedly.com
6+
7+
environment:
8+
sdk: ">=2.1.0 <3.0.0"
9+
10+
dependencies:
11+
flutter:
12+
sdk: flutter
13+
14+
# Database
15+
sqflite: ^1.1.0
16+
17+
# Connection
18+
http: ^0.12.0+2
19+
20+
# Time formatting
21+
intl_translation: ^0.17.1
22+
23+
dev_dependencies:
24+
flutter_test:
25+
sdk: flutter
26+
27+
# For information on the generic Dart part of this file, see the
28+
# following page: https://www.dartlang.org/tools/pub/pubspec
29+
30+
# The following section is specific to Flutter.
31+
flutter:
32+
33+
# To add assets to your package, add an assets section, like this:
34+
# assets:
35+
# - images/a_dot_burr.jpeg
36+
# - images/a_dot_ham.jpeg
37+
#
38+
# For details regarding assets in packages, see
39+
# https://flutter.dev/assets-and-images/#from-packages
40+
#
41+
# An image asset can refer to one or more resolution-specific "variants", see
42+
# https://flutter.dev/assets-and-images/#resolution-aware.
43+
44+
# To add custom fonts to your package, add a fonts section here,
45+
# in this "flutter" section. Each entry in this list should have a
46+
# "family" key with the font family name, and a "fonts" key with a
47+
# list giving the asset and other descriptors for the font. For
48+
# example:
49+
# fonts:
50+
# - family: Schyler
51+
# fonts:
52+
# - asset: fonts/Schyler-Regular.ttf
53+
# - asset: fonts/Schyler-Italic.ttf
54+
# style: italic
55+
# - family: Trajan Pro
56+
# fonts:
57+
# - asset: fonts/TrajanPro.ttf
58+
# - asset: fonts/TrajanPro_Bold.ttf
59+
# weight: 700
60+
#
61+
# For details regarding fonts in packages, see
62+
# https://flutter.dev/custom-fonts/#from-packages

‎test/Client_test.dart

+216
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
import 'package:flutter_test/flutter_test.dart';
2+
import 'package:famedlysdk/src/Client.dart';
3+
import 'package:famedlysdk/src/Connection.dart';
4+
import 'package:famedlysdk/src/sync/EventUpdate.dart';
5+
import 'package:famedlysdk/src/sync/RoomUpdate.dart';
6+
import 'package:famedlysdk/src/responses/ErrorResponse.dart';
7+
import 'dart:async';
8+
import 'FakeMatrixApi.dart';
9+
10+
void main() {
11+
Client matrix;
12+
13+
Future<List<RoomUpdate>> roomUpdateListFuture;
14+
Future<List<EventUpdate>> eventUpdateListFuture;
15+
16+
/// All Tests related to the Login
17+
group("FluffyMatrix", () {
18+
/// Check if all Elements get created
19+
20+
final create = (WidgetTester tester) {
21+
22+
matrix = Client("testclient");
23+
matrix.connection.httpClient = FakeMatrixApi();
24+
matrix.homeserver = "https://fakeServer.notExisting";
25+
26+
roomUpdateListFuture = matrix.connection.onRoomUpdate.stream.toList();
27+
eventUpdateListFuture = matrix.connection.onEvent.stream.toList();
28+
};
29+
testWidgets('should get created', create);
30+
31+
test("Get version", () async {
32+
final versionResp =
33+
await matrix.connection.jsonRequest(type: "GET", action: "/client/versions");
34+
expect(versionResp is ErrorResponse, false);
35+
expect(versionResp["versions"].indexOf("r0.4.0") != -1, true);
36+
matrix.matrixVersions = List<String>.from(versionResp["versions"]);
37+
matrix.lazyLoadMembers = true;
38+
});
39+
40+
test("Get login types", () async {
41+
final resp =
42+
await matrix.connection.jsonRequest(type: "GET", action: "/client/r0/login");
43+
expect(resp is ErrorResponse, false);
44+
expect(resp["flows"] is List<dynamic>, true);
45+
bool hasMLoginType = false;
46+
for (int i = 0; i < resp["flows"].length; i++)
47+
if (resp["flows"][i]["type"] is String &&
48+
resp["flows"][i]["type"] == "m.login.password") {
49+
hasMLoginType = true;
50+
break;
51+
}
52+
expect(hasMLoginType, true);
53+
});
54+
55+
final loginText = () async{
56+
final resp = await matrix
57+
.connection.jsonRequest(type: "POST", action: "/client/r0/login", data: {
58+
"type": "m.login.password",
59+
"user": "test",
60+
"password": "1234",
61+
"initial_device_display_name": "Fluffy Matrix Client"
62+
});
63+
expect(resp is ErrorResponse, false);
64+
65+
Future<LoginState> loginStateFuture = matrix.connection.onLoginStateChanged.stream.first;
66+
Future<bool> firstSyncFuture = matrix.connection.onFirstSync.stream.first;
67+
Future<dynamic> syncFuture = matrix.connection.onSync.stream.first;
68+
69+
matrix.connection.connect(
70+
newToken: resp["access_token"],
71+
newUserID: resp["user_id"],
72+
newHomeserver: matrix.homeserver,
73+
newDeviceName: "Text Matrix Client",
74+
newDeviceID: resp["device_id"],
75+
newMatrixVersions: matrix.matrixVersions,
76+
newLazyLoadMembers: matrix.lazyLoadMembers);
77+
78+
expect(matrix.accessToken == resp["access_token"], true);
79+
expect(matrix.deviceName == "Text Matrix Client", true);
80+
expect(matrix.deviceID == resp["device_id"], true);
81+
expect(matrix.userID == resp["user_id"], true);
82+
83+
LoginState loginState = await loginStateFuture;
84+
bool firstSync = await firstSyncFuture;
85+
dynamic sync = await syncFuture;
86+
87+
expect(loginState, LoginState.logged);
88+
expect(firstSync, true);
89+
expect(sync["next_batch"] == matrix.prevBatch, true);
90+
};
91+
92+
test('Login', loginText);
93+
94+
test('Try to get ErrorResponse', () async{
95+
final resp = await matrix
96+
.connection.jsonRequest(type: "PUT", action: "/non/existing/path");
97+
expect(resp is ErrorResponse, true);
98+
});
99+
100+
test('Logout', () async{
101+
final dynamic resp = await matrix
102+
.connection.jsonRequest(type: "POST", action: "/client/r0/logout");
103+
expect(resp is ErrorResponse, false);
104+
105+
Future<LoginState> loginStateFuture = matrix.connection.onLoginStateChanged.stream.first;
106+
107+
matrix.connection.clear();
108+
109+
expect(matrix.accessToken == null, true);
110+
expect(matrix.homeserver == null, true);
111+
expect(matrix.userID == null, true);
112+
expect(matrix.deviceID == null, true);
113+
expect(matrix.deviceName == null, true);
114+
expect(matrix.matrixVersions == null, true);
115+
expect(matrix.lazyLoadMembers == null, true);
116+
expect(matrix.prevBatch == null, true);
117+
118+
LoginState loginState = await loginStateFuture;
119+
expect(loginState, LoginState.loggedOut);
120+
});
121+
122+
test('Room Update Test', () async{
123+
matrix.connection.onRoomUpdate.close();
124+
125+
List<RoomUpdate> roomUpdateList = await roomUpdateListFuture;
126+
127+
expect(roomUpdateList.length,3);
128+
129+
expect(roomUpdateList[0].id=="!726s6s6q:example.com", true);
130+
expect(roomUpdateList[0].membership=="join", true);
131+
expect(roomUpdateList[0].prev_batch=="t34-23535_0_0", true);
132+
expect(roomUpdateList[0].limitedTimeline==true, true);
133+
expect(roomUpdateList[0].notification_count==2, true);
134+
expect(roomUpdateList[0].highlight_count==2, true);
135+
136+
expect(roomUpdateList[1].id=="!696r7674:example.com", true);
137+
expect(roomUpdateList[1].membership=="invite", true);
138+
expect(roomUpdateList[1].prev_batch=="", true);
139+
expect(roomUpdateList[1].limitedTimeline==false, true);
140+
expect(roomUpdateList[1].notification_count==0, true);
141+
expect(roomUpdateList[1].highlight_count==0, true);
142+
143+
expect(roomUpdateList[2].id=="!5345234234:example.com", true);
144+
expect(roomUpdateList[2].membership=="leave", true);
145+
expect(roomUpdateList[2].prev_batch=="", true);
146+
expect(roomUpdateList[2].limitedTimeline==false, true);
147+
expect(roomUpdateList[2].notification_count==0, true);
148+
expect(roomUpdateList[2].highlight_count==0, true);
149+
});
150+
151+
test('Event Update Test', () async{
152+
matrix.connection.onEvent.close();
153+
154+
List<EventUpdate> eventUpdateList = await eventUpdateListFuture;
155+
156+
expect(eventUpdateList.length,10);
157+
158+
expect(eventUpdateList[0].eventType=="m.room.member", true);
159+
expect(eventUpdateList[0].roomID=="!726s6s6q:example.com", true);
160+
expect(eventUpdateList[0].type=="state", true);
161+
162+
expect(eventUpdateList[1].eventType=="m.room.member", true);
163+
expect(eventUpdateList[1].roomID=="!726s6s6q:example.com", true);
164+
expect(eventUpdateList[1].type=="timeline", true);
165+
166+
expect(eventUpdateList[2].eventType=="m.room.message", true);
167+
expect(eventUpdateList[2].roomID=="!726s6s6q:example.com", true);
168+
expect(eventUpdateList[2].type=="timeline", true);
169+
170+
expect(eventUpdateList[3].eventType=="m.tag", true);
171+
expect(eventUpdateList[3].roomID=="!726s6s6q:example.com", true);
172+
expect(eventUpdateList[3].type=="account_data", true);
173+
174+
expect(eventUpdateList[4].eventType=="org.example.custom.room.config", true);
175+
expect(eventUpdateList[4].roomID=="!726s6s6q:example.com", true);
176+
expect(eventUpdateList[4].type=="account_data", true);
177+
178+
expect(eventUpdateList[5].eventType=="m.room.name", true);
179+
expect(eventUpdateList[5].roomID=="!696r7674:example.com", true);
180+
expect(eventUpdateList[5].type=="invite_state", true);
181+
182+
expect(eventUpdateList[6].eventType=="m.room.member", true);
183+
expect(eventUpdateList[6].roomID=="!696r7674:example.com", true);
184+
expect(eventUpdateList[6].type=="invite_state", true);
185+
186+
expect(eventUpdateList[7].eventType=="m.presence", true);
187+
expect(eventUpdateList[7].roomID=="presence", true);
188+
expect(eventUpdateList[7].type=="presence", true);
189+
190+
expect(eventUpdateList[8].eventType=="org.example.custom.config", true);
191+
expect(eventUpdateList[8].roomID=="account_data", true);
192+
expect(eventUpdateList[8].type=="account_data", true);
193+
194+
expect(eventUpdateList[9].eventType=="m.new_device", true);
195+
expect(eventUpdateList[9].roomID=="to_device", true);
196+
expect(eventUpdateList[9].type=="to_device", true);
197+
198+
199+
});
200+
201+
testWidgets('should get created', create);
202+
203+
test('Login', loginText);
204+
205+
test('Logout when token is unknown', () async{
206+
Future<LoginState> loginStateFuture = matrix.connection.onLoginStateChanged.stream.first;
207+
final resp = await matrix
208+
.connection.jsonRequest(type: "DELETE", action: "/unknown/token");
209+
210+
LoginState state = await loginStateFuture;
211+
expect(state, LoginState.loggedOut);
212+
expect(matrix.isLogged(), false);
213+
});
214+
215+
});
216+
}

‎test/FakeMatrixApi.dart

+192
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
import 'package:http/testing.dart';
2+
import 'dart:convert';
3+
import 'dart:core';
4+
import 'dart:math';
5+
import 'package:http/http.dart';
6+
7+
class FakeMatrixApi extends MockClient {
8+
FakeMatrixApi()
9+
: super((request) async {
10+
// Collect data from Request
11+
final String action = request.url.path.split("/_matrix")[1];
12+
final String method = request.method;
13+
final dynamic data =
14+
method == "GET" ? request.url.queryParameters : request.body;
15+
var res = {};
16+
17+
//print("$method request to $action with Data: $data");
18+
19+
// Sync requests with timeout
20+
if (data is Map<String, dynamic> && data["timeout"] is String) {
21+
await new Future.delayed(Duration(seconds: 5));
22+
}
23+
24+
// Call API
25+
if (api.containsKey(method) && api[method].containsKey(action))
26+
res = api[method][action](data);
27+
else
28+
res = {
29+
"errcode": "M_UNRECOGNIZED",
30+
"error": "Unrecognized request"
31+
};
32+
33+
return Response(json.encode(res), 100);
34+
});
35+
36+
static final Map<String, Map<String, dynamic>> api = {
37+
"GET": {
38+
"/client/versions": (var req) => {
39+
"versions": ["r0.0.1", "r0.1.0", "r0.2.0", "r0.3.0", "r0.4.0"],
40+
"unstable_features": {"m.lazy_load_members": true},
41+
},
42+
"/client/r0/login": (var req) => {
43+
"flows": [
44+
{"type": "m.login.password"}
45+
]
46+
},
47+
"/client/r0/sync": (var req) => {
48+
"next_batch": Random().nextDouble().toString(),
49+
"presence": {
50+
"events": [
51+
{
52+
"sender": "@alice:example.com",
53+
"type": "m.presence",
54+
"content": {"presence": "online"}
55+
}
56+
]
57+
},
58+
"account_data": {
59+
"events": [
60+
{
61+
"type": "org.example.custom.config",
62+
"content": {"custom_config_key": "custom_config_value"}
63+
}
64+
]
65+
},
66+
"to_device": {
67+
"events": [
68+
{
69+
"sender": "@alice:example.com",
70+
"type": "m.new_device",
71+
"content": {
72+
"device_id": "XYZABCDE",
73+
"rooms": ["!726s6s6q:example.com"]
74+
}
75+
}
76+
]
77+
},
78+
"rooms": {
79+
"join": {
80+
"!726s6s6q:example.com": {
81+
"unread_notifications": {
82+
"highlight_count": 2,
83+
"notification_count": 2,
84+
},
85+
"state": {
86+
"events": [
87+
{
88+
"sender": "@alice:example.com",
89+
"type": "m.room.member",
90+
"state_key": "@alice:example.com",
91+
"content": {"membership": "join"},
92+
"origin_server_ts": 1417731086795,
93+
"event_id": "66697273743031:example.com"
94+
}
95+
]
96+
},
97+
"timeline": {
98+
"events": [
99+
{
100+
"sender": "@bob:example.com",
101+
"type": "m.room.member",
102+
"state_key": "@bob:example.com",
103+
"content": {"membership": "join"},
104+
"prev_content": {"membership": "invite"},
105+
"origin_server_ts": 1417731086795,
106+
"event_id": "7365636s6r6432:example.com"
107+
},
108+
{
109+
"sender": "@alice:example.com",
110+
"type": "m.room.message",
111+
"txn_id": "1234",
112+
"content": {"body": "I am a fish", "msgtype": "m.text"},
113+
"origin_server_ts": 1417731086797,
114+
"event_id": "74686972643033:example.com"
115+
}
116+
],
117+
"limited": true,
118+
"prev_batch": "t34-23535_0_0"
119+
},
120+
"ephemeral": {
121+
"events": [
122+
{
123+
"type": "m.typing",
124+
"content": {
125+
"user_ids": ["@alice:example.com"]
126+
}
127+
}
128+
]
129+
},
130+
"account_data": {
131+
"events": [
132+
{
133+
"type": "m.tag",
134+
"content": {
135+
"tags": {
136+
"work": {"order": 1}
137+
}
138+
}
139+
},
140+
{
141+
"type": "org.example.custom.room.config",
142+
"content": {"custom_config_key": "custom_config_value"}
143+
}
144+
]
145+
}
146+
}
147+
},
148+
"invite": {
149+
"!696r7674:example.com": {
150+
"invite_state": {
151+
"events": [
152+
{
153+
"sender": "@alice:example.com",
154+
"type": "m.room.name",
155+
"state_key": "",
156+
"content": {"name": "My Room Name"}
157+
},
158+
{
159+
"sender": "@alice:example.com",
160+
"type": "m.room.member",
161+
"state_key": "@bob:example.com",
162+
"content": {"membership": "invite"}
163+
}
164+
]
165+
}
166+
}
167+
},
168+
"leave": {
169+
"!5345234234:example.com": {
170+
"timeline": {"events": []}
171+
},
172+
},
173+
}
174+
},
175+
},
176+
"POST": {
177+
"/client/r0/login": (var req) => {
178+
"user_id": "@test:fakeServer.notExisting",
179+
"access_token": "abc123",
180+
"device_id": "GHTYAJCE"
181+
},
182+
"/client/r0/logout": (var reqI) => {},
183+
"/client/r0/logout/all": (var reqI) => {},
184+
},
185+
"PUT": {},
186+
"DELETE": {
187+
"/unknown/token": (var req) => {
188+
"errcode": "M_UNKNOWN_TOKEN"
189+
},
190+
},
191+
};
192+
}

0 commit comments

Comments
 (0)
Please sign in to comment.