diff --git a/packages/stream_chat/lib/src/api/channel.dart b/packages/stream_chat/lib/src/api/channel.dart index f2e8e0fc..f5cc5482 100644 --- a/packages/stream_chat/lib/src/api/channel.dart +++ b/packages/stream_chat/lib/src/api/channel.dart @@ -19,19 +19,24 @@ class Channel { /// Create a channel client instance. Channel( this._client, - this.type, - this._id, - this._extraData, - ) : _cid = _id != null ? '$type:$_id' : null { + this._type, + this._id, { + Map extraData = const {}, + }) : _cid = _id != null ? '$_type:$_id' : null, + _extraData = extraData { _client.logger.info('New Channel instance not initialized created'); } /// Create a channel client instance from a [ChannelState] object - Channel.fromState(this._client, ChannelState channelState) : _extraData = {} { - _cid = channelState.channel!.cid; - _id = channelState.channel!.id; - type = channelState.channel!.type; - + Channel.fromState(this._client, ChannelState channelState) + : assert( + channelState.channel != null, + 'No channel found inside channel state', + ), + _id = channelState.channel!.id, + _type = channelState.channel!.type, + _cid = channelState.channel!.cid, + _extraData = channelState.channel!.extraData ?? {} { state = ChannelClientState(this, channelState); _initializedCompleter.complete(true); _client.logger.info('New Channel instance initialized created'); @@ -41,19 +46,20 @@ class Channel { ChannelClientState? state; /// The channel type - String? type; + final String _type; String? _id; String? _cid; - Map _extraData; + final Map _extraData; set extraData(Map extraData) { if (_initializedCompleter.isCompleted) { - throw Exception( - 'Once the channel is initialized you should use channel.update ' - 'to update channel data'); + throw StateError( + 'Once the channel is initialized you should use channel.update ' + 'to update channel data', + ); } - _extraData = extraData; + _extraData.addAll(extraData); } /// Returns true if the channel is muted @@ -179,6 +185,9 @@ class Channel { /// Channel id String? get id => state?._channelState.channel?.id ?? _id; + /// Channel type + String get type => state?._channelState.channel?.type ?? _type; + /// Channel cid String? get cid => state?._channelState.channel?.cid ?? _cid; @@ -193,9 +202,11 @@ class Channel { state?._channelState.channel?.extraData ?? _extraData; /// Channel extra data as a stream - Stream?>? get extraDataStream { + Stream> get extraDataStream { _checkInitialized(); - return state?.channelStateStream.map((cs) => cs.channel?.extraData); + return state!.channelStateStream.map( + (cs) => cs.channel?.extraData ?? _extraData, + ); } /// The main Stream chat client @@ -384,7 +395,7 @@ class Channel { message = await attachmentsUploadCompleter.future; } - final response = await _client.sendMessage(message, id!, type!); + final response = await _client.sendMessage(message, id!, type); state!.addMessage(response.message); return response; } catch (error) { @@ -537,7 +548,7 @@ class Channel { return _client.sendFile( file, id!, - type!, + type, onSendProgress: onSendProgress, cancelToken: cancelToken, ); @@ -553,7 +564,7 @@ class Channel { return _client.sendImage( file, id!, - type!, + type, onSendProgress: onSendProgress, cancelToken: cancelToken, ); @@ -565,18 +576,16 @@ class Channel { Map? messageFilters, List? sort, PaginationParams? paginationParams, - }) => - _client.search( - { - 'cid': { - r'$in': [cid], - }, - }, - sort: sort, - query: query, - paginationParams: paginationParams, - messageFilters: messageFilters, - ); + }) { + _checkInitialized(); + return _client.search( + Filter.in_('cid', [cid!]), + sort: sort, + query: query, + paginationParams: paginationParams, + messageFilters: messageFilters, + ); + } /// Delete a file from this channel Future deleteFile( @@ -587,7 +596,7 @@ class Channel { return _client.deleteFile( url, id!, - type!, + type, cancelToken: cancelToken, ); } @@ -601,7 +610,7 @@ class Channel { return _client.deleteImage( url, id!, - type!, + type, cancelToken: cancelToken, ); } @@ -903,7 +912,7 @@ class Channel { void _initState(ChannelState channelState) { state = ChannelClientState(this, channelState); - client.state.channels![cid] = this; + client.state.channels[cid!] = this; if (!_initializedCompleter.isCompleted) { _initializedCompleter.complete(true); } @@ -1018,9 +1027,7 @@ class Channel { bool preferOffline = false, }) async { var path = '/channels/$type'; - if (id != null) { - path = '$path/$id'; - } + if (id != null) path = '$path/$id'; path = '$path/query'; final payload = Map.from({ @@ -1082,7 +1089,7 @@ class Channel { /// Query channel members Future queryMembers({ - Map? filter, + Filter? filter, List? sort, PaginationParams? pagination, }) async { diff --git a/packages/stream_chat/lib/src/client.dart b/packages/stream_chat/lib/src/client.dart index 4689e9ec..04bb8ae1 100644 --- a/packages/stream_chat/lib/src/client.dart +++ b/packages/stream_chat/lib/src/client.dart @@ -29,6 +29,8 @@ import 'package:stream_chat/src/platform_detector/platform_detector.dart'; import 'package:stream_chat/version.dart'; import 'package:uuid/uuid.dart'; +import 'package:stream_chat/src/models/filter.dart'; + /// Handler function used for logging records. Function requires a single /// [LogRecord] as the only parameter. typedef LogHandlerFunction = void Function(LogRecord record); @@ -552,13 +554,11 @@ class StreamChatClient { type: EventType.connectionRecovered, online: true, )); - if (state.channels?.isNotEmpty == true) { + if (state.channels.isNotEmpty == true) { // ignore: unawaited_futures - queryChannelsOnline(filter: { - 'cid': { - '\$in': state.channels!.keys.toList(), - }, - }).then( + queryChannelsOnline( + filter: Filter.in_('cid', state.channels.keys.toList()), + ).then( (_) async { await resync(); }, @@ -641,7 +641,7 @@ class StreamChatClient { /// Requests channels with a given query. Stream> queryChannels({ - Map? filter, + Filter? filter, List>? sort, Map? options, PaginationParams paginationParams = const PaginationParams(), @@ -684,7 +684,7 @@ class StreamChatClient { /// Requests channels with a given query from the API. Future> queryChannelsOnline({ - required Map? filter, + Filter? filter, List>? sort, Map? options, int? messageLimit, @@ -764,7 +764,7 @@ class StreamChatClient { final updateData = _mapChannelStateToChannel(channels); await _chatPersistenceClient?.updateChannelQueries( - filter ?? {}, + filter, channels.map((c) => c.channel!.cid).toList(), clearQueryCache: paginationParams.offset == 0, ); @@ -775,8 +775,8 @@ class StreamChatClient { /// Requests channels with a given query from the Persistence client. Future> queryChannelsOffline({ - required Map? filter, - required List>? sort, + Filter? filter, + List>? sort, PaginationParams paginationParams = const PaginationParams(), }) async { final offlineChannels = (await _chatPersistenceClient?.getChannelStates( @@ -790,10 +790,10 @@ class StreamChatClient { return updatedData.value; } - MapEntry, List> _mapChannelStateToChannel( + MapEntry, List> _mapChannelStateToChannel( List channelStates, ) { - final channels = {...state.channels ?? {}}; + final channels = {...state.channels}; final newChannels = []; for (final channelState in channelStates) { final channel = channels[channelState.channel!.cid]; @@ -802,7 +802,7 @@ class StreamChatClient { newChannels.add(channel); } else { final newChannel = Channel.fromState(this, channelState); - channels[newChannel.cid] = newChannel; + channels[newChannel.cid!] = newChannel; newChannels.add(newChannel); } } @@ -1020,7 +1020,7 @@ class StreamChatClient { /// Requests users with a given query. Future queryUsers({ - Map? filter, + Filter? filter, List? sort, Map? options, PaginationParams? pagination, @@ -1030,7 +1030,7 @@ class StreamChatClient { }; final payload = { - 'filter_conditions': filter ?? {}, + 'filter_conditions': filter, 'sort': sort, }..addAll(defaultOptions); @@ -1061,16 +1061,13 @@ class StreamChatClient { /// A message search. Future search( - Map filters, { + Filter filter, { String? query, List? sort, PaginationParams? paginationParams, Map? messageFilters, }) async { assert(() { - if (filters.isEmpty) { - throw ArgumentError('`filters` cannot be set as null or empty'); - } if (query == null && messageFilters == null) { throw ArgumentError('Provide at least `query` or `messageFilters`'); } @@ -1083,7 +1080,7 @@ class StreamChatClient { }(), 'Check incoming params.'); final payload = { - 'filter_conditions': filters, + 'filter_conditions': filter, 'message_filter_conditions': messageFilters, 'query': query, 'sort': sort, @@ -1194,15 +1191,12 @@ class StreamChatClient { Channel channel( String type, { String? id, - Map extraData = const {}, + Map extraData = const {}, }) { - if (id != null && state.channels?.containsKey('$type:$id') == true) { - if (state.channels!['$type:$id'] != null) { - return state.channels!['$type:$id'] as Channel; - } + if (id != null && state.channels.containsKey('$type:$id')) { + return state.channels['$type:$id']!; } - - return Channel(this, type, id, extraData); + return Channel(this, type, id, extraData: extraData); } /// Update or Create the given user object. @@ -1443,9 +1437,7 @@ class ClientState { if (cid != null) { _client.chatPersistenceClient?.deleteChannels([cid]); } - if (channels != null) { - channels = channels?..removeWhere((cid, ch) => cid == event.cid); - } + channels = channels..removeWhere((cid, ch) => cid == event.cid); })); } @@ -1468,9 +1460,7 @@ class ClientState { .listen((Event event) async { final eventChannel = event.channel!; await _client.chatPersistenceClient?.deleteChannels([eventChannel.cid]); - if (channels != null) { - channels = channels?..remove(eventChannel.cid); - } + channels = channels..remove(eventChannel.cid); })); } @@ -1521,13 +1511,13 @@ class ClientState { _channelsController.stream; /// The current list of channels in memory - Map? get channels => _channelsController.value; + Map get channels => _channelsController.value!; - set channels(Map? v) { - _channelsController.add(v); + set channels(Map? v) { + if (v != null) _channelsController.add(v); } - final BehaviorSubject?> _channelsController = + final BehaviorSubject> _channelsController = BehaviorSubject.seeded({}); final BehaviorSubject _userController = BehaviorSubject(); final BehaviorSubject> _usersController = @@ -1541,7 +1531,7 @@ class ClientState { _userController.close(); _unreadChannelsController.close(); _totalUnreadCountController.close(); - channels!.values.forEach((c) => c.dispose()); + channels.values.forEach((c) => c.dispose()); _channelsController.close(); } } diff --git a/packages/stream_chat/lib/src/db/chat_persistence_client.dart b/packages/stream_chat/lib/src/db/chat_persistence_client.dart index 479743b2..e9c21956 100644 --- a/packages/stream_chat/lib/src/db/chat_persistence_client.dart +++ b/packages/stream_chat/lib/src/db/chat_persistence_client.dart @@ -2,6 +2,7 @@ import 'package:stream_chat/src/api/requests.dart'; import 'package:stream_chat/src/models/channel_model.dart'; import 'package:stream_chat/src/models/channel_state.dart'; import 'package:stream_chat/src/models/event.dart'; +import 'package:stream_chat/src/models/filter.dart'; import 'package:stream_chat/src/models/member.dart'; import 'package:stream_chat/src/models/message.dart'; import 'package:stream_chat/src/models/reaction.dart'; @@ -90,7 +91,7 @@ abstract class ChatPersistenceClient { /// Optionally, pass [filter], [sort], [paginationParams] /// for filtering out states. Future> getChannelStates({ - Map? filter, + Filter? filter, List>? sort, PaginationParams? paginationParams, }); @@ -100,7 +101,7 @@ abstract class ChatPersistenceClient { /// If [clearQueryCache] is true before the insert /// the list of matching rows will be deleted Future updateChannelQueries( - Map filter, + Filter? filter, List cids, { bool clearQueryCache = false, }); diff --git a/packages/stream_chat/lib/src/models/channel_model.dart b/packages/stream_chat/lib/src/models/channel_model.dart index fa9f6eab..95145296 100644 --- a/packages/stream_chat/lib/src/models/channel_model.dart +++ b/packages/stream_chat/lib/src/models/channel_model.dart @@ -23,16 +23,16 @@ class ChannelModel { this.memberCount = 0, this.extraData, this.team, - }) : config = config ?? ChannelConfig(), - createdAt = createdAt ?? DateTime.now(), - updatedAt = updatedAt ?? DateTime.now(), - assert( + }) : assert( (cid != null && cid.contains(':')) || (id != null && type != null), 'provide either a cid or an id and type', ), id = id ?? cid!.split(':')[1], type = type ?? cid!.split(':')[0], - cid = cid ?? '$type:$id'; + cid = cid ?? '$type:$id', + config = config ?? ChannelConfig(), + createdAt = createdAt ?? DateTime.now(), + updatedAt = updatedAt ?? DateTime.now(); /// Create a new instance from a json factory ChannelModel.fromJson(Map json) => diff --git a/packages/stream_chat/lib/src/models/filter.dart b/packages/stream_chat/lib/src/models/filter.dart new file mode 100644 index 00000000..2356c57b --- /dev/null +++ b/packages/stream_chat/lib/src/models/filter.dart @@ -0,0 +1,208 @@ +// ignore_for_file: non_constant_identifier_names, constant_identifier_names + +import 'package:equatable/equatable.dart'; + +const _groupOperators = [ + FilterOperator.and, + FilterOperator.or, + FilterOperator.nor, +]; + +/// Possible operators to use in filters. +enum FilterOperator { + /// Matches values that are equal to a specified value. + equal, + + /// Matches all values that are not equal to a specified value. + notEqual, + + /// Matches values that are greater than a specified value. + greater, + + /// Matches values that are greater than a specified value. + greaterOrEqual, + + /// Matches values that are less than a specified value. + less, + + /// Matches values that are less than or equal to a specified value. + lessOrEqual, + + /// Matches any of the values specified in an array. + in_, + + /// Matches none of the values specified in an array. + notIn, + + /// Matches values by performing text search with the specified value. + query, + + /// Matches values with the specified prefix. + autoComplete, + + /// Matches values that exist/don't exist based on the specified boolean value. + exists, + + /// Matches all the values specified in an array. + and, + + /// Matches at least one of the values specified in an array. + or, + + /// Matches none of the values specified in an array. + nor, +} + +/// Helper extension for [FilterOperator] +extension FilterOperatorX on FilterOperator { + /// Converts [FilterOperator] into rew values + String get rawValue => { + FilterOperator.equal: '\$eq', + FilterOperator.notEqual: '\$ne', + FilterOperator.greater: '\$gt', + FilterOperator.greaterOrEqual: '\$gte', + FilterOperator.less: '\$lt', + FilterOperator.lessOrEqual: '\$lte', + FilterOperator.in_: '\$in', + FilterOperator.notIn: '\$nin', + FilterOperator.query: '\$q', + FilterOperator.autoComplete: '\$autocomplete', + FilterOperator.exists: '\$exists', + FilterOperator.and: '\$and', + FilterOperator.or: '\$or', + FilterOperator.nor: '\$nor', + }[this]!; +} + +/// Stream supports a limited set of filters for querying channels, +/// users and members. The example below shows how to filter for channels +/// of type messaging where the current user is a member +/// +/// ```dart +/// final filter = Filter.and( +/// Filter.equal('type', 'messaging'), +/// Filter.in_('members', [user.id]) +/// ) +/// ``` +/// See Query Channels Documentation +class Filter extends Equatable { + const Filter.__({ + required this.operator, + required this.value, + this.key, + }); + + Filter._({ + required FilterOperator operator, + required this.value, + this.key, + }) : operator = operator.rawValue; + + /// Combines the provided filters and matches the values + /// matched by all filters. + factory Filter.and(List filters) => + Filter._(operator: FilterOperator.and, value: filters); + + /// Combines the provided filters and matches the values + /// matched by at least one of the filters. + factory Filter.or(List filters) => + Filter._(operator: FilterOperator.or, value: filters); + + /// Combines the provided filters and matches the values + /// not matched by all the filters. + factory Filter.nor(List filters) => + Filter._(operator: FilterOperator.nor, value: filters); + + /// Matches values that are equal to a specified value. + factory Filter.equal(String key, Object value) => + Filter._(operator: FilterOperator.equal, key: key, value: value); + + /// Matches all values that are not equal to a specified value. + factory Filter.notEqual(String key, Object value) => + Filter._(operator: FilterOperator.notEqual, key: key, value: value); + + /// Matches values that are greater than a specified value. + factory Filter.greater(String key, Object value) => + Filter._(operator: FilterOperator.greater, key: key, value: value); + + /// Matches values that are greater than a specified value. + factory Filter.greaterOrEqual(String key, Object value) => + Filter._(operator: FilterOperator.greaterOrEqual, key: key, value: value); + + /// Matches values that are less than a specified value. + factory Filter.less(String key, Object value) => + Filter._(operator: FilterOperator.less, key: key, value: value); + + /// Matches values that are less than or equal to a specified value. + factory Filter.lessOrEqual(String key, Object value) => + Filter._(operator: FilterOperator.lessOrEqual, key: key, value: value); + + /// Matches any of the values specified in an array. + factory Filter.in_(String key, List values) => + Filter._(operator: FilterOperator.in_, key: key, value: values); + + /// Matches none of the values specified in an array. + factory Filter.notIn(String key, List values) => + Filter._(operator: FilterOperator.notIn, key: key, value: values); + + /// Matches values by performing text search with the specified value. + factory Filter.query(String key, String text) => + Filter._(operator: FilterOperator.query, key: key, value: text); + + /// Matches values with the specified prefix. + factory Filter.autoComplete(String key, String text) => + Filter._(operator: FilterOperator.autoComplete, key: key, value: text); + + /// Matches values that exist/don't exist based on the specified boolean value. + factory Filter.exists(String key, {bool exists = true}) => + Filter._(operator: FilterOperator.exists, key: key, value: exists); + + /// Creates a custom [Filter] if there isn't one already available. + const factory Filter.custom({ + required String operator, + required Object value, + String? key, + }) = Filter.__; + + /// An operator used for the filter. The operator string must start with `$` + final String operator; + + /// The "left-hand" side of the filter. + /// Specifies the name of the field the filter should match. + /// + /// Some operators like `and` or `or`, + /// don't require the key value to be present. + /// see-more : [_groupOperators] + final String? key; + + /// The "right-hand" side of the filter. + /// Specifies the [value] the filter should match. + final Object /*List|List|String*/ value; + + @override + List get props => [operator, key, value]; + + /// Serializes to json object + Map toJson() { + final json = {}; + final groupOperators = _groupOperators.map((it) => it.rawValue); + + assert( + groupOperators.contains(operator) || key != null, + 'Filter must contain the `key` when the operator is not a ' + 'group operator.', + ); + + if (groupOperators.contains(operator)) { + // Filters with group operators are encoded in the following form: + // { $: [ , ] } + json[operator] = value; + } else { + // Normal filters are encoded in the following form: + // { key: { $: } } + json[key!] = {operator: value}; + } + + return json; + } +} diff --git a/packages/stream_chat/lib/stream_chat.dart b/packages/stream_chat/lib/stream_chat.dart index ef146180..2ac56b10 100644 --- a/packages/stream_chat/lib/stream_chat.dart +++ b/packages/stream_chat/lib/stream_chat.dart @@ -28,6 +28,7 @@ export './src/models/channel_state.dart'; export './src/models/command.dart'; export './src/models/device.dart'; export './src/models/event.dart'; +export './src/models/filter.dart' show Filter; export './src/models/member.dart'; export './src/models/message.dart'; export './src/models/mute.dart'; diff --git a/packages/stream_chat/test/src/api/channel_test.dart b/packages/stream_chat/test/src/api/channel_test.dart index 8f18d5e2..49a12cb4 100644 --- a/packages/stream_chat/test/src/api/channel_test.dart +++ b/packages/stream_chat/test/src/api/channel_test.dart @@ -516,7 +516,7 @@ void main() { ); await channelClient.watch(); - final event = const Event(type: EventType.any); + const event = Event(type: EventType.any); when( () => mockDio.post( @@ -564,7 +564,7 @@ void main() { ); await channelClient.watch(); - final event = const Event(type: EventType.typingStart); + const event = Event(type: EventType.typingStart); when( () => mockDio.post( @@ -610,7 +610,7 @@ void main() { ); await channelClient.watch(); - final event = const Event(type: EventType.typingStop); + const event = Event(type: EventType.typingStop); when( () => mockDio.post( diff --git a/packages/stream_chat/test/src/client_test.dart b/packages/stream_chat/test/src/client_test.dart index c93059d5..b2a5bbac 100644 --- a/packages/stream_chat/test/src/client_test.dart +++ b/packages/stream_chat/test/src/client_test.dart @@ -10,6 +10,7 @@ import 'package:stream_chat/src/api/requests.dart'; import 'package:stream_chat/src/client.dart'; import 'package:stream_chat/src/exceptions.dart'; import 'package:stream_chat/src/models/channel_model.dart'; +import 'package:stream_chat/src/models/filter.dart'; import 'package:stream_chat/src/models/message.dart'; import 'package:stream_chat/src/models/user.dart'; import 'package:test/test.dart'; @@ -89,7 +90,7 @@ void main() { test('Channel', () { final client = StreamChatClient('test'); - final data = {'test': 1}; + final data = {'test': 1}; final channelClient = client.channel('type', id: 'id', extraData: data); expect(channelClient.type, 'type'); expect(channelClient.id, 'id'); @@ -126,7 +127,7 @@ void main() { ), ); - await client.queryChannelsOnline(filter: null, waitForConnect: false); + await client.queryChannelsOnline(waitForConnect: false); verify(() => mockDio.get('/channels', queryParameters: queryParams)) @@ -140,11 +141,7 @@ void main() { when(() => mockDio.interceptors).thenReturn(Interceptors()); final client = StreamChatClient('api-key', httpClient: mockDio); - final queryFilter = { - 'id': { - '\$in': ['test'], - }, - }; + final queryFilter = Filter.in_('id', const ['test']); final sortOptions = >[]; final options = {'state': false, 'watch': false, 'presence': true}; const paginationParams = PaginationParams(offset: 2); @@ -192,11 +189,7 @@ void main() { when(() => mockDio.interceptors).thenReturn(Interceptors()); final client = StreamChatClient('api-key', httpClient: mockDio); - final filter = { - 'cid': { - r'$in': ['messaging:testId'] - } - }; + final filter = Filter.in_('cid', const ['messaging:testId']); const query = 'hello'; final queryParams = { 'payload': json.encode({ @@ -229,11 +222,7 @@ void main() { when(() => mockDio.interceptors).thenReturn(Interceptors()); final client = StreamChatClient('api-key', httpClient: mockDio); - final filters = { - 'id': { - '\$in': ['test'], - }, - }; + final filters = Filter.in_('id', const ['test']); const sortOptions = [SortOption('name')]; const query = 'query'; final queryParams = { @@ -374,7 +363,7 @@ void main() { final client = StreamChatClient('api-key', httpClient: mockDio); final queryParams = { 'payload': json.encode({ - 'filter_conditions': {}, + 'filter_conditions': null, 'sort': null, 'presence': false, }), @@ -407,11 +396,7 @@ void main() { when(() => mockDio.interceptors).thenReturn(Interceptors()); final client = StreamChatClient('api-key', httpClient: mockDio); - final queryFilter = { - 'id': { - '\$in': ['test'], - }, - }; + final queryFilter = Filter.in_('id', const ['test']); const sortOptions = []; final options = {'presence': true}; final queryParams = { @@ -1163,8 +1148,11 @@ void main() { httpClient: mockDio, ); - final channelClient = - client.channel('type', id: 'id', extraData: {'name': 'init'}); + final channelClient = client.channel( + 'type', + id: 'id', + extraData: {'name': 'init'}, + ); const update = { 'set': {'name': 'demo'} diff --git a/packages/stream_chat/test/src/models/filter_test.dart b/packages/stream_chat/test/src/models/filter_test.dart new file mode 100644 index 00000000..c73c0d0b --- /dev/null +++ b/packages/stream_chat/test/src/models/filter_test.dart @@ -0,0 +1,212 @@ +import 'dart:convert'; + +import 'package:test/test.dart'; +import 'package:stream_chat/src/models/filter.dart'; + +void main() { + group('operators', () { + test('equal', () { + const key = 'testKey'; + const value = 'testValue'; + final filter = Filter.equal(key, value); + expect(filter.key, key); + expect(filter.value, value); + expect(filter.operator, FilterOperator.equal.rawValue); + }); + + test('notEqual', () { + const key = 'testKey'; + const value = 'testValue'; + final filter = Filter.notEqual(key, value); + expect(filter.key, key); + expect(filter.value, value); + expect(filter.operator, FilterOperator.notEqual.rawValue); + }); + + test('greater', () { + const key = 'testKey'; + const value = 'testValue'; + final filter = Filter.greater(key, value); + expect(filter.key, key); + expect(filter.value, value); + expect(filter.operator, FilterOperator.greater.rawValue); + }); + + test('greaterOrEqual', () { + const key = 'testKey'; + const value = 'testValue'; + final filter = Filter.greaterOrEqual(key, value); + expect(filter.key, key); + expect(filter.value, value); + expect(filter.operator, FilterOperator.greaterOrEqual.rawValue); + }); + + test('less', () { + const key = 'testKey'; + const value = 'testValue'; + final filter = Filter.less(key, value); + expect(filter.key, key); + expect(filter.value, value); + expect(filter.operator, FilterOperator.less.rawValue); + }); + + test('lessOrEqual', () { + const key = 'testKey'; + const value = 'testValue'; + final filter = Filter.lessOrEqual(key, value); + expect(filter.key, key); + expect(filter.value, value); + expect(filter.operator, FilterOperator.lessOrEqual.rawValue); + }); + + test('in', () { + const key = 'testKey'; + const values = ['testValue']; + final filter = Filter.in_(key, values); + expect(filter.key, key); + expect(filter.value, values); + expect(filter.operator, FilterOperator.in_.rawValue); + }); + + test('in', () { + const key = 'testKey'; + const values = ['testValue']; + final filter = Filter.in_(key, values); + expect(filter.key, key); + expect(filter.value, values); + expect(filter.operator, FilterOperator.in_.rawValue); + }); + + test('notIn', () { + const key = 'testKey'; + const values = ['testValue']; + final filter = Filter.notIn(key, values); + expect(filter.key, key); + expect(filter.value, values); + expect(filter.operator, FilterOperator.notIn.rawValue); + }); + + test('query', () { + const key = 'testKey'; + const value = 'testQuery'; + final filter = Filter.query(key, value); + expect(filter.key, key); + expect(filter.value, value); + expect(filter.operator, FilterOperator.query.rawValue); + }); + + test('autoComplete', () { + const key = 'testKey'; + const value = 'testQuery'; + final filter = Filter.autoComplete(key, value); + expect(filter.key, key); + expect(filter.value, value); + expect(filter.operator, FilterOperator.autoComplete.rawValue); + }); + + test('exists', () { + const key = 'testKey'; + final filter = Filter.exists(key); + expect(filter.key, key); + expect(filter.value, isTrue); + expect(filter.operator, FilterOperator.exists.rawValue); + }); + + test('notExists', () { + const key = 'testKey'; + final filter = Filter.exists(key, exists: false); + expect(filter.key, key); + expect(filter.value, isFalse); + expect(filter.operator, FilterOperator.exists.rawValue); + }); + + test('custom', () { + const key = 'testKey'; + const value = 'testValue'; + const operator = '\$customOperator'; + const filter = Filter.custom(operator: operator, key: key, value: value); + expect(filter.key, key); + expect(filter.value, value); + expect(filter.operator, operator); + }); + + group('groupedOperator', () { + final filter1 = Filter.equal('testKey', 'testValue'); + final filter2 = Filter.in_('testKey', const ['testValue']); + final filters = [filter1, filter2]; + + test('and', () { + final filter = Filter.and(filters); + expect(filter.key, isNull); + expect(filter.value, filters); + expect(filter.operator, FilterOperator.and.rawValue); + }); + + test('or', () { + final filter = Filter.or(filters); + expect(filter.key, isNull); + expect(filter.value, filters); + expect(filter.operator, FilterOperator.or.rawValue); + }); + + test('nor', () { + final filter = Filter.nor(filters); + expect(filter.key, isNull); + expect(filter.value, filters); + expect(filter.operator, FilterOperator.nor.rawValue); + }); + }); + }); + + group('encoding', () { + group('nonGroupedFilter', () { + test('simpleValue', () { + const key = 'testKey'; + const value = 'testValue'; + final filter = Filter.equal(key, value); + final encoded = json.encode(filter); + expect( + encoded, + '{"$key":{"${FilterOperator.equal.rawValue}":${json.encode(value)}}}', + ); + }); + test('listValue', () { + const key = 'testKey'; + const values = ['testValue']; + final filter = Filter.in_(key, values); + final encoded = json.encode(filter); + expect( + encoded, + '{"$key":{"${FilterOperator.in_.rawValue}":${json.encode(values)}}}', + ); + }); + }); + + test('groupedFilter', () { + final filter1 = Filter.equal('testKey', 'testValue'); + final filter2 = Filter.in_('testKey', const ['testValue']); + final filters = [filter1, filter2]; + + final filter = Filter.and(filters); + final encoded = json.encode(filter); + expect( + encoded, + '{"${FilterOperator.and.rawValue}":${json.encode(filters)}}', + ); + }); + + group('equality', () { + test('simpleFilter', () { + final filter1 = Filter.equal('testKey', 'testValue'); + final filter2 = Filter.equal('testKey', 'testValue'); + expect(filter1, filter2); + }); + + test('groupedFilter', () { + final filter1 = Filter.and([Filter.equal('testKey', 'testValue')]); + final filter2 = Filter.and([Filter.equal('testKey', 'testValue')]); + expect(filter1, filter2); + }); + }); + }); +} diff --git a/packages/stream_chat_flutter/example/lib/split_view.dart b/packages/stream_chat_flutter/example/lib/split_view.dart index 2026b101..0432a0b4 100644 --- a/packages/stream_chat_flutter/example/lib/split_view.dart +++ b/packages/stream_chat_flutter/example/lib/split_view.dart @@ -93,11 +93,10 @@ class ChannelListPage extends StatelessWidget { onTap(channel); } : null, - filter: { - 'members': { - '\$in': [StreamChat.of(context).user.id], - } - }, + filter: Filter.in_( + 'members', + [StreamChat.of(context).user.id], + ), sort: [SortOption('last_message_at')], pagination: PaginationParams( limit: 20, diff --git a/packages/stream_chat_flutter/example/lib/tutorial-part-2.dart b/packages/stream_chat_flutter/example/lib/tutorial-part-2.dart index 8b3009c8..57ee471b 100644 --- a/packages/stream_chat_flutter/example/lib/tutorial-part-2.dart +++ b/packages/stream_chat_flutter/example/lib/tutorial-part-2.dart @@ -56,11 +56,10 @@ class ChannelListPage extends StatelessWidget { return Scaffold( body: ChannelsBloc( child: ChannelListView( - filter: { - // 'members': { - // '\$in': [StreamChat.of(context).user.id], - // } - }, + filter: Filter.in_( + 'members', + [StreamChat.of(context).user.id], + ), sort: [SortOption('last_message_at')], pagination: PaginationParams( limit: 20, diff --git a/packages/stream_chat_flutter/example/lib/tutorial-part-3.dart b/packages/stream_chat_flutter/example/lib/tutorial-part-3.dart index 3a34dc01..89613b82 100644 --- a/packages/stream_chat_flutter/example/lib/tutorial-part-3.dart +++ b/packages/stream_chat_flutter/example/lib/tutorial-part-3.dart @@ -57,11 +57,10 @@ class ChannelListPage extends StatelessWidget { return Scaffold( body: ChannelsBloc( child: ChannelListView( - filter: { - 'members': { - '\$in': [StreamChat.of(context).user.id], - } - }, + filter: Filter.in_( + 'members', + [StreamChat.of(context).user.id], + ), channelPreviewBuilder: _channelPreviewBuilder, // sort: [SortOption('last_message_at')], pagination: PaginationParams( diff --git a/packages/stream_chat_flutter/example/lib/tutorial-part-4.dart b/packages/stream_chat_flutter/example/lib/tutorial-part-4.dart index 5bdd5472..82589355 100644 --- a/packages/stream_chat_flutter/example/lib/tutorial-part-4.dart +++ b/packages/stream_chat_flutter/example/lib/tutorial-part-4.dart @@ -49,11 +49,10 @@ class ChannelListPage extends StatelessWidget { return Scaffold( body: ChannelsBloc( child: ChannelListView( - filter: { - 'members': { - '\$in': [StreamChat.of(context).user.id], - } - }, + filter: Filter.in_( + 'members', + [StreamChat.of(context).user.id], + ), sort: [SortOption('last_message_at')], pagination: PaginationParams( limit: 20, diff --git a/packages/stream_chat_flutter/example/lib/tutorial-part-5.dart b/packages/stream_chat_flutter/example/lib/tutorial-part-5.dart index 18ae5ff8..b265eef3 100644 --- a/packages/stream_chat_flutter/example/lib/tutorial-part-5.dart +++ b/packages/stream_chat_flutter/example/lib/tutorial-part-5.dart @@ -52,11 +52,10 @@ class ChannelListPage extends StatelessWidget { return Scaffold( body: ChannelsBloc( child: ChannelListView( - filter: { - 'members': { - '\$in': [StreamChat.of(context).user.id], - } - }, + filter: Filter.in_( + 'members', + [StreamChat.of(context).user.id], + ), sort: [SortOption('last_message_at')], pagination: PaginationParams( limit: 20, diff --git a/packages/stream_chat_flutter/example/lib/tutorial-part-6.dart b/packages/stream_chat_flutter/example/lib/tutorial-part-6.dart index 110cc1dc..7d732eac 100644 --- a/packages/stream_chat_flutter/example/lib/tutorial-part-6.dart +++ b/packages/stream_chat_flutter/example/lib/tutorial-part-6.dart @@ -80,11 +80,10 @@ class ChannelListPage extends StatelessWidget { return Scaffold( body: ChannelsBloc( child: ChannelListView( - filter: { - 'members': { - '\$in': [StreamChat.of(context).user.id], - } - }, + filter: Filter.in_( + 'members', + [StreamChat.of(context).user.id], + ), sort: [SortOption('last_message_at')], pagination: PaginationParams( limit: 20, diff --git a/packages/stream_chat_flutter/lib/src/channel_list_view.dart b/packages/stream_chat_flutter/lib/src/channel_list_view.dart index 9751ad6d..90afbd64 100644 --- a/packages/stream_chat_flutter/lib/src/channel_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/channel_list_view.dart @@ -84,7 +84,7 @@ class ChannelListView extends StatefulWidget { /// The query filters to use. /// You can query on any of the custom fields you've defined on the [Channel]. /// You can also filter other built-in channel fields. - final Map filter; + final Filter filter; /// Query channels options. /// diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index b4b2176c..70307936 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -1243,11 +1243,10 @@ class MessageInputState extends State { Future> queryMembers; if (query.isNotEmpty) { - queryMembers = StreamChannel.of(context).channel.queryMembers(filter: { - 'name': { - '\$autocomplete': query, - }, - }).then((res) => res.members); + queryMembers = StreamChannel.of(context) + .channel + .queryMembers(filter: Filter.autoComplete('name', query)) + .then((res) => res.members); } final members = StreamChannel.of(context).channel.state.members?.where((m) { @@ -2096,7 +2095,7 @@ class MessageInputState extends State { } return sendingFuture.then((resp) { - if (resp.message?.type == 'error') { + if (resp.message?._type == 'error') { _parseExistingMessage(message); } if (widget.onMessageSent != null) { diff --git a/packages/stream_chat_flutter/lib/src/message_search_list_view.dart b/packages/stream_chat_flutter/lib/src/message_search_list_view.dart index 6431a311..d7ece34e 100644 --- a/packages/stream_chat_flutter/lib/src/message_search_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_search_list_view.dart @@ -71,7 +71,7 @@ class MessageSearchListView extends StatefulWidget { /// The query filters to use. /// You can query on any of the custom fields you've defined on the [Channel]. /// You can also filter other built-in channel fields. - final Map filters; + final Filter filters; /// The sorting used for the channels matching the filters. /// Sorting is based on field and direction, multiple sorting options can be provided. diff --git a/packages/stream_chat_flutter/lib/src/user_list_view.dart b/packages/stream_chat_flutter/lib/src/user_list_view.dart index 416f7ba0..c300971d 100644 --- a/packages/stream_chat_flutter/lib/src/user_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/user_list_view.dart @@ -72,7 +72,7 @@ class UserListView extends StatefulWidget { /// The query filters to use. /// You can query on any of the custom fields you've defined on the [Channel]. /// You can also filter other built-in channel fields. - final Map filter; + final Filter filter; /// Query channels options. /// diff --git a/packages/stream_chat_flutter_core/analysis_options.yaml b/packages/stream_chat_flutter_core/analysis_options.yaml index 545d5492..cf09a170 100644 --- a/packages/stream_chat_flutter_core/analysis_options.yaml +++ b/packages/stream_chat_flutter_core/analysis_options.yaml @@ -4,6 +4,7 @@ analyzer: - lib/**/*.freezed.dart - example/* - test/* + linter: rules: - always_use_package_imports diff --git a/packages/stream_chat_flutter_core/example/lib/main.dart b/packages/stream_chat_flutter_core/example/lib/main.dart index f5f803c8..e7ef400a 100644 --- a/packages/stream_chat_flutter_core/example/lib/main.dart +++ b/packages/stream_chat_flutter_core/example/lib/main.dart @@ -78,14 +78,12 @@ class HomeScreen extends StatelessWidget { body: ChannelsBloc( child: ChannelListCore( channelListController: channelListController, - filter: { - 'type': 'messaging', - 'members': { - r'$in': [ - StreamChatCore.of(context).user!.id, - ] - } - }, + filter: Filter.and([ + Filter.equal('type', 'messaging'), + Filter.in_('members', [ + StreamChatCore.of(context).user!.id, + ]) + ]), emptyBuilder: (BuildContext context) { return Center( child: Text('Looks like you are not in any channels'), diff --git a/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart b/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart index 322a2780..8ead2637 100644 --- a/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart @@ -92,7 +92,7 @@ class ChannelListCore extends StatefulWidget { /// The query filters to use. /// You can query on any of the custom fields you've defined on the [Channel]. /// You can also filter other built-in channel fields. - final Map? filter; + final Filter? filter; /// Query channels options. /// diff --git a/packages/stream_chat_flutter_core/lib/src/channels_bloc.dart b/packages/stream_chat_flutter_core/lib/src/channels_bloc.dart index 7910ab7e..32e63bdc 100644 --- a/packages/stream_chat_flutter_core/lib/src/channels_bloc.dart +++ b/packages/stream_chat_flutter_core/lib/src/channels_bloc.dart @@ -88,7 +88,7 @@ class ChannelsBlocState extends State /// Calls [client.queryChannels] updating [queryChannelsLoading] stream Future queryChannels({ - Map? filter, + Filter? filter, List>? sortOptions, PaginationParams paginationParams = const PaginationParams(limit: 30), Map? options, @@ -160,9 +160,8 @@ class ChannelsBlocState extends State newChannels.insert(0, _hiddenChannels[hiddenIndex]); _hiddenChannels.removeAt(hiddenIndex); } else { - if (client.state.channels != null && - client.state.channels?[e.cid] != null) { - newChannels.insert(0, client.state.channels![e.cid]!); + if (client.state.channels[e.cid] != null) { + newChannels.insert(0, client.state.channels[e.cid]!); } } } diff --git a/packages/stream_chat_flutter_core/lib/src/message_search_bloc.dart b/packages/stream_chat_flutter_core/lib/src/message_search_bloc.dart index 8586e1ff..d6220cf3 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_search_bloc.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_search_bloc.dart @@ -9,7 +9,7 @@ import 'package:stream_chat_flutter_core/src/stream_chat_core.dart'; /// [MessageSearchBloc] can be access at anytime by using the static [of] method /// using Flutter's [BuildContext]. /// -// API docs: https://getstream.io/chat/docs/flutter-dart/send_message/ +/// API docs: https://getstream.io/chat/docs/flutter-dart/send_message/ class MessageSearchBloc extends StatefulWidget { /// Instantiate a new MessageSearchBloc const MessageSearchBloc({ @@ -58,7 +58,7 @@ class MessageSearchBlocState extends State /// Calls [StreamChatClient.search] updating /// [messagesStream] and [queryMessagesLoading] stream Future search({ - required Map filter, + required Filter filter, Map? messageFilter, List? sort, String? query, diff --git a/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart b/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart index ca661dc6..2a2d31f5 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart @@ -67,7 +67,7 @@ class MessageSearchListCore extends StatefulWidget { /// The query filters to use. /// You can query on any of the custom fields you've defined on the [Channel]. /// You can also filter other built-in channel fields. - final Map filters; + final Filter filters; /// The sorting used for the channels matching the filters. /// Sorting is based on field and direction, multiple sorting options can be diff --git a/packages/stream_chat_flutter_core/lib/src/user_list_core.dart b/packages/stream_chat_flutter_core/lib/src/user_list_core.dart index 1443ef17..694c52bc 100644 --- a/packages/stream_chat_flutter_core/lib/src/user_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/user_list_core.dart @@ -90,7 +90,7 @@ class UserListCore extends StatefulWidget { /// The query filters to use. /// You can query on any of the custom fields you've defined on the [Channel]. /// You can also filter other built-in channel fields. - final Map? filter; + final Filter? filter; /// Query channels options. /// diff --git a/packages/stream_chat_flutter_core/lib/src/users_bloc.dart b/packages/stream_chat_flutter_core/lib/src/users_bloc.dart index dcc3252f..9b33f75c 100644 --- a/packages/stream_chat_flutter_core/lib/src/users_bloc.dart +++ b/packages/stream_chat_flutter_core/lib/src/users_bloc.dart @@ -58,7 +58,7 @@ class UsersBlocState extends State /// online/offline. /// [API Reference](https://getstream.io/chat/docs/flutter-dart/query_users/?language=dart) Future queryUsers({ - Map? filter, + Filter? filter, List? sort, Map? options, PaginationParams? pagination, diff --git a/packages/stream_chat_flutter_core/test/channel_list_core_test.dart b/packages/stream_chat_flutter_core/test/channel_list_core_test.dart index 5bbdaf39..53303231 100644 --- a/packages/stream_chat_flutter_core/test/channel_list_core_test.dart +++ b/packages/stream_chat_flutter_core/test/channel_list_core_test.dart @@ -24,7 +24,7 @@ void main() { client, 'testType$index', 'testId$index', - {'extra_data_key': 'extra_data_value_$index'}, + extraData: {'extra_data_key': 'extra_data_value_$index'}, ); }, ); diff --git a/packages/stream_chat_flutter_core/test/channels_bloc_test.dart b/packages/stream_chat_flutter_core/test/channels_bloc_test.dart index 28136add..19d327bd 100644 --- a/packages/stream_chat_flutter_core/test/channels_bloc_test.dart +++ b/packages/stream_chat_flutter_core/test/channels_bloc_test.dart @@ -26,7 +26,7 @@ void main() { client, 'testType$index', 'testId$index', - {'extra_data_key': 'extra_data_value_$index'}, + extraData: {'extra_data_key': 'extra_data_value_$index'}, ); }, ); @@ -703,7 +703,7 @@ void main() { final mockClient = MockClient(); final channels = _generateChannels(mockClient); final stateChannels = { - for (var c in _generateChannels(mockClient, offset: 5)) c.cid: c + for (var c in _generateChannels(mockClient, offset: 5)) c.cid!: c }; const channelsBlocKey = Key('channelsBloc'); final channelsBloc = ChannelsBloc( diff --git a/packages/stream_chat_flutter_core/test/message_search_bloc_test.dart b/packages/stream_chat_flutter_core/test/message_search_bloc_test.dart index 5b427c02..91c07435 100644 --- a/packages/stream_chat_flutter_core/test/message_search_bloc_test.dart +++ b/packages/stream_chat_flutter_core/test/message_search_bloc_test.dart @@ -8,6 +8,8 @@ import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'matchers/get_message_response_matcher.dart'; import 'mocks.dart'; +final testFilter = Filter.custom(operator: '\$test', value: 'testValue'); + void main() { List _generateMessages({ int count = 3, @@ -50,7 +52,7 @@ void main() { ); try { - await usersBlocState.search(filter: {}); + await usersBlocState.search(filter: testFilter); } catch (e) { expect(e, isInstanceOf()); } @@ -82,7 +84,7 @@ void main() { final messageResponseList = _generateMessages(); when(() => mockClient.search( - any(), + testFilter, query: any(named: 'query'), sort: any(named: 'sort'), messageFilters: any(named: 'messageFilters'), @@ -91,7 +93,7 @@ void main() { (_) async => SearchMessagesResponse()..results = messageResponseList, ); - messageSearchBlocState.search(filter: {}); + messageSearchBlocState.search(filter: testFilter); await expectLater( messageSearchBlocState.messagesStream, @@ -99,7 +101,7 @@ void main() { ); verify(() => mockClient.search( - any(), + testFilter, query: any(named: 'query'), sort: any(named: 'sort'), messageFilters: any(named: 'messageFilters'), @@ -133,14 +135,14 @@ void main() { const error = 'Error! Error! Error!'; when(() => mockClient.search( - any(), + testFilter, query: any(named: 'query'), sort: any(named: 'sort'), messageFilters: any(named: 'messageFilters'), paginationParams: any(named: 'paginationParams'), )).thenThrow(error); - messageSearchBlocState.search(filter: {}); + messageSearchBlocState.search(filter: testFilter); await expectLater( messageSearchBlocState.messagesStream, @@ -148,7 +150,7 @@ void main() { ); verify(() => mockClient.search( - any(), + testFilter, query: any(named: 'query'), sort: any(named: 'sort'), messageFilters: any(named: 'messageFilters'), @@ -185,7 +187,7 @@ void main() { final messageResponseList = _generateMessages(); when(() => mockClient.search( - any(), + testFilter, query: any(named: 'query'), sort: any(named: 'sort'), messageFilters: any(named: 'messageFilters'), @@ -194,7 +196,7 @@ void main() { (_) async => SearchMessagesResponse()..results = messageResponseList, ); - messageSearchBlocState.search(filter: {}); + messageSearchBlocState.search(filter: testFilter); await expectLater( messageSearchBlocState.messagesStream, @@ -202,7 +204,7 @@ void main() { ); verify(() => mockClient.search( - any(), + testFilter, query: any(named: 'query'), sort: any(named: 'sort'), messageFilters: any(named: 'messageFilters'), @@ -214,7 +216,7 @@ void main() { final pagination = PaginationParams(offset: offset); when(() => mockClient.search( - any(), + testFilter, query: any(named: 'query'), sort: any(named: 'sort'), messageFilters: any(named: 'messageFilters'), @@ -224,7 +226,7 @@ void main() { SearchMessagesResponse()..results = paginatedMessageResponseList, ); - messageSearchBlocState.search(pagination: pagination, filter: {}); + messageSearchBlocState.search(pagination: pagination, filter: testFilter); await Future.wait([ expectLater( @@ -240,7 +242,7 @@ void main() { ]); verify(() => mockClient.search( - any(), + testFilter, query: any(named: 'query'), sort: any(named: 'sort'), messageFilters: any(named: 'messageFilters'), @@ -277,7 +279,7 @@ void main() { final messageResponseList = _generateMessages(); when(() => mockClient.search( - any(), + testFilter, query: any(named: 'query'), sort: any(named: 'sort'), messageFilters: any(named: 'messageFilters'), @@ -286,7 +288,7 @@ void main() { (_) async => SearchMessagesResponse()..results = messageResponseList, ); - messageSearchBlocState.search(filter: {}); + messageSearchBlocState.search(filter: testFilter); await expectLater( messageSearchBlocState.messagesStream, @@ -294,7 +296,7 @@ void main() { ); verify(() => mockClient.search( - any(), + testFilter, query: any(named: 'query'), sort: any(named: 'sort'), messageFilters: any(named: 'messageFilters'), @@ -306,14 +308,14 @@ void main() { const error = 'Error! Error! Error!'; when(() => mockClient.search( - any(), + testFilter, query: any(named: 'query'), sort: any(named: 'sort'), messageFilters: any(named: 'messageFilters'), paginationParams: pagination, )).thenThrow(error); - messageSearchBlocState.search(pagination: pagination, filter: {}); + messageSearchBlocState.search(pagination: pagination, filter: testFilter); await expectLater( messageSearchBlocState.queryMessagesLoading, @@ -321,7 +323,7 @@ void main() { ); verify(() => mockClient.search( - any(), + testFilter, query: any(named: 'query'), sort: any(named: 'sort'), messageFilters: any(named: 'messageFilters'), diff --git a/packages/stream_chat_flutter_core/test/message_search_list_core_test.dart b/packages/stream_chat_flutter_core/test/message_search_list_core_test.dart index 00aee32a..1f7414d0 100644 --- a/packages/stream_chat_flutter_core/test/message_search_list_core_test.dart +++ b/packages/stream_chat_flutter_core/test/message_search_list_core_test.dart @@ -6,6 +6,8 @@ import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'mocks.dart'; +final testFilter = Filter.custom(operator: '\$test', value: 'testValue'); + void main() { List _generateMessages({ int count = 3, @@ -37,7 +39,7 @@ void main() { loadingBuilder: (BuildContext context) => const Offstage(), emptyBuilder: (BuildContext context) => const Offstage(), errorBuilder: (BuildContext context, Object? error) => const Offstage(), - filters: const {}, + filters: testFilter, ); await tester.pumpWidget(messageSearchListCore); @@ -58,7 +60,7 @@ void main() { loadingBuilder: (BuildContext context) => Offstage(), emptyBuilder: (BuildContext context) => Offstage(), errorBuilder: (BuildContext context, Object? error) => Offstage(), - filters: {}, + filters: testFilter, ); final mockClient = MockClient(); @@ -89,7 +91,7 @@ void main() { emptyBuilder: (BuildContext context) => Offstage(), errorBuilder: (BuildContext context, Object error) => Offstage(), messageSearchListController: controller, - filters: {}, + filters: testFilter, ); expect(controller.loadData, isNull); @@ -125,14 +127,14 @@ void main() { errorBuilder: (BuildContext context, Object error) => Offstage( key: errorWidgetKey, ), - filters: {}, + filters: testFilter, ); final mockClient = MockClient(); const error = 'Error! Error! Error!'; when(() => mockClient.search( - any(), + testFilter, query: any(named: 'query'), sort: any(named: 'sort'), messageFilters: any(named: 'messageFilters'), @@ -153,7 +155,7 @@ void main() { expect(find.byKey(errorWidgetKey), findsOneWidget); verify(() => mockClient.search( - any(), + testFilter, query: any(named: 'query'), sort: any(named: 'sort'), messageFilters: any(named: 'messageFilters'), @@ -174,14 +176,14 @@ void main() { loadingBuilder: (BuildContext context) => Offstage(), emptyBuilder: (BuildContext context) => Offstage(key: emptyWidgetKey), errorBuilder: (BuildContext context, Object error) => Offstage(), - filters: {}, + filters: testFilter, ); final mockClient = MockClient(); final messageResponseList = []; when(() => mockClient.search( - any(), + testFilter, query: any(named: 'query'), sort: any(named: 'sort'), messageFilters: any(named: 'messageFilters'), @@ -204,7 +206,7 @@ void main() { expect(find.byKey(emptyWidgetKey), findsOneWidget); verify(() => mockClient.search( - any(), + testFilter, query: any(named: 'query'), sort: any(named: 'sort'), messageFilters: any(named: 'messageFilters'), @@ -226,14 +228,14 @@ void main() { loadingBuilder: (BuildContext context) => Offstage(), emptyBuilder: (BuildContext context) => Offstage(), errorBuilder: (BuildContext context, Object error) => Offstage(), - filters: {}, + filters: testFilter, ); final mockClient = MockClient(); final messageResponseList = _generateMessages(); when(() => mockClient.search( - any(), + testFilter, query: any(named: 'query'), sort: any(named: 'sort'), messageFilters: any(named: 'messageFilters'), @@ -256,7 +258,7 @@ void main() { expect(find.byKey(childWidgetKey), findsOneWidget); verify(() => mockClient.search( - any(), + testFilter, query: any(named: 'query'), sort: any(named: 'sort'), messageFilters: any(named: 'messageFilters'), @@ -284,14 +286,14 @@ void main() { emptyBuilder: (BuildContext context) => Offstage(), errorBuilder: (BuildContext context, Object error) => Offstage(), paginationParams: pagination, - filters: {}, + filters: testFilter, ); final mockClient = MockClient(); final messageResponseList = _generateMessages(); when(() => mockClient.search( - any(), + testFilter, query: any(named: 'query'), sort: any(named: 'sort'), messageFilters: any(named: 'messageFilters'), @@ -325,7 +327,7 @@ void main() { ); verify(() => mockClient.search( - any(), + testFilter, query: any(named: 'query'), sort: any(named: 'sort'), messageFilters: any(named: 'messageFilters'), @@ -341,7 +343,7 @@ void main() { final paginatedMessageResponseList = _generateMessages(offset: offset); final updatedPagination = pagination.copyWith(offset: offset); when(() => mockClient.search( - any(), + testFilter, query: any(named: 'query'), sort: any(named: 'sort'), messageFilters: any(named: 'messageFilters'), @@ -365,7 +367,7 @@ void main() { ); verify(() => mockClient.search( - any(), + testFilter, query: any(named: 'query'), sort: any(named: 'sort'), messageFilters: any(named: 'messageFilters'), @@ -400,14 +402,14 @@ void main() { emptyBuilder: (BuildContext context) => Offstage(), errorBuilder: (BuildContext context, Object error) => Offstage(), paginationParams: pagination.copyWith(limit: limit), - filters: {}, + filters: testFilter, ); final mockClient = MockClient(); final messageResponseList = _generateMessages(); when(() => mockClient.search( - any(), + testFilter, query: any(named: 'query'), sort: any(named: 'sort'), messageFilters: any(named: 'messageFilters'), @@ -445,7 +447,7 @@ void main() { ); verify(() => mockClient.search( - any(), + testFilter, query: any(named: 'query'), sort: any(named: 'sort'), messageFilters: any(named: 'messageFilters'), @@ -458,7 +460,7 @@ void main() { final updatedMessageResponseList = _generateMessages(count: limit); final updatedPagination = pagination.copyWith(limit: limit); when(() => mockClient.search( - any(), + testFilter, query: any(named: 'query'), sort: any(named: 'sort'), messageFilters: any(named: 'messageFilters'), @@ -479,7 +481,7 @@ void main() { ); verify(() => mockClient.search( - any(), + testFilter, query: any(named: 'query'), sort: any(named: 'sort'), messageFilters: any(named: 'messageFilters'), diff --git a/packages/stream_chat_persistence/lib/src/dao/channel_query_dao.dart b/packages/stream_chat_persistence/lib/src/dao/channel_query_dao.dart index 099f6798..5bff63dc 100644 --- a/packages/stream_chat_persistence/lib/src/dao/channel_query_dao.dart +++ b/packages/stream_chat_persistence/lib/src/dao/channel_query_dao.dart @@ -18,7 +18,7 @@ class ChannelQueryDao extends DatabaseAccessor /// Creates a new channel query dao instance ChannelQueryDao(MoorChatDatabase db) : super(db); - String _computeHash(Map? filter) { + String _computeHash(Filter? filter) { if (filter == null) { return 'allchannels'; } @@ -30,7 +30,7 @@ class ChannelQueryDao extends DatabaseAccessor /// If [clearQueryCache] is true before the insert /// the list of matching rows will be deleted Future updateChannelQueries( - Map filter, + Filter? filter, List cids, { bool clearQueryCache = false, }) async => @@ -58,7 +58,7 @@ class ChannelQueryDao extends DatabaseAccessor }); /// - Future> getCachedChannelCids(Map? filter) { + Future> getCachedChannelCids(Filter? filter) { final hash = _computeHash(filter); return (select(channelQueries)..where((c) => c.queryHash.equals(hash))) .map((c) => c.channelCid) @@ -67,7 +67,7 @@ class ChannelQueryDao extends DatabaseAccessor /// Get list of channels by filter, sort and paginationParams Future> getChannels({ - Map? filter, + Filter? filter, List>? sort, PaginationParams? paginationParams, }) async { diff --git a/packages/stream_chat_persistence/lib/src/stream_chat_persistence_client.dart b/packages/stream_chat_persistence/lib/src/stream_chat_persistence_client.dart index 3f955eb2..20c4e327 100644 --- a/packages/stream_chat_persistence/lib/src/stream_chat_persistence_client.dart +++ b/packages/stream_chat_persistence/lib/src/stream_chat_persistence_client.dart @@ -253,7 +253,7 @@ class StreamChatPersistenceClient extends ChatPersistenceClient { @override Future> getChannelStates({ - Map? filter, + Filter? filter, List>? sort, PaginationParams? paginationParams, }) { @@ -273,7 +273,7 @@ class StreamChatPersistenceClient extends ChatPersistenceClient { @override Future updateChannelQueries( - Map filter, + Filter? filter, List cids, { bool clearQueryCache = false, }) { diff --git a/packages/stream_chat_persistence/test/src/dao/channel_query_dao_test.dart b/packages/stream_chat_persistence/test/src/dao/channel_query_dao_test.dart index dee6e076..3f04711d 100644 --- a/packages/stream_chat_persistence/test/src/dao/channel_query_dao_test.dart +++ b/packages/stream_chat_persistence/test/src/dao/channel_query_dao_test.dart @@ -17,11 +17,7 @@ void main() { }); test('updateChannelQueries', () async { - const filter = { - 'members': { - r'$in': ['testUserId'], - }, - }; + final filter = Filter.in_('members', const ['testUserId']); const cids = ['testCid1', 'testCid2', 'testCid3']; @@ -36,11 +32,7 @@ void main() { }); test('clear queryCache before updateChannelQueries', () async { - const filter = { - 'members': { - r'$in': ['testUserId'], - }, - }; + final filter = Filter.in_('members', const ['testUserId']); const cids = ['testCid1', 'testCid2', 'testCid3']; @@ -59,11 +51,7 @@ void main() { }); test('getCachedChannelCids', () async { - const filter = { - 'members': { - r'$in': ['testUserId'], - }, - }; + final filter = Filter.in_('members', const ['testUserId']); const cids = ['testCid1', 'testCid2', 'testCid3']; @@ -78,7 +66,7 @@ void main() { }); Future> _insertTestDataForGetChannel( - Map filter, { + Filter filter, { int count = 3, }) async { final now = DateTime.now(); @@ -110,11 +98,7 @@ void main() { } group('getChannels', () { - const filter = { - 'members': { - r'$in': ['testUserId'], - }, - }; + final filter = Filter.in_('members', const ['testUserId']); test('should return empty list of channels', () async { final channels = await channelQueryDao.getChannels(filter: filter); diff --git a/packages/stream_chat_persistence/test/stream_chat_persistence_client_test.dart b/packages/stream_chat_persistence/test/stream_chat_persistence_client_test.dart index 26d0bfb5..8a275201 100644 --- a/packages/stream_chat_persistence/test/stream_chat_persistence_client_test.dart +++ b/packages/stream_chat_persistence/test/stream_chat_persistence_client_test.dart @@ -292,7 +292,7 @@ void main() { }); test('updateChannelQueries', () async { - const filter = {}; + final filter = Filter.in_('members', const ['testUserId']); const cids = []; when(() => mockDatabase.channelQueryDao.updateChannelQueries(filter, cids))