wip
This commit is contained in:
parent
92eedc92ab
commit
c2214fcc44
15 changed files with 186 additions and 89 deletions
|
|
@ -15,6 +15,7 @@ import "package:nexus/models/event.dart";
|
|||
import "package:nexus/models/paginate.dart";
|
||||
import "package:nexus/models/requests/get_event_request.dart";
|
||||
import "package:nexus/models/requests/get_related_events_request.dart";
|
||||
import "package:nexus/models/requests/get_room_state_request.dart";
|
||||
import "package:nexus/models/requests/login_request.dart";
|
||||
import "package:nexus/models/profile.dart";
|
||||
import "package:nexus/models/requests/paginate_request.dart";
|
||||
|
|
@ -117,7 +118,9 @@ class ClientController extends AsyncNotifier<int> {
|
|||
|
||||
calloc.free(bufferPointer);
|
||||
|
||||
return response.buf.toJson();
|
||||
final json = response.buf.toJson();
|
||||
if (json is String) throw json;
|
||||
return json;
|
||||
}
|
||||
|
||||
Future<void> redactEvent(RedactEventRequest report) =>
|
||||
|
|
@ -140,6 +143,12 @@ class ClientController extends AsyncNotifier<int> {
|
|||
await _sendCommand("leave_room", {"room_id": room.metadata!.id});
|
||||
}
|
||||
|
||||
Future<IList<Event>> getRoomState(GetRoomStateRequest request) async {
|
||||
final response =
|
||||
(await _sendCommand("get_room_state", request.toJson())) as List;
|
||||
return response.map((event) => Event.fromJson(event)).toIList();
|
||||
}
|
||||
|
||||
Future<IList<Event>?> getRelatedEvents(
|
||||
GetRelatedEventsRequest request,
|
||||
) async {
|
||||
|
|
@ -157,11 +166,8 @@ class ClientController extends AsyncNotifier<int> {
|
|||
Future<Paginate> paginate(PaginateRequest request) async =>
|
||||
Paginate.fromJson(await _sendCommand("paginate", request.toJson()));
|
||||
|
||||
Future<Profile?> getProfile(String userId) async {
|
||||
final json = await _sendCommand("get_profile", {"user_id": userId});
|
||||
|
||||
return json == null ? null : Profile.fromJson(json);
|
||||
}
|
||||
Future<Profile> getProfile(String userId) async =>
|
||||
Profile.fromJson(await _sendCommand("get_profile", {"user_id": userId}));
|
||||
|
||||
Future<void> reportEvent(ReportRequest report) =>
|
||||
_sendCommand("report_event", report.toJson());
|
||||
|
|
|
|||
|
|
@ -1,22 +1,26 @@
|
|||
import "package:collection/collection.dart";
|
||||
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
||||
import "package:flutter_riverpod/flutter_riverpod.dart";
|
||||
import "package:matrix/matrix.dart";
|
||||
import "package:nexus/models/event.dart";
|
||||
import "package:nexus/models/room.dart";
|
||||
|
||||
class MembersController extends AsyncNotifier<IList<MatrixEvent>> {
|
||||
class MembersController extends AsyncNotifier<IList<Event>> {
|
||||
final Room room;
|
||||
MembersController(this.room);
|
||||
|
||||
@override
|
||||
Future<IList<MatrixEvent>> build() async => IList(
|
||||
(await room.client.getMembersByRoom(
|
||||
room.id,
|
||||
notMembership: Membership.leave,
|
||||
)) ??
|
||||
[],
|
||||
);
|
||||
Future<IList<Event>> build() async =>
|
||||
(room.state["m.room.member"]?.values ?? [])
|
||||
.map(
|
||||
(eventRowId) => room.events.firstWhereOrNull(
|
||||
(event) => event.rowId == eventRowId,
|
||||
),
|
||||
)
|
||||
.nonNulls
|
||||
.toIList();
|
||||
|
||||
static final provider = AsyncNotifierProvider.family
|
||||
.autoDispose<MembersController, IList<MatrixEvent>, Room>(
|
||||
.autoDispose<MembersController, IList<Event>, Room>(
|
||||
MembersController.new,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
17
lib/controllers/profile_controller.dart
Normal file
17
lib/controllers/profile_controller.dart
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import "package:flutter_riverpod/flutter_riverpod.dart";
|
||||
import "package:nexus/controllers/client_controller.dart";
|
||||
import "package:nexus/models/profile.dart";
|
||||
|
||||
class ProfileController extends AsyncNotifier<Profile> {
|
||||
final String userId;
|
||||
ProfileController(this.userId);
|
||||
|
||||
@override
|
||||
Future<Profile> build() =>
|
||||
ref.watch(ClientController.provider.notifier).getProfile(userId);
|
||||
|
||||
static final provider =
|
||||
AsyncNotifierProvider.family<ProfileController, Profile, String>(
|
||||
ProfileController.new,
|
||||
);
|
||||
}
|
||||
|
|
@ -6,14 +6,17 @@ import "package:flutter_riverpod/flutter_riverpod.dart";
|
|||
import "package:fluttertagger/fluttertagger.dart" as tagger;
|
||||
import "package:nexus/controllers/client_controller.dart";
|
||||
import "package:nexus/controllers/new_events_controller.dart";
|
||||
import "package:nexus/controllers/rooms_controller.dart";
|
||||
import "package:nexus/controllers/selected_room_controller.dart";
|
||||
import "package:nexus/helpers/extensions/event_to_message.dart";
|
||||
import "package:nexus/helpers/extensions/list_to_messages.dart";
|
||||
import "package:nexus/models/event.dart";
|
||||
import "package:nexus/models/requests/get_room_state_request.dart";
|
||||
import "package:nexus/models/requests/paginate_request.dart";
|
||||
import "package:nexus/models/requests/redact_event_request.dart";
|
||||
import "package:nexus/models/relation_type.dart";
|
||||
import "package:nexus/models/requests/send_message_request.dart";
|
||||
import "package:nexus/models/room.dart";
|
||||
import "package:nexus/models/sync_data.dart";
|
||||
|
||||
class RoomChatController extends AsyncNotifier<ChatController> {
|
||||
final String roomId;
|
||||
|
|
@ -22,9 +25,8 @@ class RoomChatController extends AsyncNotifier<ChatController> {
|
|||
@override
|
||||
Future<ChatController> build() async {
|
||||
final client = ref.watch(ClientController.provider.notifier);
|
||||
final events =
|
||||
ref.read(SelectedRoomController.provider)?.events ??
|
||||
const IList.empty();
|
||||
final room = ref.read(SelectedRoomController.provider);
|
||||
if (room == null) return InMemoryChatController();
|
||||
|
||||
ref.onDispose(
|
||||
ref.listen(NewEventsController.provider(roomId), (_, next) async {
|
||||
|
|
@ -38,7 +40,7 @@ class RoomChatController extends AsyncNotifier<ChatController> {
|
|||
|
||||
await controller.removeMessage(message);
|
||||
} else {
|
||||
final message = await event.toMessage(client, includeEdits: true);
|
||||
final message = await event.toMessage(ref, includeEdits: true);
|
||||
if (event.relationType == "m.replace") {
|
||||
final controller = await future;
|
||||
final oldMessage = controller.messages.firstWhereOrNull(
|
||||
|
|
@ -69,11 +71,26 @@ class RoomChatController extends AsyncNotifier<ChatController> {
|
|||
}).close,
|
||||
);
|
||||
|
||||
final messages = await events.toMessages(client);
|
||||
final messages = await room.timeline
|
||||
.map(
|
||||
(timelineRowTuple) => room.events.firstWhereOrNull(
|
||||
(event) => event.rowId == timelineRowTuple.eventRowId,
|
||||
),
|
||||
)
|
||||
.nonNulls
|
||||
.toMessages(ref);
|
||||
final controller = InMemoryChatController(messages: messages);
|
||||
ref.onDispose(controller.dispose);
|
||||
|
||||
if (messages.length < 20) await loadOlder(controller);
|
||||
|
||||
await client.getRoomState(
|
||||
GetRoomStateRequest(
|
||||
roomId: roomId,
|
||||
fetchMembers: room.metadata?.hasMemberList == false,
|
||||
),
|
||||
);
|
||||
|
||||
return controller;
|
||||
}
|
||||
|
||||
|
|
@ -109,23 +126,47 @@ class RoomChatController extends AsyncNotifier<ChatController> {
|
|||
final controller = chatController ?? await future;
|
||||
final client = ref.watch(ClientController.provider.notifier);
|
||||
|
||||
client.
|
||||
// await ref.watchInMemoryChatController? chatController(EventsController.provider(room).notifier).prev();
|
||||
// final timeline = await ref.watch(EventsController.provider(room).future);
|
||||
final response = await client.paginate(
|
||||
PaginateRequest(
|
||||
roomId: roomId,
|
||||
maxTimelineId: controller.messages.firstOrNull?.metadata?["timelineId"],
|
||||
),
|
||||
);
|
||||
|
||||
// final controller = await future;
|
||||
// await controller.insertAllMessages(
|
||||
// await timeline.events
|
||||
// .where(
|
||||
// (event) => !currentEvents.messages.any(
|
||||
// (existingEvent) => existingEvent.id == event.eventId,
|
||||
// ),
|
||||
// )
|
||||
// .toList()
|
||||
// .toMessages(room, timeline),
|
||||
// index: 0,
|
||||
// );
|
||||
// ref.notifyListeners();
|
||||
ref
|
||||
.watch(RoomsController.provider.notifier)
|
||||
.update(
|
||||
IMap({
|
||||
roomId: Room(
|
||||
events: response.events.addAll(response.relatedEvents),
|
||||
hasMore: response.hasMore,
|
||||
timeline: response.events
|
||||
.map(
|
||||
(event) => TimelineRowTuple(
|
||||
timelineRowId: event.timelineRowId,
|
||||
eventRowId: event.rowId,
|
||||
),
|
||||
)
|
||||
.toIList(),
|
||||
),
|
||||
}),
|
||||
const ISet.empty(),
|
||||
);
|
||||
|
||||
final existingIds = controller.messages.map((m) => m.id).toSet();
|
||||
|
||||
final messages = await response.events
|
||||
.where((event) => !existingIds.contains(event.eventId))
|
||||
.fold(<String, Event>{}, (acc, event) {
|
||||
acc[event.eventId] =
|
||||
event; // overwrites duplicates in response.events
|
||||
return acc;
|
||||
})
|
||||
.values
|
||||
.toIList()
|
||||
.reversed
|
||||
.toMessages(ref);
|
||||
await controller.insertAllMessages(messages, index: 0);
|
||||
}
|
||||
|
||||
Future<void> updateMessage(Message message, Message newMessage) async =>
|
||||
|
|
@ -167,7 +208,7 @@ class RoomChatController extends AsyncNotifier<ChatController> {
|
|||
.getProfile(id);
|
||||
return chat.User(
|
||||
id: id,
|
||||
name: user?.displayName,
|
||||
name: user.displayName,
|
||||
// imageSource: user.avatarUrl == null
|
||||
// ? null
|
||||
// : (await ref.watch(
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import "package:collection/collection.dart";
|
||||
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
||||
import "package:flutter_riverpod/flutter_riverpod.dart";
|
||||
import "package:nexus/controllers/new_events_controller.dart";
|
||||
|
|
@ -28,16 +29,22 @@ class RoomsController extends Notifier<IMap<String, Room>> {
|
|||
),
|
||||
state: incoming.state.entries.fold(
|
||||
existing.state,
|
||||
(stateAcc, event) => stateAcc.add(
|
||||
(previousValue, event) => previousValue.add(
|
||||
event.key,
|
||||
(stateAcc[event.key] ?? IMap<dynamic, dynamic>()).addAll(
|
||||
(previousValue[event.key] ?? const IMap.empty()).addAll(
|
||||
event.value,
|
||||
),
|
||||
),
|
||||
),
|
||||
timeline: incoming.reset
|
||||
timeline:
|
||||
(incoming.reset
|
||||
? incoming.timeline
|
||||
: existing.timeline.addAll(incoming.timeline),
|
||||
: existing.timeline.updateById(
|
||||
incoming.timeline,
|
||||
(item) => item.timelineRowId,
|
||||
))
|
||||
.sortedBy((element) => element.timelineRowId)
|
||||
.toIList(),
|
||||
receipts: incoming.receipts.entries.fold(
|
||||
existing.receipts,
|
||||
(receiptAcc, event) => receiptAcc.add(
|
||||
|
|
|
|||
|
|
@ -1,16 +1,19 @@
|
|||
import "package:flutter_chat_core/flutter_chat_core.dart";
|
||||
import "package:flutter_riverpod/flutter_riverpod.dart";
|
||||
import "package:nexus/controllers/client_controller.dart";
|
||||
import "package:nexus/controllers/profile_controller.dart";
|
||||
import "package:nexus/models/event.dart";
|
||||
import "package:nexus/models/requests/get_event_request.dart";
|
||||
import "package:nexus/models/requests/get_related_events_request.dart";
|
||||
|
||||
extension EventToMessage on Event {
|
||||
Future<Message?> toMessage(
|
||||
ClientController client, {
|
||||
Ref ref, {
|
||||
bool mustBeText = false,
|
||||
bool includeEdits = false,
|
||||
}) async {
|
||||
if (relationType == "m.replace" && !includeEdits) return null;
|
||||
final client = ref.watch(ClientController.provider.notifier);
|
||||
|
||||
final newEvents = await client.getRelatedEvents(
|
||||
GetRelatedEventsRequest(
|
||||
|
|
@ -28,22 +31,25 @@ extension EventToMessage on Event {
|
|||
GetEventRequest(roomId: roomId, eventId: replyId),
|
||||
);
|
||||
|
||||
final author = await client.getProfile(event.authorId);
|
||||
final author = await ref.watch(
|
||||
ProfileController.provider(event.authorId).future,
|
||||
);
|
||||
final content = (decrypted ?? this.content);
|
||||
final type = (decryptedType ?? this.type);
|
||||
final newContent = content["m.new_content"] as Map?;
|
||||
final metadata = {
|
||||
"timelineId": event.timelineRowId,
|
||||
"formatted":
|
||||
newContent?["formatted_body"] ??
|
||||
newContent?["body"] ??
|
||||
content["formatted_body"] ??
|
||||
content["body"] ??
|
||||
"",
|
||||
"reply": await replyEvent?.toMessage(client, mustBeText: true),
|
||||
"reply": await replyEvent?.toMessage(ref, mustBeText: true),
|
||||
"body": newContent?["body"] ?? content["body"],
|
||||
"eventType": type,
|
||||
"avatarUrl": author?.avatarUrl,
|
||||
"displayName": author?.displayName ?? authorId,
|
||||
"avatarUrl": author.avatarUrl,
|
||||
"displayName": author.displayName ?? authorId,
|
||||
"txnId": transactionId,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
||||
import "package:flutter_chat_core/flutter_chat_core.dart";
|
||||
import "package:nexus/controllers/client_controller.dart";
|
||||
import "package:flutter_riverpod/flutter_riverpod.dart";
|
||||
import "package:nexus/helpers/extensions/event_to_message.dart";
|
||||
import "package:nexus/models/event.dart";
|
||||
|
||||
extension ListToMessages on IList<Event> {
|
||||
Future<List<Message>> toMessages(ClientController client) async =>
|
||||
(await Future.wait(
|
||||
map((event) => event.toMessage(client)),
|
||||
extension ListToMessages on Iterable<Event> {
|
||||
Future<List<Message>> toMessages(Ref ref) async => (await Future.wait(
|
||||
map((event) => event.toMessage(ref)),
|
||||
)).nonNulls.toList();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ part "paginate.g.dart";
|
|||
abstract class Paginate with _$Paginate {
|
||||
const factory Paginate({
|
||||
required IList<Event> events,
|
||||
required IList<Event> relatedEvents,
|
||||
required bool hasMore,
|
||||
}) = _Paginate;
|
||||
|
||||
|
|
|
|||
15
lib/models/requests/get_room_state_request.dart
Normal file
15
lib/models/requests/get_room_state_request.dart
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import "package:freezed_annotation/freezed_annotation.dart";
|
||||
part "get_room_state_request.freezed.dart";
|
||||
part "get_room_state_request.g.dart";
|
||||
|
||||
@freezed
|
||||
abstract class GetRoomStateRequest with _$GetRoomStateRequest {
|
||||
const factory GetRoomStateRequest({
|
||||
required String roomId,
|
||||
required bool fetchMembers,
|
||||
@Default(false) bool includeMembers,
|
||||
}) = _GetRoomStateRequest;
|
||||
|
||||
factory GetRoomStateRequest.fromJson(Map<String, Object?> json) =>
|
||||
_$GetRoomStateRequestFromJson(json);
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ part "paginate_request.g.dart";
|
|||
abstract class PaginateRequest with _$PaginateRequest {
|
||||
const factory PaginateRequest({
|
||||
required String roomId,
|
||||
required int? maxTimelineId,
|
||||
@Default(20) int limit,
|
||||
}) = _PaginateRequest;
|
||||
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@ abstract class SendMessageRequest with _$SendMessageRequest {
|
|||
const factory SendMessageRequest({
|
||||
required String roomId,
|
||||
required String text,
|
||||
@Default(Mentions()) @JsonKey(name: "m.mentions") Mentions mentions,
|
||||
@JsonKey(name: "m.relates_to") Relation? relation,
|
||||
@Default(Mentions()) @JsonKey(name: "mentions") Mentions mentions,
|
||||
@JsonKey(name: "relates_to") Relation? relation,
|
||||
}) = _SendMessageRequest;
|
||||
|
||||
factory SendMessageRequest.fromJson(Map<String, Object?> json) =>
|
||||
|
|
@ -28,17 +28,16 @@ abstract class Mentions with _$Mentions {
|
|||
_$MentionsFromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
@Freezed(toJson: false)
|
||||
abstract class Relation with _$Relation {
|
||||
const Relation._(); // required for custom methods
|
||||
const Relation._();
|
||||
|
||||
const factory Relation({
|
||||
required String eventId,
|
||||
required RelationType relationType,
|
||||
}) = _Relation;
|
||||
|
||||
@override
|
||||
Map<String, Object?> toJson() {
|
||||
Map<String, dynamic> toJson() {
|
||||
switch (relationType) {
|
||||
case RelationType.reply:
|
||||
return {
|
||||
|
|
@ -50,6 +49,6 @@ abstract class Relation with _$Relation {
|
|||
}
|
||||
}
|
||||
|
||||
factory Relation.fromJson(Map<String, Object?> json) =>
|
||||
factory Relation.fromJson(Map<String, dynamic> json) =>
|
||||
_$RelationFromJson(json);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,12 +11,13 @@ abstract class Room with _$Room {
|
|||
const factory Room({
|
||||
@JsonKey(name: "meta") RoomMetadata? metadata,
|
||||
@Default(IList.empty()) IList<TimelineRowTuple> timeline,
|
||||
required bool reset,
|
||||
required IMap<String, IMap> state,
|
||||
@Default(false) bool reset,
|
||||
@Default(IMap.empty()) IMap<String, IMap<String, int>> state,
|
||||
// required IMap<String, AccountData> accountData,
|
||||
required IList<Event> events,
|
||||
@Default(IList.empty()) IList<Event> events,
|
||||
@Default(IMap.empty()) IMap<String, IList<ReadReceipt>> receipts,
|
||||
required bool dismissNotifications,
|
||||
@Default(false) bool dismissNotifications,
|
||||
@Default(true) bool hasMore,
|
||||
// required IList<Notification> notifications,
|
||||
}) = _Room;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,8 @@
|
|||
import "package:flutter/material.dart";
|
||||
import "package:hooks_riverpod/hooks_riverpod.dart";
|
||||
import "package:matrix/matrix.dart";
|
||||
import "package:nexus/controllers/avatar_controller.dart";
|
||||
import "package:nexus/controllers/members_controller.dart";
|
||||
import "package:nexus/helpers/extensions/better_when.dart";
|
||||
import "package:nexus/helpers/extensions/get_headers.dart";
|
||||
import "package:nexus/widgets/avatar_or_hash.dart";
|
||||
import "package:nexus/models/room.dart";
|
||||
|
||||
class MemberList extends ConsumerWidget {
|
||||
final Room room;
|
||||
|
|
@ -36,26 +33,30 @@ class MemberList extends ConsumerWidget {
|
|||
.where(
|
||||
(membership) =>
|
||||
membership.content["membership"] ==
|
||||
Membership.join.name,
|
||||
"join", // TODO: Show invites seperately
|
||||
)
|
||||
.map(
|
||||
(member) => ListTile(
|
||||
onTap: () {},
|
||||
leading: AvatarOrHash(
|
||||
ref
|
||||
.watch(
|
||||
AvatarController.provider(
|
||||
member.content["avatar_url"].toString(),
|
||||
),
|
||||
)
|
||||
.whenOrNull(data: (data) => data),
|
||||
member.content["displayname"].toString(),
|
||||
headers: room.client.headers,
|
||||
),
|
||||
// leading: AvatarOrHash( TODO
|
||||
// ref
|
||||
// .watch(
|
||||
// AvatarController.provider(
|
||||
// member.content["avatar_url"].toString(),
|
||||
// ),
|
||||
// )
|
||||
// .whenOrNull(data: (data) => data),
|
||||
// member.content["displayname"].toString(),
|
||||
// headers: room.client.headers,
|
||||
// ),
|
||||
title: Text(
|
||||
member.content["displayname"].toString(),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
subtitle: Text(
|
||||
member.authorId,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -21,12 +21,12 @@ import "package:nexus/models/relation_type.dart";
|
|||
import "package:nexus/models/requests/report_request.dart";
|
||||
import "package:nexus/widgets/chat_page/chat_box.dart";
|
||||
import "package:nexus/widgets/chat_page/html/html.dart";
|
||||
import "package:nexus/widgets/chat_page/member_list.dart";
|
||||
import "package:nexus/widgets/chat_page/room_appbar.dart";
|
||||
import "package:nexus/widgets/chat_page/top_widget.dart";
|
||||
import "package:nexus/widgets/form_text_input.dart";
|
||||
import "package:nexus/widgets/loading.dart";
|
||||
// import "package:dynamic_polls/dynamic_polls.dart";
|
||||
// import "package:matrix/matrix.dart";
|
||||
|
||||
class RoomChat extends HookConsumerWidget {
|
||||
final bool isDesktop;
|
||||
|
|
@ -534,12 +534,12 @@ class RoomChat extends HookConsumerWidget {
|
|||
),
|
||||
),
|
||||
|
||||
// if (memberListOpened.value == true && showMembersByDefault) TODO: Member list
|
||||
// MemberList(room),
|
||||
if (memberListOpened.value == true && showMembersByDefault)
|
||||
MemberList(room),
|
||||
],
|
||||
),
|
||||
|
||||
// endDrawer: showMembersByDefault ? null : MemberList(room),
|
||||
endDrawer: showMembersByDefault ? null : MemberList(room),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -186,7 +186,7 @@ class Sidebar extends HookConsumerWidget {
|
|||
? null
|
||||
: Icon(selectedSpace.icon),
|
||||
|
||||
selectedSpace.title, // TODO RM
|
||||
selectedSpace.title,
|
||||
headers: {},
|
||||
// space.client.headers, TODO
|
||||
),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue