Release/1.4.0 beta (#318)

* fix: fixed long system messages overflowing

* fix: fixed long system messages overflowing

* [UI-Kit] Refactor, add support for downloading attachments.

Signed-off-by: Sahil Kumar <[email protected]>

* fix: header casing

* [UI-Kit] Disable attachment download dialog pop using back button

Signed-off-by: Sahil Kumar <[email protected]>

* stream_chat_flutter fix svg on web

* [UI-Kit] Pop attachment download dialog in case of any error.

Signed-off-by: Sahil Kumar <[email protected]>

* restore svg

* feat: add pr title linter

* fix test

* fix client headers

* update system message design

* update gh token env var

* add scopes

* expose systemMessageBuilder in messagelistview

* fix(ui): fix button size

* add dispatch workflow

* add dispatch workflow

* update dispatch acton

* add runson

* fix ref

* fix ref

* update secret

* specify repo

* fix: list scroll keyboard behaviour  (#304)

* fix: Keyboard now closes by clicking on space or scrolling.

* fix: Exposed system and normal message tap builders

* fmt: dartfmt

* feat: Add support for messages filter in `MessageListView` and `MessageListCore` (#303)

* [MessageListView, MessageListCore] Add support for message filter

Signed-off-by: Sahil Kumar <[email protected]>

* [UI-Kit -> Pubspec] Remove `stream_chat_flutter_core` relative import

Signed-off-by: Sahil Kumar <[email protected]>

* add relative import

* add relative import

* remove svg for web

* remove badge logic on web

* style: Update for team lint (#297)

* Update for team lint

* fix tests

* remove pedantic and sort deps

* remove jiffy from system message

Co-authored-by: Salvatore Giordano <[email protected]>

* add section highlighting sample app repo (#307)

Co-authored-by: Salvatore Giordano <[email protected]>

* add null check to debounce

* fix messagelistview loading errors

* remove core relative import

* fix: fixes message newline issue (#308)

* fix: minor

* fix: generate image thumbnails and use transparent image as placeholder for cached network images (#310)

* update cdn handling

* fix image attachment with new cdn

* fix(ui): Fix message input permission request crash (#311)

Signed-off-by: Sahil Kumar <[email protected]>

* fix(llc): update member presence and add skip_push to message (#314)

* update member presence

* add skip_push to message

* fix tests

* fix: Fixed shimmer overflow (#316)

* fix dialogs

* fix(llc): Fix attachment upload state uneven progress. (#315)

* feat(llc): Add rate limiter functions.
fix(llc): Fix attachment upload state uneven progress.

Signed-off-by: Sahil Kumar <[email protected]>

* feat(llc): Export `async.dart` and `rate_limit.dart`

Signed-off-by: Sahil Kumar <[email protected]>

* refactor(llc): Replace our `rate_limit` implementation with the dart port of js `lodash` to better handlw `cancel` and `flush` functions.

Signed-off-by: Sahil Kumar <[email protected]>

* fix(UI-Kit): Make android example run on real devices

Signed-off-by: Sahil Kumar <[email protected]>

* fix offline behaviour

Co-authored-by: Salvatore Giordano <[email protected]>

* feat: Introduce onAttachmentTap on messages attachment (#309)

* introdce onAttachmentTap on message

* correct indent

* sdfsd

Co-authored-by: Salvatore Giordano <[email protected]>

* feat: added customization options in main widgets (#312)

* polish channelpreview customization options

* polish channelheader customization options

* polish messageinput customization options

* polish threadheader customization options

* polish channellistheader customization options

* show the parent message if no messages in thread

* fix edit message

* fix channelinfo textstyle

* fix review

* fix useravatar

* add ontitle tap to thread header

* add custom message actions

* add borderradius and button builder

* add ontap to messageinput custom send button

* fix bottom sheet

* add doc

* use placeholder image

* add doc

* fix listtile density

* remove subtitle from ChannelListHeaderTheme.copyWith

* add InputDecoration.merge extension

* fix: use shimmer in images

* fix: remove notification badge logic from the sdk and move it to sample app

* fix import

* fix: Method being called during build (#317)

* method call bug fix

* dartfmt

* fix: show error messages as system and keep them in the message input (#319)

* fix: pre release (#324)

* use share_plus plugin

* bump dependency versions

* fix ui for web

* unfocus messageinput only on commands

* fix default error for messagesearchlistview

* fix thumbnail animation

* fix lint

* ignore example in linter

* update changelogs

* fix lint

* fix lint

* update stream_chat pana min

* add doc

Co-authored-by: Sahil Kumar <[email protected]>
Co-authored-by: Deven Joshi <[email protected]>
Co-authored-by: Neevash Ramdial (Nash) <[email protected]>
Co-authored-by: Nelson Nunes <[email protected]>
This commit is contained in:
Salvatore Giordano
2021-03-09 18:58:23 +01:00
committed by GitHub
co-authored by Sahil Kumar Deven Joshi Neevash Ramdial Nelson Nunes
parent 3332a48a0e
commit 4488fa6c3a
121 changed files with 3768 additions and 2376 deletions
+172 -158
View File
@@ -1,36 +1,34 @@
import 'dart:async';
import 'dart:convert';
import 'package:stream_chat/src/extensions/map_extension.dart';
import 'package:dio/dio.dart';
import 'package:logging/logging.dart';
import 'package:meta/meta.dart';
import 'package:pedantic/pedantic.dart' show unawaited;
import 'package:rxdart/rxdart.dart';
import 'package:stream_chat/src/api/channel.dart';
import 'package:stream_chat/src/api/connection_status.dart';
import 'package:stream_chat/src/api/requests.dart';
import 'package:stream_chat/src/api/responses.dart';
import 'package:stream_chat/src/api/retry_policy.dart';
import 'package:stream_chat/src/api/websocket.dart';
import 'package:stream_chat/src/attachment_file_uploader.dart';
import 'package:stream_chat/src/db/chat_persistence_client.dart';
import 'package:stream_chat/src/event_type.dart';
import 'package:stream_chat/src/exceptions.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/channel_state.dart';
import 'package:stream_chat/src/models/event.dart';
import 'package:stream_chat/src/models/message.dart';
import 'package:stream_chat/src/models/own_user.dart';
import 'package:stream_chat/src/models/user.dart';
import 'package:stream_chat/src/platform_detector/platform_detector.dart';
import 'package:stream_chat/version.dart';
import 'package:uuid/uuid.dart';
import 'attachment_file_uploader.dart';
import 'api/channel.dart';
import 'api/connection_status.dart';
import 'api/requests.dart';
import 'api/responses.dart';
import 'api/websocket.dart';
import 'db/chat_persistence_client.dart';
import 'exceptions.dart';
import 'models/channel_state.dart';
import 'models/event.dart';
import 'models/message.dart';
import 'models/user.dart';
import 'extensions/map_extension.dart';
/// Handler function used for logging records. Function requires a single [LogRecord]
/// as the only parameter.
/// Handler function used for logging records. Function requires a single
/// [LogRecord] as the only parameter.
typedef LogHandlerFunction = void Function(LogRecord record);
/// Used for decoding [Map] data to a generic type `T`.
@@ -62,7 +60,9 @@ extension on PushProvider {
/// The official Dart client for Stream Chat,
/// a service for building chat applications.
/// This library can be used on any Dart project and on both mobile and web apps with Flutter.
/// This library can be used on any Dart project and on both mobile and web apps
/// with Flutter.
///
/// You can sign up for a Stream account at https://getstream.io/chat/
///
/// The Chat client will manage API call, event handling and manage the
@@ -73,7 +73,8 @@ extension on PushProvider {
/// ```
class StreamChatClient {
/// Create a client instance with default options.
/// You should only create the client once and re-use it across your application.
/// You should only create the client once and re-use it across your
/// application.
StreamChatClient(
this.apiKey, {
this.tokenProvider,
@@ -86,12 +87,14 @@ class StreamChatClient {
RetryPolicy retryPolicy,
this.attachmentFileUploader,
}) {
_retryPolicy ??= RetryPolicy(
retryTimeout: (StreamChatClient client, int attempt, ApiError error) =>
Duration(seconds: 1 * attempt),
shouldRetry: (StreamChatClient client, int attempt, ApiError error) =>
attempt < 5,
);
_retryPolicy = retryPolicy ??
RetryPolicy(
retryTimeout:
(StreamChatClient client, int attempt, ApiError error) =>
Duration(seconds: 1 * attempt),
shouldRetry: (StreamChatClient client, int attempt, ApiError error) =>
attempt < 5,
);
attachmentFileUploader ??= StreamAttachmentFileUploader(this);
@@ -122,30 +125,39 @@ class StreamChatClient {
/// This client state
ClientState state;
/// By default the Chat client will write all messages with level Warn or Error to stdout.
/// During development you might want to enable more logging information, you can change the default log level when constructing the client.
/// By default the Chat client will write all messages with level Warn or
/// Error to stdout.
///
/// During development you might want to enable more logging information,
/// you can change the default log level when constructing the client.
///
/// ```dart
/// final client = StreamChatClient("stream-chat-api-key", logLevel: Level.INFO);
/// final client = StreamChatClient("stream-chat-api-key",
/// logLevel: Level.INFO);
/// ```
final Level logLevel;
/// Client specific logger instance.
/// Refer to the class [Logger] to learn more about the specific implementation.
/// Refer to the class [Logger] to learn more about the specific
/// implementation.
final Logger logger = Logger.detached('📡');
/// A function that has a parameter of type [LogRecord].
/// This is called on every new log record.
/// By default the client will use the handler returned by [_getDefaultLogHandler].
/// Setting it you can handle the log messages directly instead of have them written to stdout,
/// this is very convenient if you use an error tracking tool or if you want to centralize your logs into one facility.
/// By default the client will use the handler returned by
/// [_getDefaultLogHandler].
/// Setting it you can handle the log messages directly instead of have them
/// written to stdout,
/// this is very convenient if you use an error tracking tool or if you want
/// to centralize your logs into one facility.
///
/// ```dart
/// myLogHandlerFunction = (LogRecord record) {
/// // do something with the record (ie. send it to Sentry or Fabric)
/// }
///
/// final client = StreamChatClient("stream-chat-api-key", logHandlerFunction: myLogHandlerFunction);
/// final client = StreamChatClient("stream-chat-api-key",
/// logHandlerFunction: myLogHandlerFunction);
///```
LogHandlerFunction logHandlerFunction;
@@ -156,14 +168,18 @@ class StreamChatClient {
/// Your project Stream Chat base url.
final String baseURL;
/// A function in which you send a request to your own backend to get a Stream Chat API token.
/// A function in which you send a request to your own backend to get a Stream
/// Chat API token.
///
/// The token will be the return value of the function.
/// It's used by the client to refresh the token once expired or to connect the user without a predefined token using [connectUserWithProvider].
/// It's used by the client to refresh the token once expired or to connect
/// the user without a predefined token using [connectUserWithProvider].
final TokenProvider tokenProvider;
/// [Dio] httpClient
/// It's be chosen because it's easy to use and supports interesting features out of the box
/// (Interceptors, Global configuration, FormData, File downloading etc.)
/// It's be chosen because it's easy to use and supports interesting features
/// out of the box (Interceptors, Global configuration, FormData,
/// File downloading etc.)
@visibleForTesting
Dio httpClient = Dio();
@@ -234,7 +250,7 @@ class StreamChatClient {
(options.data is Map || options.data == null)) {
options.data = {
'connection_id': _connectionId,
...(options.data ?? {}),
...options.data ?? {},
};
}
@@ -278,7 +294,7 @@ class StreamChatClient {
await _disconnect();
final newToken = await tokenProvider(userId);
await Future.delayed(Duration(seconds: 4));
await Future.delayed(const Duration(seconds: 4));
token = newToken;
httpClient.unlock();
@@ -312,7 +328,10 @@ class StreamChatClient {
};
return (LogRecord record) {
print(
'(${record.time}) ${levelEmojiMapper[record.level.name] ?? record.level.name} ${record.loggerName} ${record.message}');
'(${record.time}) '
'${levelEmojiMapper[record.level.name] ?? record.level.name} '
'${record.loggerName} ${record.message}',
);
if (record.stackTrace != null) {
print(record.stackTrace);
}
@@ -321,11 +340,10 @@ class StreamChatClient {
Logger _detachedLogger(
String name,
) {
return Logger.detached(name)
..level = logLevel
..onRecord.listen(logHandlerFunction ?? _getDefaultLogHandler());
}
) =>
Logger.detached(name)
..level = logLevel
..onRecord.listen(logHandlerFunction ?? _getDefaultLogHandler());
void _setupLogger() {
logger.level = logLevel;
@@ -350,7 +368,7 @@ class StreamChatClient {
Map<String, String> get _httpHeaders => {
'Authorization': token,
'stream-auth-type': _authType,
'x-stream-client': _userAgent,
'X-Stream-Client': _userAgent,
'Content-Encoding': 'gzip',
};
@@ -386,7 +404,8 @@ class StreamChatClient {
/// Set the current user using the [tokenProvider] to fetch the token.
/// It returns a [Future] that resolves when the connection is setup.
@Deprecated(
'Use `connectUserWithProvider` instead. Will be removed in Future releases')
'Use `connectUserWithProvider` instead. Will be removed in Future releases',
)
Future<Event> setUserWithProvider(User user) => connectUserWithProvider(user);
/// Connects the current user using the [tokenProvider] to fetch the token.
@@ -470,7 +489,7 @@ class StreamChatClient {
'api_key': apiKey,
'authorization': token,
'stream-auth-type': _authType,
'x-stream-client': _userAgent,
'X-Stream-Client': _userAgent,
},
connectPayload: {
'user_id': state.user.id,
@@ -491,7 +510,8 @@ class StreamChatClient {
if (status == ConnectionStatus.connected &&
state.channels?.isNotEmpty == true) {
unawaited(queryChannelsOnline(filter: {
// ignore: unawaited_futures
queryChannelsOnline(filter: {
'cid': {
'\$in': state.channels.keys.toList(),
},
@@ -503,7 +523,7 @@ class StreamChatClient {
online: true,
));
},
));
);
} else {
_synced = false;
}
@@ -521,6 +541,7 @@ class StreamChatClient {
}).catchError((err, stacktrace) {
logger.severe('error connecting ws', err, stacktrace);
if (err is Map) {
// ignore: only_throw_errors
throw err;
}
});
@@ -558,13 +579,12 @@ class StreamChatClient {
res.events.sort((a, b) => a.createdAt.compareTo(b.createdAt));
res.events.forEach((element) {
logger.fine('element.type: ${element.type}');
logger.fine('element.message.text: ${element.message?.text}');
logger
..fine('element.type: ${element.type}')
..fine('element.message.text: ${element.message?.text}');
});
res.events.forEach((event) {
handleEvent(event);
});
res.events.forEach(handleEvent);
await chatPersistenceClient?.updateLastSyncAt(DateTime.now());
_synced = true;
@@ -573,9 +593,7 @@ class StreamChatClient {
}
}
String _asMap(sort) {
return sort?.map((s) => s.toJson().toString())?.join('');
}
String _asMap(sort) => sort?.map((s) => s.toJson().toString())?.join('');
final _queryChannelsStreams = <String, Future<List<Channel>>>{};
@@ -584,40 +602,41 @@ class StreamChatClient {
Map<String, dynamic> filter,
List<SortOption<ChannelModel>> sort,
Map<String, dynamic> options,
PaginationParams paginationParams = const PaginationParams(limit: 10),
PaginationParams paginationParams = const PaginationParams(),
int messageLimit,
bool preferOffline = false,
bool waitForConnect = true,
}) async* {
final hash = base64.encode(utf8.encode(
'$filter${_asMap(sort)}$options${paginationParams?.toJson()}$messageLimit$preferOffline',
'$filter${_asMap(sort)}$options${paginationParams?.toJson()}'
'$messageLimit',
));
if (_queryChannelsStreams.containsKey(hash)) {
yield await _queryChannelsStreams[hash];
} else {
if (preferOffline) {
final channels = await queryChannelsOffline(
filter: filter,
sort: sort,
paginationParams: paginationParams,
);
if (channels.isNotEmpty) yield channels;
}
final newQueryChannelsFuture = queryChannelsOnline(
final channels = await queryChannelsOffline(
filter: filter,
sort: sort,
options: options,
paginationParams: paginationParams,
messageLimit: messageLimit,
).whenComplete(() {
_queryChannelsStreams.remove(hash);
});
);
if (channels.isNotEmpty) yield channels;
_queryChannelsStreams[hash] = newQueryChannelsFuture;
if (wsConnectionStatus == ConnectionStatus.connected) {
final newQueryChannelsFuture = queryChannelsOnline(
filter: filter,
sort: sort,
options: options,
paginationParams: paginationParams,
messageLimit: messageLimit,
waitForConnect: waitForConnect,
).whenComplete(() {
_queryChannelsStreams.remove(hash);
});
yield await newQueryChannelsFuture;
_queryChannelsStreams[hash] = newQueryChannelsFuture;
yield await newQueryChannelsFuture;
}
}
}
@@ -627,7 +646,7 @@ class StreamChatClient {
List<SortOption<ChannelModel>> sort,
Map<String, dynamic> options,
int messageLimit,
PaginationParams paginationParams = const PaginationParams(limit: 10),
PaginationParams paginationParams = const PaginationParams(),
bool waitForConnect = true,
}) async {
if (waitForConnect) {
@@ -650,7 +669,7 @@ class StreamChatClient {
'presence': false,
};
var payload = <String, dynamic>{
final payload = <String, dynamic>{
'filter_conditions': filter,
'sort': sort,
};
@@ -682,9 +701,12 @@ class StreamChatClient {
);
if ((res.channels ?? []).isEmpty && (paginationParams?.offset ?? 0) == 0) {
logger.warning('''We could not find any channel for this query.
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
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>[];
}
@@ -715,7 +737,7 @@ class StreamChatClient {
Future<List<Channel>> queryChannelsOffline({
@required Map<String, dynamic> filter,
@required List<SortOption<ChannelModel>> sort,
PaginationParams paginationParams = const PaginationParams(limit: 10),
PaginationParams paginationParams = const PaginationParams(),
}) async {
final offlineChannels = await chatPersistenceClient?.getChannelStates(
filter: filter,
@@ -771,6 +793,7 @@ class StreamChatClient {
);
return response;
} on DioError catch (error) {
// ignore: only_throw_errors
throw _parseError(error);
}
}
@@ -791,6 +814,7 @@ class StreamChatClient {
);
return response;
} on DioError catch (error) {
// ignore: only_throw_errors
throw _parseError(error);
}
}
@@ -809,6 +833,7 @@ class StreamChatClient {
);
return response;
} on DioError catch (error) {
// ignore: only_throw_errors
throw _parseError(error);
}
}
@@ -827,6 +852,7 @@ class StreamChatClient {
);
return response;
} on DioError catch (error) {
// ignore: only_throw_errors
throw _parseError(error);
}
}
@@ -845,6 +871,7 @@ class StreamChatClient {
);
return response;
} on DioError catch (error) {
// ignore: only_throw_errors
throw _parseError(error);
}
}
@@ -864,8 +891,8 @@ class StreamChatClient {
String get _authType => _anonymous ? 'anonymous' : 'jwt';
String get _userAgent =>
'stream-chat-dart-client-${CurrentPlatform.name}-${PACKAGE_VERSION.split('+')[0]}';
String get _userAgent => 'stream-chat-dart-client-${CurrentPlatform.name}-'
'${PACKAGE_VERSION.split('+')[0]}';
Map<String, String> get _commonQueryParams => {
'user_id': state.user?.id,
@@ -873,14 +900,15 @@ class StreamChatClient {
'connection_id': _connectionId,
};
/// Set the current user with an anonymous id, this triggers a connection to the API.
/// It returns a [Future] that resolves when the connection is setup.
/// Set the current user with an anonymous id, this triggers a connection to
/// the API. It returns a [Future] that resolves when the connection is setup.
@Deprecated(
'Use `connectAnonymousUser` instead. Will be removed in Future releases')
Future<Event> setAnonymousUser() => connectAnonymousUser();
/// Connects the current user with an anonymous id, this triggers a connection to the API.
/// It returns a [Future] that resolves when the connection is setup.
/// Connects the current user with an anonymous id, this triggers a connection
/// to the API. It returns a [Future] that resolves when the connection is
/// setup.
Future<Event> connectAnonymousUser() async {
if (_connectCompleter != null && !_connectCompleter.isCompleted) {
logger.warning('Already connecting');
@@ -923,14 +951,14 @@ class StreamChatClient {
}
/// Closes the websocket connection and resets the client
/// If [flushChatPersistence] is true the client deletes all offline user's data
/// If [clearUser] is true the client unsets the current user
/// If [flushChatPersistence] is true the client deletes all offline
/// user's data. If [clearUser] is true the client unsets the current user
Future<void> disconnect({
bool flushChatPersistence = false,
bool clearUser = false,
}) async {
logger.info(
'Disconnecting flushOfflineStorage: $flushChatPersistence; clearUser: $clearUser');
logger.info('Disconnecting flushOfflineStorage: $flushChatPersistence; '
'clearUser: $clearUser');
await chatPersistenceClient?.disconnect(flush: flushChatPersistence);
chatPersistenceClient = null;
@@ -966,9 +994,7 @@ class StreamChatClient {
final payload = <String, dynamic>{
'filter_conditions': filter ?? {},
'sort': sort,
};
payload.addAll(defaultOptions);
}..addAll(defaultOptions);
if (pagination != null) {
payload.addAll(pagination.toJson());
@@ -1016,7 +1042,7 @@ class StreamChatClient {
);
}
return true;
}());
}(), 'Check incoming params.');
final payload = {
'filter_conditions': filters,
@@ -1041,15 +1067,14 @@ class StreamChatClient {
String channelType, {
ProgressCallback onSendProgress,
CancelToken cancelToken,
}) {
return attachmentFileUploader.sendFile(
file,
channelId,
channelType,
onSendProgress: onSendProgress,
cancelToken: cancelToken,
);
}
}) =>
attachmentFileUploader.sendFile(
file,
channelId,
channelType,
onSendProgress: onSendProgress,
cancelToken: cancelToken,
);
/// Send a [image] to the [channelId] of type [channelType]
Future<SendImageResponse> sendImage(
@@ -1058,15 +1083,14 @@ class StreamChatClient {
String channelType, {
ProgressCallback onSendProgress,
CancelToken cancelToken,
}) {
return attachmentFileUploader.sendImage(
image,
channelId,
channelType,
onSendProgress: onSendProgress,
cancelToken: cancelToken,
);
}
}) =>
attachmentFileUploader.sendImage(
image,
channelId,
channelType,
onSendProgress: onSendProgress,
cancelToken: cancelToken,
);
/// Delete a file from this channel
Future<EmptyResponse> deleteFile(
@@ -1074,14 +1098,13 @@ class StreamChatClient {
String channelId,
String channelType, {
CancelToken cancelToken,
}) {
return attachmentFileUploader.deleteFile(
url,
channelId,
channelType,
cancelToken: cancelToken,
);
}
}) =>
attachmentFileUploader.deleteFile(
url,
channelId,
channelType,
cancelToken: cancelToken,
);
/// Delete an image from this channel
Future<EmptyResponse> deleteImage(
@@ -1089,14 +1112,13 @@ class StreamChatClient {
String channelId,
String channelType, {
CancelToken cancelToken,
}) {
return attachmentFileUploader.deleteImage(
url,
channelId,
channelType,
cancelToken: cancelToken,
);
}
}) =>
attachmentFileUploader.deleteImage(
url,
channelId,
channelType,
cancelToken: cancelToken,
);
/// Add a device for Push Notifications.
Future<EmptyResponse> addDevice(String id, PushProvider pushProvider) async {
@@ -1146,9 +1168,8 @@ class StreamChatClient {
}
/// Update or Create the given user object.
Future<UpdateUsersResponse> updateUser(User user) async {
return updateUsers([user]);
}
Future<UpdateUsersResponse> updateUser(User user) async =>
updateUsers([user]);
/// Batch update a list of users
Future<UpdateUsersResponse> updateUsers(List<User> users) async {
@@ -1197,23 +1218,21 @@ class StreamChatClient {
Future<EmptyResponse> shadowBan(
String targetID, [
Map<String, dynamic> options = const {},
]) async {
return banUser(targetID, {
'shadow': true,
...options,
});
}
]) async =>
banUser(targetID, {
'shadow': true,
...options,
});
/// Removes shadow ban from a user
Future<EmptyResponse> removeShadowBan(
String targetID, [
Map<String, dynamic> options = const {},
]) async {
return unbanUser(targetID, {
'shadow': true,
...options,
});
}
]) async =>
unbanUser(targetID, {
'shadow': true,
...options,
});
/// Mutes a user
Future<EmptyResponse> muteUser(String targetID) async {
@@ -1312,7 +1331,7 @@ class StreamChatClient {
throw ArgumentError('Invalid timeout or Expiration date');
}
return true;
}());
}(), 'Check whether time out is valid');
DateTime pinExpires;
if (timeoutOrExpirationDate is DateTime) {
@@ -1328,15 +1347,12 @@ class StreamChatClient {
}
/// Unpins provided message
Future<UpdateMessageResponse> unpinMessage(Message message) {
return updateMessage(message.copyWith(pinned: false));
}
Future<UpdateMessageResponse> unpinMessage(Message message) =>
updateMessage(message.copyWith(pinned: false));
}
/// The class that handles the state of the channel listening to the events
class ClientState {
final _subscriptions = <StreamSubscription>[];
/// Creates a new instance listening to events and updating the state
ClientState(this._client) {
_subscriptions.addAll([
@@ -1358,16 +1374,12 @@ class ClientState {
.on()
.where((event) => event.unreadChannels != null)
.map((e) => e.unreadChannels)
.listen((unreadChannels) {
_unreadChannelsController.add(unreadChannels);
}),
.listen(_unreadChannelsController.add),
_client
.on()
.where((event) => event.totalUnreadCount != null)
.map((e) => e.totalUnreadCount)
.listen((totalUnreadCount) {
_totalUnreadCountController.add(totalUnreadCount);
}),
.listen(_totalUnreadCountController.add),
]);
_listenChannelDeleted();
@@ -1377,6 +1389,8 @@ class ClientState {
_listenUserUpdated();
}
final _subscriptions = <StreamSubscription>[];
/// Used internally for optimistic update of unread count
set totalUnreadCount(int unreadCount) {
_totalUnreadCountController?.add(unreadCount ?? 0);