feat: Implement spaces
This commit is contained in:
parent
d7b7619a63
commit
fb0177ac5f
|
|
@ -526,6 +526,34 @@ class Client extends MatrixApi {
|
|||
return roomId;
|
||||
}
|
||||
|
||||
/// Creates a new space and returns the Room ID. The parameters are mostly
|
||||
/// the same like in [createRoom()].
|
||||
/// Be aware that spaces appear in the [rooms] list. You should check if a
|
||||
/// room is a space by using the `room.isSpace` getter and then just use the
|
||||
/// room as a space with `room.toSpace()`.
|
||||
///
|
||||
/// https://github.com/matrix-org/matrix-doc/blob/matthew/msc1772/proposals/1772-groups-as-rooms.md
|
||||
Future<String> createSpace({
|
||||
String name,
|
||||
String topic,
|
||||
Visibility visibility = Visibility.public,
|
||||
String spaceAliasName,
|
||||
List<String> invite,
|
||||
List<Map<String, dynamic>> invite3pid,
|
||||
String roomVersion,
|
||||
}) =>
|
||||
createRoom(
|
||||
name: name,
|
||||
topic: topic,
|
||||
visibility: visibility,
|
||||
roomAliasName: spaceAliasName,
|
||||
creationContent: {'type': 'm.space'},
|
||||
powerLevelContentOverride: {'events_default': 100},
|
||||
invite: invite,
|
||||
invite3pid: invite3pid,
|
||||
roomVersion: roomVersion,
|
||||
);
|
||||
|
||||
/// Returns the user's own displayname and avatar url. In Matrix it is possible that
|
||||
/// one user can have different displaynames and avatar urls in different rooms. So
|
||||
/// this endpoint first checks if the profile is the same in all rooms. If not, the
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:famedlysdk/src/utils/space_child.dart';
|
||||
import 'package:html_unescape/html_unescape.dart';
|
||||
import 'package:matrix_file_e2ee/matrix_file_e2ee.dart';
|
||||
|
||||
|
|
@ -1806,6 +1807,73 @@ class Room {
|
|||
? getState(EventTypes.RoomTombstone).parsedTombstoneContent
|
||||
: null;
|
||||
|
||||
/// Checks if the `m.room.create` state has a `type` key with the value
|
||||
/// `m.space`.
|
||||
bool get isSpace =>
|
||||
getState(EventTypes.RoomCreate)?.content?.tryGet<String>('type') ==
|
||||
RoomCreationTypes.mSpace; // TODO: Magic string!
|
||||
|
||||
/// The parents of this room. Currently this SDK doesn't yet set the canonical
|
||||
/// flag and is not checking if this room is in fact a child of this space.
|
||||
/// You should therefore not rely on this and always check the children of
|
||||
/// the space.
|
||||
List<SpaceParent> get spaceParents =>
|
||||
states[EventTypes.spaceParent]
|
||||
?.values
|
||||
?.map((state) => SpaceParent.fromState(state))
|
||||
?.where((child) => child.via?.isNotEmpty ?? false)
|
||||
?.toList() ??
|
||||
[];
|
||||
|
||||
/// List all children of this space. Children without a `via` domain will be
|
||||
/// ignored.
|
||||
/// Children are sorted by the `order` while those without this field will be
|
||||
/// sorted at the end of the list.
|
||||
List<SpaceChild> get spaceChildren => !isSpace
|
||||
? throw Exception('Room is not a space!')
|
||||
: (states[EventTypes.spaceChild]
|
||||
?.values
|
||||
?.map((state) => SpaceChild.fromState(state))
|
||||
?.where((child) => child.via?.isNotEmpty ?? false)
|
||||
?.toList() ??
|
||||
[])
|
||||
..sort((a, b) => a.order.isEmpty || b.order.isEmpty
|
||||
? b.order.compareTo(a.order)
|
||||
: a.order.compareTo(b.order));
|
||||
|
||||
/// Adds or edits a child of this space.
|
||||
Future<void> setSpaceChild(
|
||||
String roomId, {
|
||||
List<String> via,
|
||||
String order,
|
||||
bool suggested,
|
||||
}) async {
|
||||
if (!isSpace) throw Exception('Room is not a space!');
|
||||
via ??= [roomId.domain];
|
||||
await client.sendState(
|
||||
id,
|
||||
EventTypes.spaceChild,
|
||||
{
|
||||
'via': via,
|
||||
if (order != null) 'order': order,
|
||||
if (suggested != null) 'suggested': suggested,
|
||||
},
|
||||
roomId);
|
||||
await client.sendState(
|
||||
roomId,
|
||||
EventTypes.spaceParent,
|
||||
{
|
||||
'via': via,
|
||||
},
|
||||
id);
|
||||
return;
|
||||
}
|
||||
|
||||
/// Remove a child from this space by setting the `via` to an empty list.
|
||||
Future<void> removeSpaceChild(String roomId) => !isSpace
|
||||
? throw Exception('Room is not a space!')
|
||||
: setSpaceChild(roomId, via: const []);
|
||||
|
||||
@override
|
||||
bool operator ==(dynamic other) => (other is Room && other.id == id);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Famedly Matrix SDK
|
||||
* Copyright (C) 2019, 2020, 2021 Famedly GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'package:matrix_api_lite/matrix_api_lite.dart';
|
||||
|
||||
import '../event.dart';
|
||||
|
||||
class SpaceChild {
|
||||
final String roomId;
|
||||
final List<String> via;
|
||||
final String order;
|
||||
final bool suggested;
|
||||
|
||||
SpaceChild.fromState(Event state)
|
||||
: assert(state.type == EventTypes.spaceChild),
|
||||
roomId = state.stateKey,
|
||||
via = state.content.tryGetList<String>('via'),
|
||||
order = state.content.tryGet<String>('order', ''),
|
||||
suggested = state.content.tryGet<bool>('suggested');
|
||||
}
|
||||
|
||||
class SpaceParent {
|
||||
final String roomId;
|
||||
final List<String> via;
|
||||
final bool canonical;
|
||||
|
||||
SpaceParent.fromState(Event state)
|
||||
: assert(state.type == EventTypes.spaceParent),
|
||||
roomId = state.stateKey,
|
||||
via = state.content.tryGetList<String>('via'),
|
||||
canonical = state.content.tryGet<bool>('canonical');
|
||||
}
|
||||
|
|
@ -23,7 +23,7 @@ dependencies:
|
|||
matrix_file_e2ee: ^1.1.0
|
||||
isolate: ^2.0.3
|
||||
logger: ^1.0.0
|
||||
matrix_api_lite: ^0.2.2
|
||||
matrix_api_lite: ^0.2.4
|
||||
|
||||
dev_dependencies:
|
||||
test: ^1.15.7
|
||||
|
|
|
|||
|
|
@ -340,6 +340,16 @@ void main() {
|
|||
await matrix.setMuteAllPushNotifications(false);
|
||||
});
|
||||
|
||||
test('createSpace', () async {
|
||||
await matrix.createSpace(
|
||||
name: 'space',
|
||||
topic: 'My test space',
|
||||
spaceAliasName: '#myspace:example.invalid',
|
||||
invite: ['@alice:example.invalid'],
|
||||
roomVersion: '3',
|
||||
);
|
||||
});
|
||||
|
||||
test('get archive', () async {
|
||||
var archive = await matrix.archive;
|
||||
|
||||
|
|
|
|||
|
|
@ -768,6 +768,110 @@ void main() {
|
|||
expect(room.getState('m.room.message') != null, true);
|
||||
});
|
||||
|
||||
test('Spaces', () async {
|
||||
expect(room.isSpace, false);
|
||||
room.states['m.room.create'] = {
|
||||
'': Event.fromJson({
|
||||
'content': {'type': 'm.space'},
|
||||
'event_id': '\$143273582443PhrSn:example.org',
|
||||
'origin_server_ts': 1432735824653,
|
||||
'room_id': '!jEsUZKDJdhlrceRyVU:example.org',
|
||||
'sender': '@example:example.org',
|
||||
'type': 'm.room.create',
|
||||
'unsigned': {'age': 1234},
|
||||
'state_key': '',
|
||||
}, room, 1432735824653.0),
|
||||
};
|
||||
expect(room.isSpace, true);
|
||||
|
||||
expect(room.spaceParents.isEmpty, true);
|
||||
room.states[EventTypes.spaceParent] = {
|
||||
'!1234:example.invalid': Event.fromJson({
|
||||
'content': {
|
||||
'via': ['example.invalid']
|
||||
},
|
||||
'event_id': '\$143273582443PhrSn:example.org',
|
||||
'origin_server_ts': 1432735824653,
|
||||
'room_id': '!jEsUZKDJdhlrceRyVU:example.org',
|
||||
'sender': '@example:example.org',
|
||||
'type': EventTypes.spaceParent,
|
||||
'unsigned': {'age': 1234},
|
||||
'state_key': '!1234:example.invalid',
|
||||
}, room, 1432735824653.0),
|
||||
};
|
||||
expect(room.spaceParents.length, 1);
|
||||
|
||||
expect(room.spaceChildren.isEmpty, true);
|
||||
room.states[EventTypes.spaceChild] = {
|
||||
'!b:example.invalid': Event.fromJson({
|
||||
'content': {
|
||||
'via': ['example.invalid'],
|
||||
'order': 'b',
|
||||
},
|
||||
'event_id': '\$143273582443PhrSn:example.org',
|
||||
'origin_server_ts': 1432735824653,
|
||||
'room_id': '!jEsUZKDJdhlrceRyVU:example.org',
|
||||
'sender': '@example:example.org',
|
||||
'type': EventTypes.spaceChild,
|
||||
'unsigned': {'age': 1234},
|
||||
'state_key': '!b:example.invalid',
|
||||
}, room, 1432735824653.0),
|
||||
'!c:example.invalid': Event.fromJson({
|
||||
'content': {
|
||||
'via': ['example.invalid'],
|
||||
'order': 'c',
|
||||
},
|
||||
'event_id': '\$143273582443PhrSn:example.org',
|
||||
'origin_server_ts': 1432735824653,
|
||||
'room_id': '!jEsUZKDJdhlrceRyVU:example.org',
|
||||
'sender': '@example:example.org',
|
||||
'type': EventTypes.spaceChild,
|
||||
'unsigned': {'age': 1234},
|
||||
'state_key': '!c:example.invalid',
|
||||
}, room, 1432735824653.0),
|
||||
'!noorder:example.invalid': Event.fromJson({
|
||||
'content': {
|
||||
'via': ['example.invalid'],
|
||||
},
|
||||
'event_id': '\$143273582443PhrSn:example.org',
|
||||
'origin_server_ts': 1432735824653,
|
||||
'room_id': '!jEsUZKDJdhlrceRyVU:example.org',
|
||||
'sender': '@example:example.org',
|
||||
'type': EventTypes.spaceChild,
|
||||
'unsigned': {'age': 1234},
|
||||
'state_key': '!noorder:example.invalid',
|
||||
}, room, 1432735824653.0),
|
||||
'!a:example.invalid': Event.fromJson({
|
||||
'content': {
|
||||
'via': ['example.invalid'],
|
||||
'order': 'a',
|
||||
},
|
||||
'event_id': '\$143273582443PhrSn:example.org',
|
||||
'origin_server_ts': 1432735824653,
|
||||
'room_id': '!jEsUZKDJdhlrceRyVU:example.org',
|
||||
'sender': '@example:example.org',
|
||||
'type': EventTypes.spaceChild,
|
||||
'unsigned': {'age': 1234},
|
||||
'state_key': '!a:example.invalid',
|
||||
}, room, 1432735824653.0),
|
||||
};
|
||||
expect(room.spaceChildren.length, 4);
|
||||
|
||||
expect(room.spaceChildren[0].roomId, '!a:example.invalid');
|
||||
expect(room.spaceChildren[1].roomId, '!b:example.invalid');
|
||||
expect(room.spaceChildren[2].roomId, '!c:example.invalid');
|
||||
expect(room.spaceChildren[3].roomId, '!noorder:example.invalid');
|
||||
|
||||
// TODO: Implement a more generic fake api
|
||||
/*await room.setSpaceChild(
|
||||
'!jEsUZKDJdhlrceRyVU:example.org',
|
||||
via: ['example.invalid'],
|
||||
order: '5',
|
||||
suggested: true,
|
||||
);
|
||||
await room.removeSpaceChild('!1234:example.invalid');*/
|
||||
});
|
||||
|
||||
test('logout', () async {
|
||||
await matrix.logout();
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue