fix(llc, ui): message search pagination

Signed-off-by: xsahil03x <[email protected]>
This commit is contained in:
Sahil Kumar
2021-09-06 19:35:04 +05:30
committed by xsahil03x
parent 8585456edd
commit 4ddb0703b3
11 changed files with 127 additions and 29 deletions
@@ -36,6 +36,10 @@ class GeneralApi {
PaginationParams? pagination, PaginationParams? pagination,
Filter? messageFilters, Filter? messageFilters,
}) async { }) async {
assert(
pagination?.offset == null || pagination?.offset == 0 || sort == null,
'Cannot specify `offset` with `sort` parameter',
);
assert(() { assert(() {
if (query == null && messageFilters == null) { if (query == null && messageFilters == null) {
throw ArgumentError('Provide at least `query` or `messageFilters`'); throw ArgumentError('Provide at least `query` or `messageFilters`');
@@ -60,12 +60,16 @@ class PaginationParams extends Equatable {
/// ``` /// ```
const PaginationParams({ const PaginationParams({
this.limit = 10, this.limit = 10,
this.offset = 0, this.offset,
this.next,
this.greaterThan, this.greaterThan,
this.greaterThanOrEqual, this.greaterThanOrEqual,
this.lessThan, this.lessThan,
this.lessThanOrEqual, this.lessThanOrEqual,
}); }) : assert(
offset == null || offset == 0 || next == null,
'Cannot specify non-zero `offset` with `next` parameter',
);
/// Create a new instance from a json /// Create a new instance from a json
factory PaginationParams.fromJson(Map<String, dynamic> json) => factory PaginationParams.fromJson(Map<String, dynamic> json) =>
@@ -75,7 +79,10 @@ class PaginationParams extends Equatable {
final int limit; final int limit;
/// The offset of requesting items. /// The offset of requesting items.
final int offset; final int? offset;
/// A key used to paginate.
final String? next;
/// Filter on ids greater than the given value. /// Filter on ids greater than the given value.
@JsonKey(name: 'id_gt') @JsonKey(name: 'id_gt')
@@ -100,6 +107,7 @@ class PaginationParams extends Equatable {
PaginationParams copyWith({ PaginationParams copyWith({
int? limit, int? limit,
int? offset, int? offset,
String? next,
String? greaterThan, String? greaterThan,
String? greaterThanOrEqual, String? greaterThanOrEqual,
String? lessThan, String? lessThan,
@@ -108,6 +116,7 @@ class PaginationParams extends Equatable {
PaginationParams( PaginationParams(
limit: limit ?? this.limit, limit: limit ?? this.limit,
offset: offset ?? this.offset, offset: offset ?? this.offset,
next: next ?? this.next,
greaterThan: greaterThan ?? this.greaterThan, greaterThan: greaterThan ?? this.greaterThan,
greaterThanOrEqual: greaterThanOrEqual ?? this.greaterThanOrEqual, greaterThanOrEqual: greaterThanOrEqual ?? this.greaterThanOrEqual,
lessThan: lessThan ?? this.lessThan, lessThan: lessThan ?? this.lessThan,
@@ -118,6 +127,7 @@ class PaginationParams extends Equatable {
List<Object?> get props => [ List<Object?> get props => [
limit, limit,
offset, offset,
next,
greaterThan, greaterThan,
greaterThanOrEqual, greaterThanOrEqual,
lessThan, lessThan,
@@ -23,6 +23,7 @@ PaginationParams _$PaginationParamsFromJson(Map<String, dynamic> json) {
return PaginationParams( return PaginationParams(
limit: json['limit'] as int, limit: json['limit'] as int,
offset: json['offset'] as int, offset: json['offset'] as int,
next: json['next'] as String?,
greaterThan: json['id_gt'] as String?, greaterThan: json['id_gt'] as String?,
greaterThanOrEqual: json['id_gte'] as String?, greaterThanOrEqual: json['id_gte'] as String?,
lessThan: json['id_lt'] as String?, lessThan: json['id_lt'] as String?,
@@ -42,6 +43,7 @@ Map<String, dynamic> _$PaginationParamsToJson(PaginationParams instance) {
} }
} }
writeNotNull('next', instance.next);
writeNotNull('id_gt', instance.greaterThan); writeNotNull('id_gt', instance.greaterThan);
writeNotNull('id_gte', instance.greaterThanOrEqual); writeNotNull('id_gte', instance.greaterThanOrEqual);
writeNotNull('id_lt', instance.lessThan); writeNotNull('id_lt', instance.lessThan);
@@ -253,6 +253,12 @@ class SearchMessagesResponse extends _BaseResponse {
@JsonKey(defaultValue: []) @JsonKey(defaultValue: [])
late List<GetMessageResponse> results; late List<GetMessageResponse> results;
/// Message id of where to start searching from for next [results]
late String? next;
/// Message id of where to start searching from for previous [results]
late String? previous;
/// Create a new instance from a json /// Create a new instance from a json
static SearchMessagesResponse fromJson(Map<String, dynamic> json) => static SearchMessagesResponse fromJson(Map<String, dynamic> json) =>
_$SearchMessagesResponseFromJson(json); _$SearchMessagesResponseFromJson(json);
@@ -161,7 +161,9 @@ SearchMessagesResponse _$SearchMessagesResponseFromJson(
..results = (json['results'] as List<dynamic>?) ..results = (json['results'] as List<dynamic>?)
?.map((e) => GetMessageResponse.fromJson(e as Map<String, dynamic>)) ?.map((e) => GetMessageResponse.fromJson(e as Map<String, dynamic>))
.toList() ?? .toList() ??
[]; []
..next = json['next'] as String?
..previous = json['previous'] as String?;
} }
GetMessagesByIdResponse _$GetMessagesByIdResponseFromJson( GetMessagesByIdResponse _$GetMessagesByIdResponseFromJson(
@@ -86,6 +86,24 @@ void main() {
}, },
); );
test(
'should throw if `pagination.offset` and `sort` both are provided',
() async {
final filter = Filter.in_('cid', const ['test-cid-1', 'test-cid-2']);
const sort = [SortOption<ChannelModel>('test-field')];
const pagination = PaginationParams(offset: 10);
try {
await generalApi.searchMessages(
filter,
sort: sort,
pagination: pagination,
);
} catch (e) {
expect(e, isA<AssertionError>());
}
},
);
test('should run successfully with `query`', () async { test('should run successfully with `query`', () async {
final filter = Filter.in_('cid', const ['test-cid-1', 'test-cid-2']); final filter = Filter.in_('cid', const ['test-cid-1', 'test-cid-2']);
const query = 'test-query'; const query = 'test-query';
@@ -9,11 +9,23 @@ void main() {
expect(j, {'field': 'name', 'direction': -1}); expect(j, {'field': 'name', 'direction': -1});
}); });
test('PaginationParams', () { group('PaginationParams', () {
const option = PaginationParams(); test('default', () {
final j = option.toJson(); const option = PaginationParams();
expect(j, containsPair('limit', 10)); final j = option.toJson();
expect(j, containsPair('offset', 0)); expect(j, containsPair('limit', 10));
});
test(
'should throw if non-zero `offset` and `next` both are provided',
() {
try {
PaginationParams(offset: 10, next: 'next-message-id');
} catch (e) {
expect(e, isA<AssertionError>());
}
},
);
}); });
}); });
} }
@@ -43,6 +43,9 @@ class MessageSearchBlocState extends State<MessageSearchBloc>
with AutomaticKeepAliveClientMixin { with AutomaticKeepAliveClientMixin {
late StreamChatCoreState _streamChatCoreState; late StreamChatCoreState _streamChatCoreState;
String? nextId;
String? previousId;
/// The current messages list /// The current messages list
List<GetMessageResponse>? get messageResponses => List<GetMessageResponse>? get messageResponses =>
_messageResponses.valueOrNull; _messageResponses.valueOrNull;
@@ -76,11 +79,17 @@ class MessageSearchBlocState extends State<MessageSearchBloc>
_queryMessagesLoadingController.add(true); _queryMessagesLoadingController.add(true);
} }
try { try {
final clear = pagination == null || pagination.offset == 0; var clear = pagination == null;
if (sort != null) {
clear |= pagination?.next == null;
} else {
final offset = pagination?.offset;
clear |= offset == null || offset == 0;
}
final oldMessages = List<GetMessageResponse>.from(messageResponses ?? []); final oldMessages = List<GetMessageResponse>.from(messageResponses ?? []);
final messages = await client.search( final response = await client.search(
filter, filter,
sort: sort, sort: sort,
query: query, query: query,
@@ -88,10 +97,20 @@ class MessageSearchBlocState extends State<MessageSearchBloc>
messageFilters: messageFilter, messageFilters: messageFilter,
); );
final next = response.next;
final previous = response.previous;
nextId = next != null && next.isNotEmpty
? next
: /*reset nextId if we get nothing*/ null;
previousId = previous != null && previous.isNotEmpty
? previous
: /*reset previousId if we get nothing*/ null;
if (clear) { if (clear) {
_messageResponses.add(messages.results); _messageResponses.add(response.results);
} else { } else {
final temp = oldMessages + messages.results; final temp = oldMessages + response.results;
_messageResponses.add(temp); _messageResponses.add(temp);
} }
if (_messageResponses.hasValue && _queryMessagesLoadingController.value) { if (_messageResponses.hasValue && _queryMessagesLoadingController.value) {
@@ -37,7 +37,7 @@ class MessageSearchListCore extends StatefulWidget {
/// * [errorBuilder] /// * [errorBuilder]
/// * [loadingBuilder] /// * [loadingBuilder]
/// * [childBuilder] /// * [childBuilder]
const MessageSearchListCore({ MessageSearchListCore({
Key? key, Key? key,
required this.emptyBuilder, required this.emptyBuilder,
required this.errorBuilder, required this.errorBuilder,
@@ -49,7 +49,21 @@ class MessageSearchListCore extends StatefulWidget {
this.paginationParams, this.paginationParams,
this.messageFilters, this.messageFilters,
this.messageSearchListController, this.messageSearchListController,
}) : super(key: key); }) : assert(
messageQuery != null || messageFilters != null,
'Provide at least `query` or `messageFilters`',
),
assert(
messageQuery == null || messageFilters == null,
"Can't provide both `query` and `messageFilters` at the same time",
),
assert(
paginationParams?.offset == null ||
paginationParams?.offset == 0 ||
sortOptions == null,
'Cannot specify `offset` with `sortOptions` parameter',
),
super(key: key);
/// A [MessageSearchListController] allows reloading and pagination. /// A [MessageSearchListController] allows reloading and pagination.
/// Use [MessageSearchListController.loadData] and /// Use [MessageSearchListController.loadData] and
@@ -73,9 +87,8 @@ class MessageSearchListCore extends StatefulWidget {
final List<SortOption>? sortOptions; final List<SortOption>? sortOptions;
/// Pagination parameters /// Pagination parameters
/// limit: the number of users to return (max is 30) /// limit: the number of messages to return (max is 30)
/// offset: the offset (max is 1000) /// offset: the offset (max is 1000)
/// message_limit: how many messages should be included to each channel
final PaginationParams? paginationParams; final PaginationParams? paginationParams;
/// The message query filters to use. /// The message query filters to use.
@@ -159,15 +172,25 @@ class MessageSearchListCoreState extends State<MessageSearchListCore> {
); );
/// Fetches more messages with updated pagination and updates the widget /// Fetches more messages with updated pagination and updates the widget
Future<void> paginateData() => _messageSearchBloc!.search( Future<void> paginateData() {
filter: widget.filters, PaginationParams? pagination;
sort: widget.sortOptions, if (widget.sortOptions != null) {
pagination: widget.paginationParams!.copyWith( pagination = widget.paginationParams?.copyWith(
offset: _messageSearchBloc!.messageResponses?.length ?? 0, next: _messageSearchBloc?.nextId,
),
query: widget.messageQuery,
messageFilter: widget.messageFilters,
); );
} else {
pagination = widget.paginationParams?.copyWith(
offset: _messageSearchBloc?.messageResponses?.length,
);
}
return _messageSearchBloc!.search(
filter: widget.filters,
sort: widget.sortOptions,
pagination: pagination,
query: widget.messageQuery,
messageFilter: widget.messageFilters,
);
}
@override @override
void didUpdateWidget(MessageSearchListCore oldWidget) { void didUpdateWidget(MessageSearchListCore oldWidget) {
@@ -113,8 +113,9 @@ class ChannelQueryDao extends DatabaseAccessor<MoorChatDatabase>
cachedChannels.sort(chainedComparator); cachedChannels.sort(chainedComparator);
if (paginationParams?.offset != null && cachedChannels.isNotEmpty) { final offset = paginationParams?.offset;
cachedChannels.removeRange(0, paginationParams!.offset); if (offset != null && offset > 0 && cachedChannels.isNotEmpty) {
cachedChannels.removeRange(0, offset);
} }
if (paginationParams?.limit != null) { if (paginationParams?.limit != null) {
@@ -117,8 +117,9 @@ class MessageDao extends DatabaseAccessor<MoorChatDatabase>
msgList.removeRange(0, greaterThanIndex); msgList.removeRange(0, greaterThanIndex);
} }
} }
if (options?.limit != null) { final limit = options?.limit;
return msgList.take(options!.limit).toList(); if (limit != null && limit > 0) {
return msgList.take(limit).toList();
} }
} }
return msgList; return msgList;