allow paginating in the historical timeline
This commit is contained in:
parent
72321cc413
commit
5b58573762
11 changed files with 165 additions and 70 deletions
|
|
@ -17,6 +17,7 @@ import "package:nexus/models/gomuks_config.dart";
|
||||||
import "package:nexus/models/oauth_auth_code_response.dart";
|
import "package:nexus/models/oauth_auth_code_response.dart";
|
||||||
import "package:nexus/models/open_graph_data.dart";
|
import "package:nexus/models/open_graph_data.dart";
|
||||||
import "package:nexus/models/paginate.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/deregister_pusher.dart";
|
||||||
import "package:nexus/models/requests/download_media.dart";
|
import "package:nexus/models/requests/download_media.dart";
|
||||||
import "package:nexus/models/requests/get_event.dart";
|
import "package:nexus/models/requests/get_event.dart";
|
||||||
|
|
@ -30,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/get_auth_url.dart";
|
||||||
import "package:nexus/models/requests/oauth/register_client.dart";
|
import "package:nexus/models/requests/oauth/register_client.dart";
|
||||||
import "package:nexus/models/requests/paginate.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/redact_event.dart";
|
||||||
import "package:nexus/models/requests/register_pusher.dart";
|
import "package:nexus/models/requests/register_pusher.dart";
|
||||||
import "package:nexus/models/requests/report.dart";
|
import "package:nexus/models/requests/report.dart";
|
||||||
|
|
@ -229,6 +231,9 @@ class ClientController extends AsyncNotifier<int> {
|
||||||
Future<Paginate> paginate(PaginateRequest request) async =>
|
Future<Paginate> paginate(PaginateRequest request) async =>
|
||||||
.fromJson(await _sendCommand("paginate", request.toJson()));
|
.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 =>
|
Future<EventContext> getEventContext(GetEventContextRequest request) async =>
|
||||||
.fromJson(await _sendCommand("get_event_context", request.toJson()));
|
.fromJson(await _sendCommand("get_event_context", request.toJson()));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,6 @@ typedef NotificationsRequest = (UnreadType? unreadType, String? roomId);
|
||||||
|
|
||||||
class NotificationsController([final NotificationsRequest? request])
|
class NotificationsController([final NotificationsRequest? request])
|
||||||
extends AsyncNotifier<IList<Event>> {
|
extends AsyncNotifier<IList<Event>> {
|
||||||
static const limit = 20;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<IList<Event>> build() async {
|
Future<IList<Event>> build() async {
|
||||||
final client = ref.read(ClientController.provider.notifier);
|
final client = ref.read(ClientController.provider.notifier);
|
||||||
|
|
@ -19,7 +17,6 @@ class NotificationsController([final NotificationsRequest? request])
|
||||||
.new(
|
.new(
|
||||||
maxTimestamp: .now(),
|
maxTimestamp: .now(),
|
||||||
unreadType: unreadType ?? .highlight,
|
unreadType: unreadType ?? .highlight,
|
||||||
limit: limit,
|
|
||||||
roomId: roomId,
|
roomId: roomId,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
@ -39,7 +36,6 @@ class NotificationsController([final NotificationsRequest? request])
|
||||||
.new(
|
.new(
|
||||||
maxTimestamp: lastTs,
|
maxTimestamp: lastTs,
|
||||||
unreadType: unreadType ?? .highlight,
|
unreadType: unreadType ?? .highlight,
|
||||||
limit: limit,
|
|
||||||
roomId: roomId,
|
roomId: roomId,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import "package:nexus/controllers/client.dart";
|
||||||
import "package:nexus/controllers/rooms.dart";
|
import "package:nexus/controllers/rooms.dart";
|
||||||
import "package:nexus/models/content/content.dart";
|
import "package:nexus/models/content/content.dart";
|
||||||
import "package:nexus/models/content/reaction.dart";
|
import "package:nexus/models/content/reaction.dart";
|
||||||
|
import "package:nexus/models/direction.dart";
|
||||||
import "package:nexus/models/event.dart";
|
import "package:nexus/models/event.dart";
|
||||||
import "package:nexus/models/requests/redact_event.dart";
|
import "package:nexus/models/requests/redact_event.dart";
|
||||||
import "package:nexus/models/relation_type.dart";
|
import "package:nexus/models/relation_type.dart";
|
||||||
|
|
@ -60,14 +61,19 @@ class RoomChatController(final (String roomId, String? contextualEvent) info)
|
||||||
if (info.$2 == null || timeline.map((e) => e.eventId).contains(info.$2)) {
|
if (info.$2 == null || timeline.map((e) => e.eventId).contains(info.$2)) {
|
||||||
ref.watch(RoomsController.provider.select((rooms) => rooms[roomId]));
|
ref.watch(RoomsController.provider.select((rooms) => rooms[roomId]));
|
||||||
|
|
||||||
return .new(timeline: timeline, hasMore: room.hasMore);
|
return .new(
|
||||||
|
timeline: timeline,
|
||||||
|
hasMoreBackward: room.hasMore,
|
||||||
|
hasMoreForward: false,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
final context = await client.getEventContext(
|
final context = await client.getEventContext(
|
||||||
.new(roomId: roomId, eventId: info.$2!, limit: 20),
|
.new(roomId: roomId, eventId: info.$2!),
|
||||||
);
|
);
|
||||||
return .new(
|
return .new(
|
||||||
timeline: context.before.add(context.event).addAll(context.after),
|
timeline: context.before.add(context.event).addAll(context.after),
|
||||||
hasMore: true,
|
hasMoreBackward: true,
|
||||||
|
hasMoreForward: true,
|
||||||
historicalData: .new(start: context.start, end: context.end),
|
historicalData: .new(start: context.start, end: context.end),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -83,18 +89,19 @@ class RoomChatController(final (String roomId, String? contextualEvent) info)
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
Future<void> loadOlder() async {
|
Future<void> paginate(Direction direction) async {
|
||||||
if (state.isLoading || state.value?.hasMore == false) return;
|
if (state.isLoading || state.value?.hasMoreBackward == false) return;
|
||||||
|
final chat = await future;
|
||||||
state = .loading();
|
state = .loading();
|
||||||
|
|
||||||
|
final client = ref.read(ClientController.provider.notifier);
|
||||||
|
|
||||||
|
if (chat?.historicalData == null) {
|
||||||
final timelineKeys = ref
|
final timelineKeys = ref
|
||||||
.read(RoomsController.provider.select((value) => value[info.$1]))
|
.read(RoomsController.provider.select((value) => value[info.$1]))
|
||||||
?.timeline
|
?.timeline
|
||||||
.keys;
|
.keys;
|
||||||
final response = await ref
|
final response = await client.paginate(
|
||||||
.read(ClientController.provider.notifier)
|
|
||||||
.paginate(
|
|
||||||
.new(
|
.new(
|
||||||
roomId: info.$1,
|
roomId: info.$1,
|
||||||
maxTimelineId: timelineKeys?.isNotEmpty == true
|
maxTimelineId: timelineKeys?.isNotEmpty == true
|
||||||
|
|
@ -127,6 +134,43 @@ class RoomChatController(final (String roomId, String? contextualEvent) info)
|
||||||
}),
|
}),
|
||||||
.new(),
|
.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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> send(
|
Future<void> send(
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
||||||
import "package:flutter_hooks/flutter_hooks.dart";
|
import "package:flutter_hooks/flutter_hooks.dart";
|
||||||
import "package:hooks_riverpod/hooks_riverpod.dart";
|
import "package:hooks_riverpod/hooks_riverpod.dart";
|
||||||
import "package:material_ui/material_ui.dart";
|
import "package:material_ui/material_ui.dart";
|
||||||
|
import "package:nexus/models/direction.dart";
|
||||||
import "package:nexus/models/event.dart";
|
import "package:nexus/models/event.dart";
|
||||||
import "package:nexus/models/room_chat.dart";
|
import "package:nexus/models/room_chat.dart";
|
||||||
import "package:super_sliver_list/super_sliver_list.dart";
|
import "package:super_sliver_list/super_sliver_list.dart";
|
||||||
|
|
@ -20,7 +21,7 @@ final class ChatScroll({
|
||||||
}) {
|
}) {
|
||||||
factory use({
|
factory use({
|
||||||
required AsyncValue<RoomChat?> controllerData,
|
required AsyncValue<RoomChat?> controllerData,
|
||||||
required Future<void> Function() loadOlder,
|
required Future<void> Function(Direction direction) paginate,
|
||||||
required Future<void> Function() markRead,
|
required Future<void> Function() markRead,
|
||||||
required String? contextualEvent,
|
required String? contextualEvent,
|
||||||
required void Function(String id) setContextualEvent,
|
required void Function(String id) setContextualEvent,
|
||||||
|
|
@ -118,20 +119,21 @@ final class ChatScroll({
|
||||||
);
|
);
|
||||||
}, [controllerData, anchorId.value]);
|
}, [controllerData, anchorId.value]);
|
||||||
|
|
||||||
useEffect(
|
useEffect(() {
|
||||||
() {
|
const loadThreshold = 500.0;
|
||||||
const topThreshold = 500.0;
|
const readThreshold = 50.0;
|
||||||
const bottomThreshold = 50.0;
|
|
||||||
|
|
||||||
Future<void> checkPosition() async {
|
Future<void> checkPosition() async {
|
||||||
if (!scrollController.hasClients) return;
|
if (!scrollController.hasClients) return;
|
||||||
if (contextualEvent != null) return;
|
|
||||||
|
|
||||||
final position = scrollController.position;
|
final position = scrollController.position;
|
||||||
|
|
||||||
if (position.extentAfter <= topThreshold) {
|
if (position.extentAfter <= loadThreshold) {
|
||||||
await loadOlder();
|
await paginate(Direction.backward);
|
||||||
} else if (position.extentBefore <= bottomThreshold) {
|
} else if (contextualEvent != null &&
|
||||||
|
position.extentBefore <= loadThreshold) {
|
||||||
|
await paginate(Direction.forward);
|
||||||
|
} else if (position.extentBefore <= readThreshold) {
|
||||||
await markRead();
|
await markRead();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -141,9 +143,7 @@ final class ChatScroll({
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) => checkPosition());
|
WidgetsBinding.instance.addPostFrameCallback((_) => checkPosition());
|
||||||
|
|
||||||
return () => scrollController.removeListener(checkPosition);
|
return () => scrollController.removeListener(checkPosition);
|
||||||
},
|
}, [scrollController, controllerData, paginate, markRead, contextualEvent]);
|
||||||
[scrollController, controllerData, loadOlder, markRead, contextualEvent],
|
|
||||||
);
|
|
||||||
|
|
||||||
double? resolveOffset(String itemId) {
|
double? resolveOffset(String itemId) {
|
||||||
final historyIndex = split.history.indexWhere(
|
final historyIndex = split.history.indexWhere(
|
||||||
|
|
|
||||||
8
lib/models/direction.dart
Normal file
8
lib/models/direction.dart
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
import "package:freezed_annotation/freezed_annotation.dart";
|
||||||
|
|
||||||
|
enum Direction {
|
||||||
|
@JsonValue("f")
|
||||||
|
forward,
|
||||||
|
@JsonValue("b")
|
||||||
|
backward,
|
||||||
|
}
|
||||||
19
lib/models/paginate_manual.dart
Normal file
19
lib/models/paginate_manual.dart
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
||||||
|
import "package:freezed_annotation/freezed_annotation.dart";
|
||||||
|
import "package:nexus/models/event.dart";
|
||||||
|
|
||||||
|
part "paginate_manual.freezed.dart";
|
||||||
|
part "paginate_manual.g.dart";
|
||||||
|
|
||||||
|
@Freezed(toJson: false, fromJson: false)
|
||||||
|
@JsonSerializable()
|
||||||
|
class const PaginateManual({
|
||||||
|
required final IList<Event> events,
|
||||||
|
final IList<Event> relatedEvents = const IList.empty(),
|
||||||
|
required final String? nextBatch,
|
||||||
|
}) with _$PaginateManual {
|
||||||
|
Map<String, Object?> toJson() => _$PaginateManualToJson(this);
|
||||||
|
|
||||||
|
factory PaginateManual.fromJson(Map<String, Object?> json) =>
|
||||||
|
_$PaginateManualFromJson(json);
|
||||||
|
}
|
||||||
|
|
@ -8,7 +8,7 @@ part "get_event_context.g.dart";
|
||||||
class const GetEventContextRequest({
|
class const GetEventContextRequest({
|
||||||
required final String roomId,
|
required final String roomId,
|
||||||
required final String eventId,
|
required final String eventId,
|
||||||
required final int limit,
|
final int limit = 20,
|
||||||
}) with _$GetEventContextRequest {
|
}) with _$GetEventContextRequest {
|
||||||
Map<String, Object?> toJson() => _$GetEventContextRequestToJson(this);
|
Map<String, Object?> toJson() => _$GetEventContextRequestToJson(this);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ part "get_mentions.g.dart";
|
||||||
class GetMentionsRequest({
|
class GetMentionsRequest({
|
||||||
@EpochDateTimeConverter() required final DateTime maxTimestamp,
|
@EpochDateTimeConverter() required final DateTime maxTimestamp,
|
||||||
@JsonKey(name: "type") required final UnreadType unreadType,
|
@JsonKey(name: "type") required final UnreadType unreadType,
|
||||||
required final int limit,
|
final int limit = 20,
|
||||||
final String? roomId,
|
final String? roomId,
|
||||||
}) with _$GetMentionsRequest {
|
}) with _$GetMentionsRequest {
|
||||||
Map<String, Object?> toJson() => _$GetMentionsRequestToJson(this);
|
Map<String, Object?> toJson() => _$GetMentionsRequestToJson(this);
|
||||||
|
|
|
||||||
22
lib/models/requests/paginate_manual.dart
Normal file
22
lib/models/requests/paginate_manual.dart
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
import "package:freezed_annotation/freezed_annotation.dart";
|
||||||
|
import "package:nexus/models/direction.dart";
|
||||||
|
|
||||||
|
part "paginate_manual.freezed.dart";
|
||||||
|
part "paginate_manual.g.dart";
|
||||||
|
|
||||||
|
@Freezed(toJson: false, fromJson: false)
|
||||||
|
@JsonSerializable()
|
||||||
|
class const PaginateManualRequest({
|
||||||
|
required final String roomId,
|
||||||
|
// Root event ID of a thread to paginate
|
||||||
|
final String? threadRoot,
|
||||||
|
// Can be null for starting pagination of a thread
|
||||||
|
final String? since,
|
||||||
|
required final Direction direction,
|
||||||
|
final int limit = 20,
|
||||||
|
}) with _$PaginateManualRequest {
|
||||||
|
Map<String, Object?> toJson() => _$PaginateManualRequestToJson(this);
|
||||||
|
|
||||||
|
factory PaginateManualRequest.fromJson(Map<String, Object?> json) =>
|
||||||
|
_$PaginateManualRequestFromJson(json);
|
||||||
|
}
|
||||||
|
|
@ -9,7 +9,8 @@ part "room_chat.g.dart";
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
class const RoomChat({
|
class const RoomChat({
|
||||||
required final IList<Event> timeline,
|
required final IList<Event> timeline,
|
||||||
required final bool hasMore,
|
required final bool hasMoreForward,
|
||||||
|
required final bool hasMoreBackward,
|
||||||
final HistoricalData? historicalData,
|
final HistoricalData? historicalData,
|
||||||
}) with _$RoomChat {
|
}) with _$RoomChat {
|
||||||
Map<String, Object?> toJson() => _$RoomChatToJson(this);
|
Map<String, Object?> toJson() => _$RoomChatToJson(this);
|
||||||
|
|
|
||||||
|
|
@ -73,7 +73,7 @@ final class const RoomChat({
|
||||||
|
|
||||||
final scroll = ChatScroll.use(
|
final scroll = ChatScroll.use(
|
||||||
controllerData: controllerData,
|
controllerData: controllerData,
|
||||||
loadOlder: notifier.loadOlder,
|
paginate: notifier.paginate,
|
||||||
contextualEvent: contextualEvent.value,
|
contextualEvent: contextualEvent.value,
|
||||||
setContextualEvent: (id) => contextualEvent.value = id,
|
setContextualEvent: (id) => contextualEvent.value = id,
|
||||||
markRead: () async {
|
markRead: () async {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue