add support for jumping to out-of-context events (#95)
Fixes #84. Reviewed-on: #95
This commit is contained in:
parent
3429a3ed98
commit
389b65ae61
16 changed files with 463 additions and 132 deletions
|
|
@ -12,13 +12,16 @@ import "package:nexus/helpers/extensions/gomuks_buffer.dart";
|
|||
import "package:nexus/models/capabilities.dart";
|
||||
import "package:nexus/models/content/message.dart";
|
||||
import "package:nexus/models/event.dart";
|
||||
import "package:nexus/models/event_context.dart";
|
||||
import "package:nexus/models/gomuks_config.dart";
|
||||
import "package:nexus/models/oauth_auth_code_response.dart";
|
||||
import "package:nexus/models/open_graph_data.dart";
|
||||
import "package:nexus/models/paginate.dart";
|
||||
import "package:nexus/models/paginate_manual.dart";
|
||||
import "package:nexus/models/requests/deregister_pusher.dart";
|
||||
import "package:nexus/models/requests/download_media.dart";
|
||||
import "package:nexus/models/requests/get_event.dart";
|
||||
import "package:nexus/models/requests/get_event_context.dart";
|
||||
import "package:nexus/models/requests/get_mentions.dart";
|
||||
import "package:nexus/models/requests/get_related_events.dart";
|
||||
import "package:nexus/models/requests/get_room_state.dart";
|
||||
|
|
@ -28,6 +31,7 @@ import "package:nexus/models/requests/oauth/exchange_token.dart";
|
|||
import "package:nexus/models/requests/oauth/get_auth_url.dart";
|
||||
import "package:nexus/models/requests/oauth/register_client.dart";
|
||||
import "package:nexus/models/requests/paginate.dart";
|
||||
import "package:nexus/models/requests/paginate_manual.dart";
|
||||
import "package:nexus/models/requests/redact_event.dart";
|
||||
import "package:nexus/models/requests/register_pusher.dart";
|
||||
import "package:nexus/models/requests/report.dart";
|
||||
|
|
@ -227,6 +231,12 @@ class ClientController extends AsyncNotifier<int> {
|
|||
Future<Paginate> paginate(PaginateRequest request) async =>
|
||||
.fromJson(await _sendCommand("paginate", request.toJson()));
|
||||
|
||||
Future<PaginateManual> paginateManual(PaginateManualRequest request) async =>
|
||||
.fromJson(await _sendCommand("paginate_manual", request.toJson()));
|
||||
|
||||
Future<EventContext> getEventContext(GetEventContextRequest request) async =>
|
||||
.fromJson(await _sendCommand("get_event_context", request.toJson()));
|
||||
|
||||
Future<ProfileResponse> getProfile(String userId) async {
|
||||
try {
|
||||
return .fromJson(await _sendCommand("get_profile", {"user_id": userId}));
|
||||
|
|
|
|||
|
|
@ -7,8 +7,6 @@ typedef NotificationsRequest = (UnreadType? unreadType, String? roomId);
|
|||
|
||||
class NotificationsController([final NotificationsRequest? request])
|
||||
extends AsyncNotifier<IList<Event>> {
|
||||
static const limit = 20;
|
||||
|
||||
@override
|
||||
Future<IList<Event>> build() async {
|
||||
final client = ref.read(ClientController.provider.notifier);
|
||||
|
|
@ -19,7 +17,6 @@ class NotificationsController([final NotificationsRequest? request])
|
|||
.new(
|
||||
maxTimestamp: .now(),
|
||||
unreadType: unreadType ?? .highlight,
|
||||
limit: limit,
|
||||
roomId: roomId,
|
||||
),
|
||||
);
|
||||
|
|
@ -39,7 +36,6 @@ class NotificationsController([final NotificationsRequest? request])
|
|||
.new(
|
||||
maxTimestamp: lastTs,
|
||||
unreadType: unreadType ?? .highlight,
|
||||
limit: limit,
|
||||
roomId: roomId,
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -10,18 +10,21 @@ import "package:nexus/controllers/client.dart";
|
|||
import "package:nexus/controllers/rooms.dart";
|
||||
import "package:nexus/models/content/content.dart";
|
||||
import "package:nexus/models/content/reaction.dart";
|
||||
import "package:nexus/models/direction.dart";
|
||||
import "package:nexus/models/event.dart";
|
||||
import "package:nexus/models/requests/redact_event.dart";
|
||||
import "package:nexus/models/relation_type.dart";
|
||||
import "package:nexus/models/requests/send_message.dart";
|
||||
import "package:nexus/models/room.dart";
|
||||
import "package:nexus/models/room_chat.dart";
|
||||
|
||||
class RoomChatController(final String roomId)
|
||||
extends AsyncNotifier<IList<Event>?> {
|
||||
class RoomChatController(final (String roomId, String? contextualEvent) info)
|
||||
extends AsyncNotifier<RoomChat?> {
|
||||
@override
|
||||
Future<IList<Event>?> build() async {
|
||||
Future<RoomChat?> build() async {
|
||||
final (roomId, eventId) = info;
|
||||
final client = ref.read(ClientController.provider.notifier);
|
||||
final room = ref.watch(
|
||||
final room = ref.read(
|
||||
RoomsController.provider.select((rooms) => rooms[roomId]),
|
||||
);
|
||||
|
||||
|
|
@ -32,7 +35,7 @@ class RoomChatController(final String roomId)
|
|||
await ref.read(RoomsController.provider.notifier).addState(roomId, state);
|
||||
}
|
||||
|
||||
return room.timeline
|
||||
final timeline = room.timeline
|
||||
.toEntryIList(compare: (a, b) => (a?.key ?? 0).compareTo(b?.key ?? 0))
|
||||
.map((element) => element.value)
|
||||
.toIList()
|
||||
|
|
@ -54,6 +57,26 @@ class RoomChatController(final String roomId)
|
|||
})
|
||||
.nonNulls
|
||||
.toIList();
|
||||
|
||||
if (info.$2 == null || timeline.map((e) => e.eventId).contains(info.$2)) {
|
||||
ref.watch(RoomsController.provider.select((rooms) => rooms[roomId]));
|
||||
|
||||
return .new(
|
||||
timeline: timeline,
|
||||
hasMoreBackward: room.hasMore,
|
||||
hasMoreForward: false,
|
||||
);
|
||||
} else {
|
||||
final context = await client.getEventContext(
|
||||
.new(roomId: roomId, eventId: info.$2!),
|
||||
);
|
||||
return .new(
|
||||
timeline: context.before.add(context.event).addAll(context.after),
|
||||
hasMoreBackward: true,
|
||||
hasMoreForward: true,
|
||||
historicalData: .new(start: context.start, end: context.end),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> deleteMessage(Event event, {String? reason}) => ref
|
||||
|
|
@ -61,55 +84,101 @@ class RoomChatController(final String roomId)
|
|||
.redactEvent(
|
||||
RedactEventRequest(
|
||||
eventId: event.eventId,
|
||||
roomId: roomId,
|
||||
roomId: info.$1,
|
||||
reason: reason,
|
||||
),
|
||||
);
|
||||
|
||||
Future<void> loadOlder() async {
|
||||
Future<void> paginate(Direction direction) async {
|
||||
if (state.isLoading) return;
|
||||
|
||||
final chat = await future;
|
||||
|
||||
if (direction == .forward
|
||||
? chat?.hasMoreForward == false
|
||||
: chat?.hasMoreBackward == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
state = .loading();
|
||||
|
||||
final timelineKeys = ref
|
||||
.read(RoomsController.provider.select((value) => value[roomId]))
|
||||
?.timeline
|
||||
.keys;
|
||||
final response = await ref
|
||||
.read(ClientController.provider.notifier)
|
||||
.paginate(
|
||||
.new(
|
||||
roomId: roomId,
|
||||
maxTimelineId: timelineKeys?.isNotEmpty == true
|
||||
? timelineKeys?.reduce(min)
|
||||
: null,
|
||||
final client = ref.read(ClientController.provider.notifier);
|
||||
|
||||
if (chat?.historicalData == null) {
|
||||
final timelineKeys = ref
|
||||
.read(RoomsController.provider.select((value) => value[info.$1]))
|
||||
?.timeline
|
||||
.keys;
|
||||
final response = await client.paginate(
|
||||
.new(
|
||||
roomId: info.$1,
|
||||
maxTimelineId: timelineKeys?.isNotEmpty == true
|
||||
? timelineKeys?.reduce(min)
|
||||
: null,
|
||||
),
|
||||
);
|
||||
|
||||
if (response.events.isEmpty) {
|
||||
state = .data(state.value);
|
||||
}
|
||||
|
||||
ref
|
||||
.read(RoomsController.provider.notifier)
|
||||
.update(
|
||||
IMap({
|
||||
info.$1: Room(
|
||||
events: IMap.fromIterable(
|
||||
response.events.addAll(response.relatedEvents),
|
||||
keyMapper: (event) => event.rowId,
|
||||
valueMapper: (event) => event,
|
||||
),
|
||||
hasMore: response.hasMore,
|
||||
timeline: IMap.fromIterable(
|
||||
response.events,
|
||||
keyMapper: (event) => event.timelineRowId,
|
||||
valueMapper: (event) => event.rowId,
|
||||
),
|
||||
),
|
||||
}),
|
||||
.new(),
|
||||
);
|
||||
} else {
|
||||
final paginationResponse = await client.paginateManual(
|
||||
.new(
|
||||
roomId: info.$1,
|
||||
direction: direction,
|
||||
since: direction == .forward
|
||||
? chat!.historicalData!.end
|
||||
: chat!.historicalData!.start,
|
||||
),
|
||||
);
|
||||
|
||||
state = .data(
|
||||
.new(
|
||||
timeline: direction == .forward
|
||||
? chat.timeline.addAll(paginationResponse.events)
|
||||
: paginationResponse.events.addAll(chat.timeline),
|
||||
hasMoreForward:
|
||||
direction == .forward && paginationResponse.nextBatch == null
|
||||
? false
|
||||
: chat.hasMoreForward,
|
||||
hasMoreBackward:
|
||||
direction == .backward && paginationResponse.nextBatch == null
|
||||
? false
|
||||
: chat.hasMoreBackward,
|
||||
historicalData: chat.historicalData?.copyWith(
|
||||
start:
|
||||
(direction == .backward
|
||||
? paginationResponse.nextBatch
|
||||
: null) ??
|
||||
chat.historicalData!.start,
|
||||
end:
|
||||
(direction == .forward ? paginationResponse.nextBatch : null) ??
|
||||
chat.historicalData!.end,
|
||||
),
|
||||
);
|
||||
|
||||
if (response.events.isEmpty) {
|
||||
state = .data(state.value);
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
ref
|
||||
.read(RoomsController.provider.notifier)
|
||||
.update(
|
||||
IMap({
|
||||
roomId: Room(
|
||||
events: IMap.fromIterable(
|
||||
response.events.addAll(response.relatedEvents),
|
||||
keyMapper: (event) => event.rowId,
|
||||
valueMapper: (event) => event,
|
||||
),
|
||||
hasMore: response.hasMore,
|
||||
timeline: IMap.fromIterable(
|
||||
response.events,
|
||||
keyMapper: (event) => event.timelineRowId,
|
||||
valueMapper: (event) => event.rowId,
|
||||
),
|
||||
),
|
||||
}),
|
||||
.new(),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> send(
|
||||
|
|
@ -123,7 +192,7 @@ class RoomChatController(final String roomId)
|
|||
if (relationType == .edit) {
|
||||
baseContent = relation?.content;
|
||||
} else {
|
||||
final provider = AttachmentController.provider(roomId);
|
||||
final provider = AttachmentController.provider(info.$1);
|
||||
baseContent = ref.read(provider)?.$2;
|
||||
ref.invalidate(provider);
|
||||
}
|
||||
|
|
@ -143,7 +212,7 @@ class RoomChatController(final String roomId)
|
|||
final client = ref.read(ClientController.provider.notifier);
|
||||
final event = await client.sendMessage(
|
||||
SendMessageRequest(
|
||||
roomId: roomId,
|
||||
roomId: info.$1,
|
||||
baseContent: baseContent,
|
||||
mentions: Mentions(
|
||||
userIds: [
|
||||
|
|
@ -165,7 +234,7 @@ class RoomChatController(final String roomId)
|
|||
.read(RoomsController.provider.notifier)
|
||||
.update(
|
||||
.new({
|
||||
roomId: .new(
|
||||
info.$1: .new(
|
||||
events: .new({event.rowId: event}),
|
||||
clientSticky: .new({event.rowId}),
|
||||
),
|
||||
|
|
@ -182,7 +251,7 @@ class RoomChatController(final String roomId)
|
|||
final client = ref.read(ClientController.provider.notifier);
|
||||
final allReactionEvents = await client.getRelatedEvents(
|
||||
.new(
|
||||
roomId: roomId,
|
||||
roomId: info.$1,
|
||||
eventId: event.eventId,
|
||||
relationType: "m.annotation",
|
||||
),
|
||||
|
|
@ -203,7 +272,7 @@ class RoomChatController(final String roomId)
|
|||
if (reactionEvent != null) {
|
||||
await ref
|
||||
.watch(ClientController.provider.notifier)
|
||||
.redactEvent(.new(eventId: reactionEvent.eventId, roomId: roomId));
|
||||
.redactEvent(.new(eventId: reactionEvent.eventId, roomId: info.$1));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -212,7 +281,7 @@ class RoomChatController(final String roomId)
|
|||
|
||||
await client.sendEvent(
|
||||
.new(
|
||||
roomId: roomId,
|
||||
roomId: info.$1,
|
||||
type: EventType.reaction.type,
|
||||
content: ReactionContent(key: reaction),
|
||||
synchronous: true,
|
||||
|
|
@ -224,7 +293,7 @@ class RoomChatController(final String roomId)
|
|||
}
|
||||
|
||||
static final provider = AsyncNotifierProvider.family
|
||||
.autoDispose<RoomChatController, IList<Event>?, String>(
|
||||
.autoDispose<RoomChatController, RoomChat?, (String, String?)>(
|
||||
RoomChatController.new,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue