Add custom sort support for offline channels
Signed-off-by: Sahil Kumar <[email protected]>
This commit is contained in:
@@ -1245,7 +1245,9 @@ class ChannelClientState {
|
|||||||
_channel._client.chatPersistenceClient
|
_channel._client.chatPersistenceClient
|
||||||
?.getChannelStateByCid(_channel.cid)
|
?.getChannelStateByCid(_channel.cid)
|
||||||
?.then((state) {
|
?.then((state) {
|
||||||
updateChannelState(state);
|
// Replacing the persistence state members with the latest `channelState.members`
|
||||||
|
// as they may have changes over the time.
|
||||||
|
updateChannelState(state.copyWith(members: channelState.members));
|
||||||
retryFailedMessages();
|
retryFailedMessages();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ part 'requests.g.dart';
|
|||||||
|
|
||||||
/// Sorting options
|
/// Sorting options
|
||||||
@JsonSerializable(createFactory: false)
|
@JsonSerializable(createFactory: false)
|
||||||
class SortOption {
|
class SortOption<T> {
|
||||||
/// Ascending order
|
/// Ascending order
|
||||||
static const ASC = 1;
|
static const ASC = 1;
|
||||||
|
|
||||||
@@ -17,6 +17,10 @@ class SortOption {
|
|||||||
/// A sorting direction
|
/// A sorting direction
|
||||||
final int direction;
|
final int direction;
|
||||||
|
|
||||||
|
/// Sorting field Comparator required for offline sorting
|
||||||
|
@JsonKey(ignore: true)
|
||||||
|
final Comparator<T> comparator;
|
||||||
|
|
||||||
/// Creates a new SortOption instance
|
/// Creates a new SortOption instance
|
||||||
///
|
///
|
||||||
/// For example:
|
/// For example:
|
||||||
@@ -24,7 +28,11 @@ class SortOption {
|
|||||||
/// // Sort channels by the last message date:
|
/// // Sort channels by the last message date:
|
||||||
/// final sorting = SortOption("last_message_at")
|
/// final sorting = SortOption("last_message_at")
|
||||||
/// ```
|
/// ```
|
||||||
const SortOption(this.field, {this.direction = DESC});
|
const SortOption(
|
||||||
|
this.field, {
|
||||||
|
this.direction = DESC,
|
||||||
|
this.comparator,
|
||||||
|
});
|
||||||
|
|
||||||
/// Serialize model to json
|
/// Serialize model to json
|
||||||
Map<String, dynamic> toJson() => _$SortOptionToJson(this);
|
Map<String, dynamic> toJson() => _$SortOptionToJson(this);
|
||||||
@@ -67,7 +75,7 @@ class PaginationParams {
|
|||||||
/// ```
|
/// ```
|
||||||
const PaginationParams({
|
const PaginationParams({
|
||||||
this.limit = 10,
|
this.limit = 10,
|
||||||
this.offset,
|
this.offset = 0,
|
||||||
this.greaterThan,
|
this.greaterThan,
|
||||||
this.greaterThanOrEqual,
|
this.greaterThanOrEqual,
|
||||||
this.lessThan,
|
this.lessThan,
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import 'package:rxdart/rxdart.dart';
|
|||||||
import 'package:stream_chat/src/api/retry_policy.dart';
|
import 'package:stream_chat/src/api/retry_policy.dart';
|
||||||
import 'package:stream_chat/src/event_type.dart';
|
import 'package:stream_chat/src/event_type.dart';
|
||||||
import 'package:stream_chat/src/models/attachment_file.dart';
|
import 'package:stream_chat/src/models/attachment_file.dart';
|
||||||
|
import 'package:stream_chat/src/models/channel_model.dart';
|
||||||
import 'package:stream_chat/src/models/own_user.dart';
|
import 'package:stream_chat/src/models/own_user.dart';
|
||||||
import 'package:stream_chat/src/platform_detector/platform_detector.dart';
|
import 'package:stream_chat/src/platform_detector/platform_detector.dart';
|
||||||
import 'package:stream_chat/version.dart';
|
import 'package:stream_chat/version.dart';
|
||||||
@@ -22,6 +23,7 @@ import 'api/responses.dart';
|
|||||||
import 'api/websocket.dart';
|
import 'api/websocket.dart';
|
||||||
import 'db/chat_persistence_client.dart';
|
import 'db/chat_persistence_client.dart';
|
||||||
import 'exceptions.dart';
|
import 'exceptions.dart';
|
||||||
|
import 'models/channel_state.dart';
|
||||||
import 'models/event.dart';
|
import 'models/event.dart';
|
||||||
import 'models/message.dart';
|
import 'models/message.dart';
|
||||||
import 'models/user.dart';
|
import 'models/user.dart';
|
||||||
@@ -489,7 +491,7 @@ class StreamChatClient {
|
|||||||
|
|
||||||
if (status == ConnectionStatus.connected &&
|
if (status == ConnectionStatus.connected &&
|
||||||
state.channels?.isNotEmpty == true) {
|
state.channels?.isNotEmpty == true) {
|
||||||
unawaited(queryChannels(filter: {
|
unawaited(_queryChannels(filter: {
|
||||||
'cid': {
|
'cid': {
|
||||||
'\$in': state.channels.keys.toList(),
|
'\$in': state.channels.keys.toList(),
|
||||||
},
|
},
|
||||||
@@ -578,15 +580,15 @@ class StreamChatClient {
|
|||||||
final _queryChannelsStreams = <String, Future<List<Channel>>>{};
|
final _queryChannelsStreams = <String, Future<List<Channel>>>{};
|
||||||
|
|
||||||
/// Requests channels with a given query.
|
/// Requests channels with a given query.
|
||||||
Future<List<Channel>> queryChannels({
|
Stream<List<Channel>> queryChannels({
|
||||||
Map<String, dynamic> filter,
|
Map<String, dynamic> filter,
|
||||||
List<SortOption> sort,
|
List<SortOption<ChannelModel>> sort,
|
||||||
Map<String, dynamic> options,
|
Map<String, dynamic> options,
|
||||||
PaginationParams paginationParams = const PaginationParams(limit: 10),
|
PaginationParams paginationParams = const PaginationParams(limit: 10),
|
||||||
int messageLimit,
|
int messageLimit,
|
||||||
bool onlyOffline = false,
|
bool preferOffline = true,
|
||||||
bool waitForConnect = true,
|
bool waitForConnect = true,
|
||||||
}) async {
|
}) async* {
|
||||||
if (waitForConnect) {
|
if (waitForConnect) {
|
||||||
if (_connectCompleter != null && !_connectCompleter.isCompleted) {
|
if (_connectCompleter != null && !_connectCompleter.isCompleted) {
|
||||||
logger.info('awaiting connection completer');
|
logger.info('awaiting connection completer');
|
||||||
@@ -598,7 +600,7 @@ class StreamChatClient {
|
|||||||
if (persistenceEnabled) {
|
if (persistenceEnabled) {
|
||||||
logger.warning(
|
logger.warning(
|
||||||
'$errorMessage\nTrying to retrieve channels from the offline storage.');
|
'$errorMessage\nTrying to retrieve channels from the offline storage.');
|
||||||
onlyOffline = true;
|
preferOffline = true;
|
||||||
} else {
|
} else {
|
||||||
throw Exception(errorMessage);
|
throw Exception(errorMessage);
|
||||||
}
|
}
|
||||||
@@ -606,34 +608,41 @@ class StreamChatClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final hash = base64.encode(utf8.encode(
|
final hash = base64.encode(utf8.encode(
|
||||||
'$filter${_asMap(sort)}$options${paginationParams?.toJson()}$messageLimit$onlyOffline'));
|
'$filter${_asMap(sort)}$options${paginationParams?.toJson()}$messageLimit$preferOffline',
|
||||||
|
));
|
||||||
|
|
||||||
if (_queryChannelsStreams.containsKey(hash)) {
|
if (_queryChannelsStreams.containsKey(hash)) {
|
||||||
return _queryChannelsStreams[hash];
|
yield await _queryChannelsStreams[hash];
|
||||||
|
} else {
|
||||||
|
if (true) {
|
||||||
|
final channels = await _queryChannelsOffline(
|
||||||
|
filter: filter,
|
||||||
|
sort: sort,
|
||||||
|
paginationParams: paginationParams,
|
||||||
|
);
|
||||||
|
if (channels.isNotEmpty) yield channels;
|
||||||
|
}
|
||||||
|
|
||||||
|
final newQueryChannelsFuture = _queryChannels(
|
||||||
|
filter: filter,
|
||||||
|
sort: sort,
|
||||||
|
options: options,
|
||||||
|
paginationParams: paginationParams,
|
||||||
|
messageLimit: messageLimit,
|
||||||
|
);
|
||||||
|
|
||||||
|
_queryChannelsStreams[hash] = newQueryChannelsFuture;
|
||||||
|
|
||||||
|
yield await newQueryChannelsFuture;
|
||||||
}
|
}
|
||||||
|
|
||||||
final newQueryChannelsStream = _doQueryChannels(
|
|
||||||
filter: filter,
|
|
||||||
sort: sort,
|
|
||||||
options: options,
|
|
||||||
paginationParams: paginationParams,
|
|
||||||
messageLimit: messageLimit,
|
|
||||||
onlyOffline: onlyOffline,
|
|
||||||
).whenComplete(() {
|
|
||||||
_queryChannelsStreams.remove(hash);
|
|
||||||
});
|
|
||||||
|
|
||||||
_queryChannelsStreams[hash] = newQueryChannelsStream;
|
|
||||||
|
|
||||||
return newQueryChannelsStream;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<Channel>> _doQueryChannels({
|
Future<List<Channel>> _queryChannels({
|
||||||
@required Map<String, dynamic> filter,
|
@required Map<String, dynamic> filter,
|
||||||
@required List<SortOption> sort,
|
List<SortOption<ChannelModel>> sort,
|
||||||
@required Map<String, dynamic> options,
|
Map<String, dynamic> options,
|
||||||
@required int messageLimit,
|
int messageLimit,
|
||||||
PaginationParams paginationParams = const PaginationParams(limit: 10),
|
PaginationParams paginationParams = const PaginationParams(limit: 10),
|
||||||
bool onlyOffline = false,
|
|
||||||
}) async {
|
}) async {
|
||||||
logger.info('Query channel start');
|
logger.info('Query channel start');
|
||||||
final defaultOptions = {
|
final defaultOptions = {
|
||||||
@@ -661,86 +670,85 @@ class StreamChatClient {
|
|||||||
payload.addAll(paginationParams.toJson());
|
payload.addAll(paginationParams.toJson());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (onlyOffline) {
|
final response = await get(
|
||||||
return _queryChannelsOffline(
|
'/channels',
|
||||||
filter: filter,
|
queryParameters: {
|
||||||
sort: sort,
|
'payload': jsonEncode(payload),
|
||||||
paginationParams: paginationParams,
|
},
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
final res = decode<QueryChannelsResponse>(
|
||||||
final response = await get(
|
response.data,
|
||||||
'/channels',
|
QueryChannelsResponse.fromJson,
|
||||||
queryParameters: {
|
);
|
||||||
'payload': jsonEncode(payload),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
final res = decode<QueryChannelsResponse>(
|
if (res.channels?.isEmpty == true && (paginationParams?.offset ?? 0) == 0) {
|
||||||
response.data,
|
logger.warning('''We could not find any channel for this query.
|
||||||
QueryChannelsResponse.fromJson,
|
|
||||||
);
|
|
||||||
|
|
||||||
final users = res.channels
|
|
||||||
?.expand((channel) => channel.members.map((member) => member.user))
|
|
||||||
?.toList();
|
|
||||||
|
|
||||||
if (users != null) {
|
|
||||||
state._updateUsers(users);
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info('Got ${res.channels?.length} channels from api');
|
|
||||||
|
|
||||||
if (res.channels?.isEmpty != false &&
|
|
||||||
(paginationParams?.offset ?? 0) == 0) {
|
|
||||||
logger.warning('''We could not find any channel for this query.
|
|
||||||
Please make sure to take a look at the Flutter tutorial: https://getstream.io/chat/flutter/tutorial
|
Please make sure to take a look at the Flutter tutorial: https://getstream.io/chat/flutter/tutorial
|
||||||
If your application already has users and channels, you might need to adjust your query channel as explained in the docs https://getstream.io/chat/docs/query_channels/?language=dart''');
|
If your application already has users and channels, you might need to adjust your query channel as explained in the docs https://getstream.io/chat/docs/query_channels/?language=dart''');
|
||||||
}
|
return <Channel>[];
|
||||||
|
|
||||||
final newChannels = Map<String, Channel>.from(state.channels ?? {});
|
|
||||||
final channels = <Channel>[];
|
|
||||||
|
|
||||||
if (res.channels != null) {
|
|
||||||
for (final channelState in res.channels) {
|
|
||||||
final channel = newChannels[channelState.channel.cid];
|
|
||||||
if (channel != null) {
|
|
||||||
channel.state?.updateChannelState(channelState);
|
|
||||||
channels.add(channel);
|
|
||||||
} else {
|
|
||||||
final newChannel = Channel.fromState(this, channelState);
|
|
||||||
await chatPersistenceClient
|
|
||||||
?.updateChannelState(newChannel.state.channelState);
|
|
||||||
newChannel.state?.updateChannelState(channelState);
|
|
||||||
newChannels[newChannel.cid] = newChannel;
|
|
||||||
channels.add(newChannel);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
state.channels = newChannels;
|
|
||||||
|
|
||||||
await chatPersistenceClient?.updateChannelQueries(
|
|
||||||
filter,
|
|
||||||
res.channels.map((c) => c.channel.cid).toList(),
|
|
||||||
paginationParams?.offset == null || paginationParams.offset == 0,
|
|
||||||
);
|
|
||||||
|
|
||||||
return channels;
|
|
||||||
} catch (e) {
|
|
||||||
if (!persistenceEnabled) {
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
return _queryChannelsOffline(
|
|
||||||
filter: filter,
|
|
||||||
sort: sort,
|
|
||||||
paginationParams: paginationParams,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final channels = res.channels;
|
||||||
|
|
||||||
|
final users = channels
|
||||||
|
.expand((it) => it.members)
|
||||||
|
.map((it) => it.user)
|
||||||
|
.toList(growable: false);
|
||||||
|
|
||||||
|
state._updateUsers(users);
|
||||||
|
|
||||||
|
logger.info('Got ${res.channels?.length} channels from api');
|
||||||
|
|
||||||
|
final updateData = _mapChannelStateToChannel(channels);
|
||||||
|
|
||||||
|
await chatPersistenceClient?.updateChannelQueries(
|
||||||
|
filter,
|
||||||
|
channels.map((c) => c.channel.cid).toList(),
|
||||||
|
paginationParams?.offset == null || paginationParams.offset == 0,
|
||||||
|
);
|
||||||
|
|
||||||
|
state.channels = updateData.key;
|
||||||
|
return updateData.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
dynamic _parseError(DioError error) {
|
Future<List<Channel>> _queryChannelsOffline({
|
||||||
|
@required Map<String, dynamic> filter,
|
||||||
|
@required List<SortOption<ChannelModel>> sort,
|
||||||
|
PaginationParams paginationParams = const PaginationParams(limit: 10),
|
||||||
|
}) async {
|
||||||
|
final offlineChannels = await chatPersistenceClient?.getChannelStates(
|
||||||
|
filter: filter,
|
||||||
|
sort: sort,
|
||||||
|
paginationParams: paginationParams,
|
||||||
|
);
|
||||||
|
final updatedData = _mapChannelStateToChannel(offlineChannels);
|
||||||
|
state.channels = updatedData.key;
|
||||||
|
return updatedData.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
MapEntry<Map<String, Channel>, List<Channel>> _mapChannelStateToChannel(
|
||||||
|
List<ChannelState> channelStates,
|
||||||
|
) {
|
||||||
|
final channels = {...state.channels ?? {}};
|
||||||
|
final newChannels = <Channel>[];
|
||||||
|
if (channelStates != null) {
|
||||||
|
for (final channelState in channelStates) {
|
||||||
|
final channel = channels[channelState.channel.cid];
|
||||||
|
if (channel != null) {
|
||||||
|
channel.state?.updateChannelState(channelState);
|
||||||
|
newChannels.add(channel);
|
||||||
|
} else {
|
||||||
|
final newChannel = Channel.fromState(this, channelState);
|
||||||
|
channels[newChannel.cid] = newChannel;
|
||||||
|
newChannels.add(newChannel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return MapEntry(channels, newChannels);
|
||||||
|
}
|
||||||
|
|
||||||
|
Object _parseError(DioError error) {
|
||||||
if (error.type == DioErrorType.RESPONSE) {
|
if (error.type == DioErrorType.RESPONSE) {
|
||||||
final apiError =
|
final apiError =
|
||||||
ApiError(error.response?.data, error.response?.statusCode);
|
ApiError(error.response?.data, error.response?.statusCode);
|
||||||
@@ -751,39 +759,6 @@ class StreamChatClient {
|
|||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<Channel>> _queryChannelsOffline({
|
|
||||||
@required Map<String, dynamic> filter,
|
|
||||||
@required List<SortOption> sort,
|
|
||||||
PaginationParams paginationParams = const PaginationParams(limit: 10),
|
|
||||||
}) async {
|
|
||||||
final offlineChannels = await chatPersistenceClient?.getChannelStates(
|
|
||||||
filter: filter,
|
|
||||||
sort: sort,
|
|
||||||
paginationParams: paginationParams,
|
|
||||||
) ??
|
|
||||||
[];
|
|
||||||
final newChannels = Map<String, Channel>.from(state.channels ?? {});
|
|
||||||
logger.info('Got ${offlineChannels.length} channels from storage');
|
|
||||||
final channels = offlineChannels.map((channelState) {
|
|
||||||
final channel = newChannels[channelState.channel.cid];
|
|
||||||
if (channel != null) {
|
|
||||||
channel.state?.updateChannelState(channelState);
|
|
||||||
return channel;
|
|
||||||
} else {
|
|
||||||
final newChannel = Channel.fromState(this, channelState);
|
|
||||||
chatPersistenceClient
|
|
||||||
?.updateChannelState(newChannel.state.channelState);
|
|
||||||
newChannels[newChannel.cid] = newChannel;
|
|
||||||
return newChannel;
|
|
||||||
}
|
|
||||||
}).toList();
|
|
||||||
|
|
||||||
if (channels.isNotEmpty) {
|
|
||||||
state.channels = newChannels;
|
|
||||||
}
|
|
||||||
return channels;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Handy method to make http GET request with error parsing.
|
/// Handy method to make http GET request with error parsing.
|
||||||
Future<Response<String>> get(
|
Future<Response<String>> get(
|
||||||
String path, {
|
String path, {
|
||||||
@@ -1448,18 +1423,16 @@ class ClientState {
|
|||||||
_userController.add(user);
|
_userController.add(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _updateUsers(List<User> users) {
|
void _updateUsers(List<User> userList) {
|
||||||
users?.forEach(_updateUser);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _updateUser(User user) {
|
|
||||||
final newUsers = {
|
final newUsers = {
|
||||||
...users ?? {},
|
...users ?? {},
|
||||||
user.id: user,
|
for (var user in userList) user.id: user,
|
||||||
};
|
};
|
||||||
_usersController.add(newUsers);
|
_usersController.add(newUsers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _updateUser(User user) => _updateUsers([user]);
|
||||||
|
|
||||||
/// The current user
|
/// The current user
|
||||||
OwnUser get user => _userController.value;
|
OwnUser get user => _userController.value;
|
||||||
|
|
||||||
|
|||||||
@@ -68,23 +68,19 @@ abstract class ChatPersistenceClient {
|
|||||||
PaginationParams messagePagination,
|
PaginationParams messagePagination,
|
||||||
PaginationParams pinnedMessagePagination,
|
PaginationParams pinnedMessagePagination,
|
||||||
}) async {
|
}) async {
|
||||||
final members = await getMembersByCid(cid);
|
final data = await Future.wait([
|
||||||
final reads = await getReadsByCid(cid);
|
getMembersByCid(cid),
|
||||||
final channel = await getChannelByCid(cid);
|
getReadsByCid(cid),
|
||||||
final messages = await getMessagesByCid(
|
getChannelByCid(cid),
|
||||||
cid,
|
getMessagesByCid(cid, messagePagination: messagePagination),
|
||||||
messagePagination: messagePagination,
|
getPinnedMessagesByCid(cid,messagePagination: pinnedMessagePagination),
|
||||||
);
|
]);
|
||||||
final pinnedMessages = await getPinnedMessagesByCid(
|
|
||||||
cid,
|
|
||||||
messagePagination: pinnedMessagePagination,
|
|
||||||
);
|
|
||||||
return ChannelState(
|
return ChannelState(
|
||||||
members: members,
|
members: data[0],
|
||||||
read: reads,
|
read: data[1],
|
||||||
messages: messages,
|
channel: data[2],
|
||||||
pinnedMessages: pinnedMessages,
|
messages: data[3],
|
||||||
channel: channel,
|
pinnedMessages: data[4],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,7 +90,7 @@ abstract class ChatPersistenceClient {
|
|||||||
/// for filtering out states.
|
/// for filtering out states.
|
||||||
Future<List<ChannelState>> getChannelStates({
|
Future<List<ChannelState>> getChannelStates({
|
||||||
Map<String, dynamic> filter,
|
Map<String, dynamic> filter,
|
||||||
List<SortOption> sort = const [],
|
List<SortOption<ChannelModel>> sort = const [],
|
||||||
PaginationParams paginationParams,
|
PaginationParams paginationParams,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import 'message.dart';
|
|||||||
|
|
||||||
part 'channel_state.g.dart';
|
part 'channel_state.g.dart';
|
||||||
|
|
||||||
/// The class that contains the information about a command
|
/// The class that contains the information about a channel
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
class ChannelState {
|
class ChannelState {
|
||||||
/// The channel to which this state belongs
|
/// The channel to which this state belongs
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:rxdart/rxdart.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
import 'package:stream_chat/version.dart';
|
import 'package:stream_chat/version.dart';
|
||||||
|
|
||||||
@@ -10,14 +11,113 @@ void prepareTest() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void main() {
|
// void main() {
|
||||||
prepareTest();
|
// prepareTest();
|
||||||
test('stream chat version matches pubspec', () {
|
// test('stream chat version matches pubspec', () {
|
||||||
final String pubspecPath = '${Directory.current.path}/pubspec.yaml';
|
// final String pubspecPath = '${Directory.current.path}/pubspec.yaml';
|
||||||
final String pubspec = File(pubspecPath).readAsStringSync();
|
// final String pubspec = File(pubspecPath).readAsStringSync();
|
||||||
final RegExp regex = RegExp('version:\s*(.*)');
|
// final RegExp regex = RegExp('version:\s*(.*)');
|
||||||
final RegExpMatch match = regex.firstMatch(pubspec);
|
// final RegExpMatch match = regex.firstMatch(pubspec);
|
||||||
expect(match, isNotNull);
|
// expect(match, isNotNull);
|
||||||
expect(PACKAGE_VERSION, match.group(1).trim());
|
// expect(PACKAGE_VERSION, match.group(1).trim());
|
||||||
});
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
void main() async {
|
||||||
|
var items = [
|
||||||
|
ABC('Sahil', 22, 62.0),
|
||||||
|
ABC('Devraj', 23, 76.0),
|
||||||
|
ABC('Harsh', 18, 48.0),
|
||||||
|
ABC('Harsh', 17, 88.0),
|
||||||
|
ABC('Harsh', 17, 48.0),
|
||||||
|
ABC('Devraj', 23, 74.0, {
|
||||||
|
'Test': 'Sahil',
|
||||||
|
}),
|
||||||
|
ABC('Devraj', 12, 76.0, {
|
||||||
|
'Test': 'Avni',
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
var comparators = [
|
||||||
|
(ABC a, ABC b) {
|
||||||
|
// if (a.extraData == null) return -1;
|
||||||
|
// if (b.extraData == null) return 1;
|
||||||
|
// if (a.extraData == null && b.extraData == null) return 0;
|
||||||
|
var aa = (a.extraData ?? {})['Test'] as String;
|
||||||
|
var bb = (b.extraData ?? {})['Test'] as String;
|
||||||
|
return aa.compareTo(bb);
|
||||||
|
},
|
||||||
|
// (ABC a, ABC b) => a.name.compareTo(b.name),
|
||||||
|
// (ABC a, ABC b) => a.age.compareTo(b.age),
|
||||||
|
// (ABC a, ABC b) => a.weight.compareTo(b.weight),
|
||||||
|
];
|
||||||
|
|
||||||
|
// for (var comp in comparators.reversed) {
|
||||||
|
// items.sort(comp);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// items.sort(comparators[last])
|
||||||
|
|
||||||
|
Stream<String> getLaugh2() {
|
||||||
|
if (true) {
|
||||||
|
return Stream.value('HEHO');
|
||||||
|
} else {
|
||||||
|
return getLaugh2();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream<String> getLaugh() async* {
|
||||||
|
yield 'HAHA';
|
||||||
|
await Future.delayed(const Duration(seconds: 3));
|
||||||
|
yield 'HOHO';
|
||||||
|
await Future.delayed(const Duration(seconds: 3));
|
||||||
|
yield 'HEHE';
|
||||||
|
}
|
||||||
|
|
||||||
|
// final stream = BehaviorSubject.seeded('HAHA');
|
||||||
|
//
|
||||||
|
// stream.('HOHO');
|
||||||
|
|
||||||
|
await for (var value in getLaugh2()) {
|
||||||
|
print(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// stream.add('HEHE');
|
||||||
|
|
||||||
|
// compare(ABC a, ABC b) {
|
||||||
|
// int result;
|
||||||
|
// for (final comparator in comparators) {
|
||||||
|
// try {
|
||||||
|
// result = comparator(a, b);
|
||||||
|
// } catch (e) {
|
||||||
|
// result = 0;
|
||||||
|
// }
|
||||||
|
// if (result != 0) return result;
|
||||||
|
// }
|
||||||
|
// return 0;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// items.sort(compare);
|
||||||
|
//
|
||||||
|
// print(items);
|
||||||
|
}
|
||||||
|
|
||||||
|
class ABC {
|
||||||
|
final String name;
|
||||||
|
final int age;
|
||||||
|
final double weight;
|
||||||
|
final Map<String, Object> extraData;
|
||||||
|
|
||||||
|
const ABC(this.name, this.age, this.weight, [this.extraData]);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return '''
|
||||||
|
\n
|
||||||
|
Name : $name,
|
||||||
|
Age : $age,
|
||||||
|
Weight : $weight,
|
||||||
|
ExtraData : $extraData,
|
||||||
|
''';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,8 +49,11 @@ class ChannelInfo extends StatelessWidget {
|
|||||||
var alternativeWidget;
|
var alternativeWidget;
|
||||||
|
|
||||||
if (channel.memberCount != null && channel.memberCount > 2) {
|
if (channel.memberCount != null && channel.memberCount > 2) {
|
||||||
|
var text = '${channel.memberCount} Members';
|
||||||
|
final watcherCount = channel.state.watcherCount ?? 0;
|
||||||
|
if (watcherCount > 0) text += ' $watcherCount Online';
|
||||||
alternativeWidget = Text(
|
alternativeWidget = Text(
|
||||||
'${channel.memberCount} Members, ${channel.state.watcherCount} Online',
|
text,
|
||||||
style: StreamChatTheme.of(context)
|
style: StreamChatTheme.of(context)
|
||||||
.channelTheme
|
.channelTheme
|
||||||
.channelHeaderTheme
|
.channelHeaderTheme
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ class ChannelListView extends StatefulWidget {
|
|||||||
/// Sorting is based on field and direction, multiple sorting options can be provided.
|
/// Sorting is based on field and direction, multiple sorting options can be provided.
|
||||||
/// You can sort based on last_updated, last_message_at, updated_at, created_at or member_count.
|
/// You can sort based on last_updated, last_message_at, updated_at, created_at or member_count.
|
||||||
/// Direction can be ascending or descending.
|
/// Direction can be ascending or descending.
|
||||||
final List<SortOption> sort;
|
final List<SortOption<ChannelModel>> sort;
|
||||||
|
|
||||||
/// Pagination parameters
|
/// Pagination parameters
|
||||||
/// limit: the number of channels to return (max is 30)
|
/// limit: the number of channels to return (max is 30)
|
||||||
@@ -159,54 +159,42 @@ class ChannelListView extends StatefulWidget {
|
|||||||
_ChannelListViewState createState() => _ChannelListViewState();
|
_ChannelListViewState createState() => _ChannelListViewState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ChannelListViewState extends State<ChannelListView>
|
class _ChannelListViewState extends State<ChannelListView> {
|
||||||
with WidgetsBindingObserver {
|
final _slideController = SlidableController();
|
||||||
final ScrollController _scrollController = ScrollController();
|
|
||||||
final SlidableController _slideController = SlidableController();
|
final _channelListController = ChannelListController();
|
||||||
final ChannelListController _channelListController = ChannelListController();
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var child = ChannelListCore(
|
Widget child = ChannelListCore(
|
||||||
channelListController: _channelListController,
|
|
||||||
listBuilder: widget.listBuilder ??
|
|
||||||
(context, list) {
|
|
||||||
return _buildListView(list);
|
|
||||||
},
|
|
||||||
emptyBuilder: widget.emptyBuilder ??
|
|
||||||
(BuildContext context) {
|
|
||||||
return _buildEmptyWidget();
|
|
||||||
},
|
|
||||||
errorBuilder: widget.errorBuilder ??
|
|
||||||
(BuildContext context, dynamic error) {
|
|
||||||
return _buildErrorWidget(context);
|
|
||||||
},
|
|
||||||
loadingBuilder: widget.loadingBuilder ??
|
|
||||||
(BuildContext context) {
|
|
||||||
return _buildLoadingWidget();
|
|
||||||
},
|
|
||||||
pagination: widget.pagination,
|
pagination: widget.pagination,
|
||||||
options: widget.options,
|
options: widget.options,
|
||||||
sort: widget.sort,
|
sort: widget.sort,
|
||||||
filter: widget.filter,
|
filter: widget.filter,
|
||||||
|
channelListController: _channelListController,
|
||||||
|
listBuilder: widget.listBuilder ?? _buildListView,
|
||||||
|
emptyBuilder: widget.emptyBuilder ?? _buildEmptyWidget,
|
||||||
|
errorBuilder: widget.errorBuilder ?? _buildErrorWidget,
|
||||||
|
loadingBuilder: widget.loadingBuilder ?? _buildLoadingWidget,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!widget.pullToRefresh) {
|
if (widget.pullToRefresh) {
|
||||||
return child;
|
child = RefreshIndicator(
|
||||||
} else {
|
onRefresh: () async => _channelListController.loadData(),
|
||||||
return RefreshIndicator(
|
|
||||||
onRefresh: () async {
|
|
||||||
_channelListController.loadData();
|
|
||||||
},
|
|
||||||
child: child,
|
child: child,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return LazyLoadScrollView(
|
||||||
|
onEndOfPage: () async {
|
||||||
|
_channelListController.paginateData();
|
||||||
|
},
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildListView(
|
Widget _buildListView(BuildContext context, List<Channel> channels) {
|
||||||
List<Channel> channels,
|
Widget child;
|
||||||
) {
|
|
||||||
var child;
|
|
||||||
|
|
||||||
if (channels.isNotEmpty) {
|
if (channels.isNotEmpty) {
|
||||||
if (widget.crossAxisCount > 1) {
|
if (widget.crossAxisCount > 1) {
|
||||||
@@ -216,7 +204,6 @@ class _ChannelListViewState extends State<ChannelListView>
|
|||||||
crossAxisCount: widget.crossAxisCount),
|
crossAxisCount: widget.crossAxisCount),
|
||||||
itemCount: channels.length,
|
itemCount: channels.length,
|
||||||
physics: AlwaysScrollableScrollPhysics(),
|
physics: AlwaysScrollableScrollPhysics(),
|
||||||
controller: _scrollController,
|
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
return _gridItemBuilder(context, index, channels);
|
return _gridItemBuilder(context, index, channels);
|
||||||
},
|
},
|
||||||
@@ -236,18 +223,17 @@ class _ChannelListViewState extends State<ChannelListView>
|
|||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
return _listItemBuilder(context, index, channels);
|
return _listItemBuilder(context, index, channels);
|
||||||
},
|
},
|
||||||
controller: _scrollController,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return AnimatedSwitcher(
|
return AnimatedSwitcher(
|
||||||
child: child,
|
child: child,
|
||||||
duration: Duration(milliseconds: 500),
|
duration: const Duration(milliseconds: 500),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildEmptyWidget() {
|
Widget _buildEmptyWidget(BuildContext context) {
|
||||||
return LayoutBuilder(
|
return LayoutBuilder(
|
||||||
builder: (context, viewportConstraints) {
|
builder: (context, viewportConstraints) {
|
||||||
return SingleChildScrollView(
|
return SingleChildScrollView(
|
||||||
@@ -326,7 +312,7 @@ class _ChannelListViewState extends State<ChannelListView>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildLoadingWidget() {
|
Widget _buildLoadingWidget(BuildContext context) {
|
||||||
return ListView(
|
return ListView(
|
||||||
padding: widget.padding,
|
padding: widget.padding,
|
||||||
physics: AlwaysScrollableScrollPhysics(),
|
physics: AlwaysScrollableScrollPhysics(),
|
||||||
@@ -341,13 +327,13 @@ class _ChannelListViewState extends State<ChannelListView>
|
|||||||
return _separatorBuilder(context, i);
|
return _separatorBuilder(context, i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return _buildLoadingItem();
|
return _buildLoadingItem(context);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Shimmer _buildLoadingItem() {
|
Shimmer _buildLoadingItem(BuildContext context) {
|
||||||
if (widget.crossAxisCount > 1) {
|
if (widget.crossAxisCount > 1) {
|
||||||
return Shimmer.fromColors(
|
return Shimmer.fromColors(
|
||||||
baseColor: StreamChatTheme.of(context).colorTheme.greyGainsboro,
|
baseColor: StreamChatTheme.of(context).colorTheme.greyGainsboro,
|
||||||
@@ -443,9 +429,7 @@ class _ChannelListViewState extends State<ChannelListView>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildErrorWidget(
|
Widget _buildErrorWidget(BuildContext context, Object error) {
|
||||||
BuildContext context,
|
|
||||||
) {
|
|
||||||
return Center(
|
return Center(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
@@ -572,23 +556,13 @@ class _ChannelListViewState extends State<ChannelListView>
|
|||||||
],
|
],
|
||||||
child: Container(
|
child: Container(
|
||||||
color: StreamChatTheme.of(context).colorTheme.whiteSnow,
|
color: StreamChatTheme.of(context).colorTheme.whiteSnow,
|
||||||
child: widget.channelPreviewBuilder != null
|
child: widget.channelPreviewBuilder?.call(context, channel) ??
|
||||||
? widget.channelPreviewBuilder(
|
ChannelPreview(
|
||||||
context,
|
onLongPress: widget.onChannelLongPress,
|
||||||
channel,
|
channel: channel,
|
||||||
)
|
onImageTap: widget.onImageTap?.call(channel),
|
||||||
: ChannelPreview(
|
onTap: (channel) => onTap(channel, widget.channelWidget),
|
||||||
onLongPress: widget.onChannelLongPress,
|
),
|
||||||
channel: channel,
|
|
||||||
onImageTap: widget.onImageTap != null
|
|
||||||
? () {
|
|
||||||
widget.onImageTap(channel);
|
|
||||||
}
|
|
||||||
: null,
|
|
||||||
onTap: (channel) {
|
|
||||||
onTap(channel, widget.channelWidget);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -618,9 +592,7 @@ class _ChannelListViewState extends State<ChannelListView>
|
|||||||
width: 64,
|
width: 64,
|
||||||
height: 64,
|
height: 64,
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () => widget.onChannelTap(channel, null),
|
||||||
widget.onChannelTap(channel, null);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
SizedBox(height: 7),
|
SizedBox(height: 7),
|
||||||
Padding(
|
Padding(
|
||||||
@@ -680,70 +652,4 @@ class _ChannelListViewState extends State<ChannelListView>
|
|||||||
color: effect.color.withOpacity(effect.alpha ?? 1.0),
|
color: effect.color.withOpacity(effect.alpha ?? 1.0),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _listenChannelPagination(ChannelsBlocState channelsProvider) {
|
|
||||||
if (_scrollController.position.maxScrollExtent ==
|
|
||||||
_scrollController.offset &&
|
|
||||||
_scrollController.offset != 0) {
|
|
||||||
_channelListController.paginateData();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
StreamSubscription _subscription;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
|
|
||||||
WidgetsBinding.instance.addObserver(this);
|
|
||||||
|
|
||||||
final channelsBloc = ChannelsBloc.of(context);
|
|
||||||
channelsBloc.queryChannels(
|
|
||||||
filter: widget.filter,
|
|
||||||
sortOptions: widget.sort,
|
|
||||||
paginationParams: widget.pagination,
|
|
||||||
options: widget.options,
|
|
||||||
);
|
|
||||||
|
|
||||||
_scrollController.addListener(() {
|
|
||||||
channelsBloc.queryChannelsLoading.first.then((loading) {
|
|
||||||
if (!loading) {
|
|
||||||
_listenChannelPagination(channelsBloc);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
final client = StreamChat.of(context).client;
|
|
||||||
|
|
||||||
_subscription = client
|
|
||||||
.on(
|
|
||||||
EventType.connectionRecovered,
|
|
||||||
EventType.notificationAddedToChannel,
|
|
||||||
EventType.notificationMessageNew,
|
|
||||||
EventType.channelVisible,
|
|
||||||
)
|
|
||||||
.listen((event) {
|
|
||||||
_channelListController.loadData();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void didUpdateWidget(ChannelListView oldWidget) {
|
|
||||||
super.didUpdateWidget(oldWidget);
|
|
||||||
|
|
||||||
if (widget.filter?.toString() != oldWidget.filter?.toString() ||
|
|
||||||
jsonEncode(widget.sort) != jsonEncode(oldWidget.sort) ||
|
|
||||||
widget.pagination?.toJson()?.toString() !=
|
|
||||||
oldWidget.pagination?.toJson()?.toString() ||
|
|
||||||
widget.options?.toString() != oldWidget.options?.toString()) {
|
|
||||||
_channelListController.loadData();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
_subscription.cancel();
|
|
||||||
WidgetsBinding.instance.removeObserver(this);
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ class ChannelListCore extends StatefulWidget {
|
|||||||
/// Sorting is based on field and direction, multiple sorting options can be provided.
|
/// Sorting is based on field and direction, multiple sorting options can be provided.
|
||||||
/// You can sort based on last_updated, last_message_at, updated_at, created_at or member_count.
|
/// You can sort based on last_updated, last_message_at, updated_at, created_at or member_count.
|
||||||
/// Direction can be ascending or descending.
|
/// Direction can be ascending or descending.
|
||||||
final List<SortOption> sort;
|
final List<SortOption<ChannelModel>> sort;
|
||||||
|
|
||||||
/// Pagination parameters
|
/// Pagination parameters
|
||||||
/// limit: the number of channels to return (max is 30)
|
/// limit: the number of channels to return (max is 30)
|
||||||
@@ -118,8 +118,7 @@ class ChannelListCore extends StatefulWidget {
|
|||||||
_ChannelListCoreState createState() => _ChannelListCoreState();
|
_ChannelListCoreState createState() => _ChannelListCoreState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ChannelListCoreState extends State<ChannelListCore>
|
class _ChannelListCoreState extends State<ChannelListCore> {
|
||||||
with WidgetsBindingObserver {
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final channelsBloc = ChannelsBloc.of(context);
|
final channelsBloc = ChannelsBloc.of(context);
|
||||||
@@ -133,34 +132,21 @@ class _ChannelListCoreState extends State<ChannelListCore>
|
|||||||
return StreamBuilder<List<Channel>>(
|
return StreamBuilder<List<Channel>>(
|
||||||
stream: channelsBlocState.channelsStream,
|
stream: channelsBlocState.channelsStream,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
var child;
|
|
||||||
if (snapshot.hasError) {
|
if (snapshot.hasError) {
|
||||||
child = _buildErrorWidget(
|
return _buildErrorWidget(snapshot, context, channelsBlocState);
|
||||||
snapshot,
|
|
||||||
context,
|
|
||||||
channelsBlocState,
|
|
||||||
);
|
|
||||||
} else if (!snapshot.hasData) {
|
|
||||||
child = _buildLoadingWidget();
|
|
||||||
} else {
|
|
||||||
final channels = snapshot.data;
|
|
||||||
|
|
||||||
child = widget.emptyBuilder(context);
|
|
||||||
|
|
||||||
if (channels.isNotEmpty) {
|
|
||||||
return widget.listBuilder(context, channels);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if (!snapshot.hasData) {
|
||||||
return child;
|
return widget.loadingBuilder(context);
|
||||||
|
}
|
||||||
|
final channels = snapshot.data;
|
||||||
|
if (channels.isEmpty) {
|
||||||
|
return widget.emptyBuilder(context);
|
||||||
|
}
|
||||||
|
return widget.listBuilder(context, channels);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildLoadingWidget() {
|
|
||||||
return widget.loadingBuilder(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildErrorWidget(
|
Widget _buildErrorWidget(
|
||||||
AsyncSnapshot<List<Channel>> snapshot,
|
AsyncSnapshot<List<Channel>> snapshot,
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
@@ -197,39 +183,21 @@ class _ChannelListCoreState extends State<ChannelListCore>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
StreamSubscription _subscription;
|
StreamSubscription<Event> _subscription;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
loadData();
|
||||||
WidgetsBinding.instance.addObserver(this);
|
|
||||||
|
|
||||||
final channelsBloc = ChannelsBloc.of(context);
|
|
||||||
channelsBloc.queryChannels(
|
|
||||||
filter: widget.filter,
|
|
||||||
sortOptions: widget.sort,
|
|
||||||
paginationParams: widget.pagination,
|
|
||||||
options: widget.options,
|
|
||||||
);
|
|
||||||
|
|
||||||
final client = StreamChatCore.of(context).client;
|
final client = StreamChatCore.of(context).client;
|
||||||
|
|
||||||
_subscription = client
|
_subscription = client
|
||||||
.on(
|
.on(
|
||||||
EventType.connectionRecovered,
|
EventType.connectionRecovered,
|
||||||
EventType.notificationAddedToChannel,
|
EventType.notificationAddedToChannel,
|
||||||
EventType.notificationMessageNew,
|
EventType.notificationMessageNew,
|
||||||
EventType.channelVisible,
|
EventType.channelVisible,
|
||||||
)
|
)
|
||||||
.listen((event) {
|
.listen((event) => loadData());
|
||||||
channelsBloc.queryChannels(
|
|
||||||
filter: widget.filter,
|
|
||||||
sortOptions: widget.sort,
|
|
||||||
paginationParams: widget.pagination,
|
|
||||||
options: widget.options,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (widget.channelListController != null) {
|
if (widget.channelListController != null) {
|
||||||
widget.channelListController.loadData = loadData;
|
widget.channelListController.loadData = loadData;
|
||||||
@@ -246,20 +214,13 @@ class _ChannelListCoreState extends State<ChannelListCore>
|
|||||||
widget.pagination?.toJson()?.toString() !=
|
widget.pagination?.toJson()?.toString() !=
|
||||||
oldWidget.pagination?.toJson()?.toString() ||
|
oldWidget.pagination?.toJson()?.toString() ||
|
||||||
widget.options?.toString() != oldWidget.options?.toString()) {
|
widget.options?.toString() != oldWidget.options?.toString()) {
|
||||||
final channelsBloc = ChannelsBloc.of(context);
|
loadData();
|
||||||
channelsBloc.queryChannels(
|
|
||||||
filter: widget.filter,
|
|
||||||
sortOptions: widget.sort,
|
|
||||||
paginationParams: widget.pagination,
|
|
||||||
options: widget.options,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_subscription.cancel();
|
_subscription.cancel();
|
||||||
WidgetsBinding.instance.removeObserver(this);
|
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ class ChannelsBlocState extends State<ChannelsBloc>
|
|||||||
/// Calls [client.queryChannels] updating [queryChannelsLoading] stream
|
/// Calls [client.queryChannels] updating [queryChannelsLoading] stream
|
||||||
Future<void> queryChannels({
|
Future<void> queryChannels({
|
||||||
Map<String, dynamic> filter,
|
Map<String, dynamic> filter,
|
||||||
List<SortOption> sortOptions,
|
List<SortOption<ChannelModel>> sortOptions,
|
||||||
PaginationParams paginationParams,
|
PaginationParams paginationParams,
|
||||||
Map<String, dynamic> options,
|
Map<String, dynamic> options,
|
||||||
bool onlyOffline = false,
|
bool onlyOffline = false,
|
||||||
@@ -102,21 +102,21 @@ class ChannelsBlocState extends State<ChannelsBloc>
|
|||||||
paginationParams.offset == null ||
|
paginationParams.offset == null ||
|
||||||
paginationParams.offset == 0;
|
paginationParams.offset == 0;
|
||||||
final oldChannels = List<Channel>.from(channels ?? []);
|
final oldChannels = List<Channel>.from(channels ?? []);
|
||||||
final _channels = await client.queryChannels(
|
await for (final channels in client.queryChannels(
|
||||||
filter: filter,
|
filter: filter,
|
||||||
sort: sortOptions,
|
sort: sortOptions,
|
||||||
options: options,
|
options: options,
|
||||||
paginationParams: paginationParams,
|
paginationParams: paginationParams,
|
||||||
onlyOffline: onlyOffline,
|
preferOffline: onlyOffline,
|
||||||
);
|
)) {
|
||||||
|
if (clear) {
|
||||||
if (clear) {
|
_channelsController.add(channels);
|
||||||
_channelsController.add(_channels);
|
} else {
|
||||||
} else {
|
final l = oldChannels + channels;
|
||||||
final l = oldChannels + _channels;
|
_channelsController.add(l);
|
||||||
_channelsController.add(l);
|
}
|
||||||
|
_queryChannelsLoadingController.sink.add(false);
|
||||||
}
|
}
|
||||||
_queryChannelsLoadingController.sink.add(false);
|
|
||||||
} catch (err, stackTrace) {
|
} catch (err, stackTrace) {
|
||||||
print(err);
|
print(err);
|
||||||
print(stackTrace);
|
print(stackTrace);
|
||||||
|
|||||||
@@ -15,9 +15,7 @@ part 'channel_query_dao.g.dart';
|
|||||||
class ChannelQueryDao extends DatabaseAccessor<MoorChatDatabase>
|
class ChannelQueryDao extends DatabaseAccessor<MoorChatDatabase>
|
||||||
with _$ChannelQueryDaoMixin {
|
with _$ChannelQueryDaoMixin {
|
||||||
/// Creates a new channel query dao instance
|
/// Creates a new channel query dao instance
|
||||||
ChannelQueryDao(this._db) : super(_db);
|
ChannelQueryDao(MoorChatDatabase db) : super(db);
|
||||||
|
|
||||||
final MoorChatDatabase _db;
|
|
||||||
|
|
||||||
String _computeHash(Map<String, dynamic> filter) {
|
String _computeHash(Map<String, dynamic> filter) {
|
||||||
if (filter == null) {
|
if (filter == null) {
|
||||||
@@ -37,19 +35,19 @@ class ChannelQueryDao extends DatabaseAccessor<MoorChatDatabase>
|
|||||||
) async {
|
) async {
|
||||||
final hash = _computeHash(filter);
|
final hash = _computeHash(filter);
|
||||||
if (clearQueryCache) {
|
if (clearQueryCache) {
|
||||||
await (delete(channelQueries)
|
await batch((it) {
|
||||||
..where((query) => query.queryHash.equals(hash)))
|
it.deleteWhere<ChannelQueries, ChannelQueryEntity>(
|
||||||
.go();
|
channelQueries,
|
||||||
|
(c) => c.queryHash.equals(hash),
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return batch((batch) {
|
return batch((it) {
|
||||||
batch.insertAll(
|
it.insertAll(
|
||||||
channelQueries,
|
channelQueries,
|
||||||
cids.map((cid) {
|
cids.map((cid) {
|
||||||
return ChannelQueryEntity(
|
return ChannelQueryEntity(queryHash: hash, channelCid: cid);
|
||||||
queryHash: hash,
|
|
||||||
channelCid: cid,
|
|
||||||
);
|
|
||||||
}).toList(),
|
}).toList(),
|
||||||
mode: InsertMode.insertOrReplace,
|
mode: InsertMode.insertOrReplace,
|
||||||
);
|
);
|
||||||
@@ -57,70 +55,74 @@ class ChannelQueryDao extends DatabaseAccessor<MoorChatDatabase>
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Get list of channels by filter, sort and paginationParams
|
/// Get list of channels by filter, sort and paginationParams
|
||||||
Future<List<ChannelState>> getChannelStates({
|
Future<List<ChannelModel>> getChannels({
|
||||||
Map<String, dynamic> filter,
|
Map<String, dynamic> filter,
|
||||||
List<SortOption> sort = const [],
|
List<SortOption<ChannelModel>> sort = const [],
|
||||||
PaginationParams paginationParams,
|
PaginationParams paginationParams,
|
||||||
}) async {
|
}) async {
|
||||||
|
assert(() {
|
||||||
|
if (sort != null && sort.any((it) => it.comparator == null)) {
|
||||||
|
throw ArgumentError(
|
||||||
|
'SortOption requires a comparator in order to sort',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}());
|
||||||
|
|
||||||
final hash = _computeHash(filter);
|
final hash = _computeHash(filter);
|
||||||
final cachedChannels = await Future.wait(await (select(channelQueries)
|
final cachedChannelCids = await (select(channelQueries)
|
||||||
..where((c) => c.queryHash.equals(hash)))
|
..where((c) => c.queryHash.equals(hash)))
|
||||||
.get()
|
.map((c) => c.channelCid)
|
||||||
.then((channelQueries) {
|
.get();
|
||||||
final cids = channelQueries.map((c) => c.channelCid).toList();
|
|
||||||
final query = select(channels)..where((c) => c.cid.isIn(cids));
|
|
||||||
|
|
||||||
sort = sort
|
final query = select(channels)..where((c) => c.cid.isIn(cachedChannelCids));
|
||||||
?.where((s) => ChannelModel.topLevelFields.contains(s.field))
|
|
||||||
?.toList();
|
|
||||||
|
|
||||||
if (sort != null && sort.isNotEmpty) {
|
final cachedChannels = await (query.join([
|
||||||
query.orderBy(sort.map((s) {
|
leftOuterJoin(users, channels.createdById.equalsExp(users.id)),
|
||||||
final orderExpression = CustomExpression('channels.${s.field}');
|
]).map((row) {
|
||||||
return (c) => OrderingTerm(
|
final createdByEntity = row.readTable(users);
|
||||||
expression: orderExpression,
|
final channelEntity = row.readTable(channels);
|
||||||
mode: s.direction == 1 ? OrderingMode.asc : OrderingMode.desc,
|
return channelEntity.toChannelModel(createdBy: createdByEntity?.toUser());
|
||||||
);
|
})).get();
|
||||||
}).toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (paginationParams != null) {
|
final possibleSortingFields = cachedChannels.fold<List<String>>(
|
||||||
query.limit(
|
ChannelModel.topLevelFields, (previousValue, element) {
|
||||||
paginationParams.limit ?? 10,
|
return {...previousValue, ...element.extraData.keys}.toList();
|
||||||
offset: paginationParams.offset,
|
});
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return query.join([
|
sort = sort
|
||||||
leftOuterJoin(users, channels.createdById.equalsExp(users.id)),
|
?.where((s) => possibleSortingFields.contains(s.field))
|
||||||
]).map((row) async {
|
?.toList(growable: false);
|
||||||
final userEntity = row.readTable(users);
|
|
||||||
final channelEntity = row.readTable(channels);
|
|
||||||
|
|
||||||
final cid = channelEntity.cid;
|
Comparator<ChannelModel> chainedComparator = (a, b) {
|
||||||
final members = await _db.memberDao.getMembersByCid(cid);
|
final dateA = a.lastMessageAt ?? a.createdAt;
|
||||||
final reads = await _db.readDao.getReadsByCid(cid);
|
final dateB = b.lastMessageAt ?? b.createdAt;
|
||||||
final messages = await _db.messageDao.getMessagesByCid(cid);
|
return dateB.compareTo(dateA);
|
||||||
final pinnedMessages = await _db.pinnedMessageDao.getMessagesByCid(cid);
|
};
|
||||||
|
|
||||||
return channelEntity.toChannelState(
|
if (sort != null && sort.isNotEmpty) {
|
||||||
createdBy: userEntity?.toUser(),
|
chainedComparator = (a, b) {
|
||||||
members: members,
|
int result;
|
||||||
reads: reads,
|
for (final comparator in sort.map((it) => it.comparator)) {
|
||||||
messages: messages,
|
try {
|
||||||
pinnedMessages: pinnedMessages,
|
result = comparator(a, b);
|
||||||
);
|
} catch (e) {
|
||||||
}).get();
|
result = 0;
|
||||||
}));
|
}
|
||||||
|
if (result != 0) return result;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (sort?.isEmpty != false && cachedChannels?.isNotEmpty == true) {
|
cachedChannels.sort(chainedComparator);
|
||||||
cachedChannels
|
|
||||||
.sort((a, b) => b.channel.updatedAt.compareTo(a.channel.updatedAt));
|
if (paginationParams?.offset != null) {
|
||||||
cachedChannels.sort((a, b) {
|
cachedChannels.removeRange(0, paginationParams.offset);
|
||||||
final dateA = a.channel.lastMessageAt ?? a.channel.createdAt;
|
}
|
||||||
final dateB = b.channel.lastMessageAt ?? b.channel.createdAt;
|
|
||||||
return dateB.compareTo(dateA);
|
if (paginationParams?.limit != null) {
|
||||||
});
|
return cachedChannels.take(paginationParams.limit).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
return cachedChannels;
|
return cachedChannels;
|
||||||
|
|||||||
@@ -161,14 +161,15 @@ class StreamChatPersistenceClient extends ChatPersistenceClient {
|
|||||||
@override
|
@override
|
||||||
Future<List<ChannelState>> getChannelStates({
|
Future<List<ChannelState>> getChannelStates({
|
||||||
Map<String, dynamic> filter,
|
Map<String, dynamic> filter,
|
||||||
List<SortOption> sort = const [],
|
List<SortOption<ChannelModel>> sort = const [],
|
||||||
PaginationParams paginationParams,
|
PaginationParams paginationParams,
|
||||||
}) {
|
}) async {
|
||||||
return _db.channelQueryDao.getChannelStates(
|
final channels = await _db.channelQueryDao.getChannels(
|
||||||
filter: filter,
|
filter: filter,
|
||||||
sort: sort,
|
sort: sort,
|
||||||
paginationParams: paginationParams,
|
paginationParams: paginationParams,
|
||||||
);
|
);
|
||||||
|
return Future.wait(channels.map((e) => getChannelStateByCid(e.cid)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
Reference in New Issue
Block a user