fix(llc, ui): message search pagination
Signed-off-by: xsahil03x <[email protected]>
This commit is contained in:
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user