Merge pull request #1598 from GetStream/release/v6.3.0

This commit is contained in:
Sahil Kumar
2023-06-08 19:30:50 +05:30
committed by GitHub
41 changed files with 499 additions and 236 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
name: Dart Code Metrics name: Dart Code Metrics
env: env:
flutter_version: "3.10.0" flutter_version: "3.10.4"
folders: "lib, test" folders: "lib, test"
on: on:
@@ -2,7 +2,7 @@ name: stream_flutter_workflow
env: env:
ACTIONS_ALLOW_UNSECURE_COMMANDS: 'true' ACTIONS_ALLOW_UNSECURE_COMMANDS: 'true'
flutter_version: "3.10.0" flutter_version: "3.10.4"
on: on:
pull_request: pull_request:
+14
View File
@@ -1,3 +1,14 @@
## 6.3.0
🐞 Fixed
- [[#1585]](https://github.com/GetStream/stream-chat-flutter/issues/1585) Fixed channels left not being removed from
the persistent storage.
🔄 Changed
- Updated `dio` dependency to `^5.2.0`.
## 6.2.0 ## 6.2.0
🐞 Fixed 🐞 Fixed
@@ -8,6 +19,9 @@
✅ Added ✅ Added
- Added support for `ChatPersistenceClient.isConnected` for checking if the client is connected to the database. - Added support for `ChatPersistenceClient.isConnected` for checking if the client is connected to the database.
- Added support for `ChatPersistenceClient.userId` for getting the current connected user id.
- Added two new methods `ChatPersistenceClient.disconnect` and `ChatPersistenceClient.connect` for disconnecting and
connecting to the database.
## 6.1.0 ## 6.1.0
@@ -1379,13 +1379,14 @@ class Channel {
this.state?.updateChannelState(updatedState); this.state?.updateChannelState(updatedState);
return updatedState; return updatedState;
} catch (e) { } catch (e) {
if (!_client.persistenceEnabled) { if (_client.persistenceEnabled) {
rethrow; return _client.chatPersistenceClient!.getChannelStateByCid(
cid!,
messagePagination: messagesPagination,
);
} }
return _client.chatPersistenceClient!.getChannelStateByCid(
cid!, rethrow;
messagePagination: messagesPagination,
);
} }
} }
@@ -1841,9 +1842,7 @@ class ChannelClientState {
/// [isUpToDate] flag count as a stream. /// [isUpToDate] flag count as a stream.
Stream<bool> get isUpToDateStream => _isUpToDateController.stream; Stream<bool> get isUpToDateStream => _isUpToDateController.stream;
final _isUpToDateController = BehaviorSubject.seeded(true);
final BehaviorSubject<bool> _isUpToDateController =
BehaviorSubject.seeded(true);
/// The retry queue associated to this channel. /// The retry queue associated to this channel.
late final RetryQueue _retryQueue; late final RetryQueue _retryQueue;
+101 -46
View File
@@ -125,10 +125,6 @@ class StreamChatClient {
final _tokenManager = TokenManager(); final _tokenManager = TokenManager();
final _connectionIdManager = ConnectionIdManager(); final _connectionIdManager = ConnectionIdManager();
set chatPersistenceClient(ChatPersistenceClient? value) {
_originalChatPersistenceClient = value;
}
/// Default user agent for all requests /// Default user agent for all requests
static String defaultUserAgent = static String defaultUserAgent =
'stream-chat-dart-client-${CurrentPlatform.name}'; 'stream-chat-dart-client-${CurrentPlatform.name}';
@@ -139,15 +135,15 @@ class StreamChatClient {
/// The current package version /// The current package version
static const packageVersion = PACKAGE_VERSION; static const packageVersion = PACKAGE_VERSION;
ChatPersistenceClient? _originalChatPersistenceClient;
/// Chat persistence client /// Chat persistence client
ChatPersistenceClient? get chatPersistenceClient => _chatPersistenceClient; ChatPersistenceClient? chatPersistenceClient;
ChatPersistenceClient? _chatPersistenceClient; /// Returns `True` if the [chatPersistenceClient] is available and connected.
/// Otherwise, returns `False`.
/// Whether the chat persistence is available or not bool get persistenceEnabled {
bool get persistenceEnabled => _chatPersistenceClient != null; final client = chatPersistenceClient;
return client != null && client.isConnected;
}
late final RetryPolicy _retryPolicy; late final RetryPolicy _retryPolicy;
@@ -324,20 +320,27 @@ class StreamChatClient {
final ownUser = OwnUser.fromUser(user); final ownUser = OwnUser.fromUser(user);
state.currentUser = ownUser; state.currentUser = ownUser;
if (!connectWebSocket) return ownUser;
try { try {
if (_originalChatPersistenceClient != null) { // Connect to persistence client if its set.
_chatPersistenceClient = _originalChatPersistenceClient; if (chatPersistenceClient != null) {
await _chatPersistenceClient!.connect(ownUser.id); await openPersistenceConnection(ownUser);
} }
final connectedUser = await openConnection(
includeUserDetailsInConnectCall: true, // Connect to websocket if [connectWebSocket] is true.
); //
return state.currentUser = connectedUser; // This is useful when you want to connect to websocket
// at a later stage or use the client in connection-less mode.
if (connectWebSocket) {
final connectedUser = await openConnection(
includeUserDetailsInConnectCall: true,
);
state.currentUser = connectedUser;
}
return state.currentUser!;
} catch (e, stk) { } catch (e, stk) {
if (e is StreamWebSocketError && e.isRetriable) { if (e is StreamWebSocketError && e.isRetriable) {
final event = await _chatPersistenceClient?.getConnectionInfo(); final event = await chatPersistenceClient?.getConnectionInfo();
if (event != null) return ownUser.merge(event.me); if (event != null) return ownUser.merge(event.me);
} }
logger.severe('error connecting user : ${ownUser.id}', e, stk); logger.severe('error connecting user : ${ownUser.id}', e, stk);
@@ -345,6 +348,40 @@ class StreamChatClient {
} }
} }
/// Connects the [chatPersistenceClient] to the given [user].
Future<void> openPersistenceConnection(User user) async {
final client = chatPersistenceClient;
if (client == null) {
throw const StreamChatError('Chat persistence client is not set');
}
if (client.isConnected) {
// If the persistence client is already connected to the userId,
// we don't need to connect again.
if (client.userId == user.id) return;
throw const StreamChatError('''
Chat persistence client is already connected to a different user,
please close the connection before connecting a new one.''');
}
// Connect the persistence client to the userId.
return client.connect(user.id);
}
/// Disconnects the [chatPersistenceClient] from the current user.
Future<void> closePersistenceConnection({bool flush = false}) async {
final client = chatPersistenceClient;
// If the persistence client is never connected, we don't need to close it.
if (client == null || !client.isConnected) {
logger.info('Chat persistence client is not connected');
return;
}
// Disconnect the persistence client.
return client.disconnect(flush: flush);
}
/// Creates a new WebSocket connection with the current user. /// Creates a new WebSocket connection with the current user.
/// If [includeUserDetailsInConnectCall] is true it will include the current /// If [includeUserDetailsInConnectCall] is true it will include the current
/// user details in the connect call. /// user details in the connect call.
@@ -422,7 +459,7 @@ class StreamChatClient {
final connectionId = event.connectionId; final connectionId = event.connectionId;
if (connectionId != null) { if (connectionId != null) {
_connectionIdManager.setConnectionId(connectionId); _connectionIdManager.setConnectionId(connectionId);
_chatPersistenceClient?.updateConnectionInfo(event); chatPersistenceClient?.updateConnectionInfo(event);
} }
} }
@@ -460,9 +497,9 @@ class StreamChatClient {
// channels are empty, assuming it's a fresh start // channels are empty, assuming it's a fresh start
// and making sure `lastSyncAt` is initialized // and making sure `lastSyncAt` is initialized
if (persistenceEnabled) { if (persistenceEnabled) {
final lastSyncAt = await _chatPersistenceClient?.getLastSyncAt(); final lastSyncAt = await chatPersistenceClient?.getLastSyncAt();
if (lastSyncAt == null) { if (lastSyncAt == null) {
await _chatPersistenceClient?.updateLastSyncAt(DateTime.now()); await chatPersistenceClient?.updateLastSyncAt(DateTime.now());
} }
} }
} }
@@ -493,13 +530,12 @@ class StreamChatClient {
/// Will automatically fetch [cids] and [lastSyncedAt] if [persistenceEnabled] /// Will automatically fetch [cids] and [lastSyncedAt] if [persistenceEnabled]
Future<void> sync({List<String>? cids, DateTime? lastSyncAt}) { Future<void> sync({List<String>? cids, DateTime? lastSyncAt}) {
return synchronized(() async { return synchronized(() async {
final channels = cids ?? await _chatPersistenceClient?.getChannelCids(); final channels = cids ?? await chatPersistenceClient?.getChannelCids();
if (channels == null || channels.isEmpty) { if (channels == null || channels.isEmpty) {
return; return;
} }
final syncAt = final syncAt = lastSyncAt ?? await chatPersistenceClient?.getLastSyncAt();
lastSyncAt ?? await _chatPersistenceClient?.getLastSyncAt();
if (syncAt == null) { if (syncAt == null) {
return; return;
} }
@@ -520,7 +556,7 @@ class StreamChatClient {
final now = DateTime.now(); final now = DateTime.now();
_lastSyncedAt = now; _lastSyncedAt = now;
_chatPersistenceClient?.updateLastSyncAt(now); chatPersistenceClient?.updateLastSyncAt(now);
} catch (e, stk) { } catch (e, stk) {
logger.severe('Error during sync', e, stk); logger.severe('Error during sync', e, stk);
} }
@@ -532,9 +568,8 @@ class StreamChatClient {
/// Requests channels with a given query. /// Requests channels with a given query.
Stream<List<Channel>> queryChannels({ Stream<List<Channel>> queryChannels({
Filter? filter, Filter? filter,
@Deprecated(''' @Deprecated('Use channelStateSort instead.')
sort has been deprecated. List<SortOption<ChannelModel>>? sort,
Please use channelStateSort instead.''') List<SortOption<ChannelModel>>? sort,
List<SortOption<ChannelState>>? channelStateSort, List<SortOption<ChannelState>>? channelStateSort,
bool state = true, bool state = true,
bool watch = true, bool watch = true,
@@ -679,7 +714,7 @@ class StreamChatClient {
final updateData = _mapChannelStateToChannel(channels); final updateData = _mapChannelStateToChannel(channels);
await _chatPersistenceClient?.updateChannelQueries( await chatPersistenceClient?.updateChannelQueries(
filter, filter,
channels.map((c) => c.channel!.cid).toList(), channels.map((c) => c.channel!.cid).toList(),
clearQueryCache: paginationParams.offset == 0, clearQueryCache: paginationParams.offset == 0,
@@ -694,11 +729,12 @@ class StreamChatClient {
Filter? filter, Filter? filter,
@Deprecated(''' @Deprecated('''
sort has been deprecated. sort has been deprecated.
Please use channelStateSort instead.''') List<SortOption<ChannelModel>>? sort, Please use channelStateSort instead.''')
List<SortOption<ChannelModel>>? sort,
List<SortOption<ChannelState>>? channelStateSort, List<SortOption<ChannelState>>? channelStateSort,
PaginationParams paginationParams = const PaginationParams(), PaginationParams paginationParams = const PaginationParams(),
}) async { }) async {
final offlineChannels = (await _chatPersistenceClient?.getChannelStates( final offlineChannels = (await chatPersistenceClient?.getChannelStates(
filter: filter, filter: filter,
// ignore: deprecated_member_use_from_same_package // ignore: deprecated_member_use_from_same_package
sort: sort, sort: sort,
@@ -1362,7 +1398,7 @@ class StreamChatClient {
final response = final response =
await _chatApi.message.deleteMessage(messageId, hard: hard); await _chatApi.message.deleteMessage(messageId, hard: hard);
if (hard == true) { if (hard == true) {
await _chatPersistenceClient?.deleteMessageById(messageId); await chatPersistenceClient?.deleteMessageById(messageId);
} }
return response; return response;
} }
@@ -1468,34 +1504,33 @@ class StreamChatClient {
Future<void> disconnectUser({bool flushChatPersistence = false}) async { Future<void> disconnectUser({bool flushChatPersistence = false}) async {
logger.info('Disconnecting user : ${state.currentUser?.id}'); logger.info('Disconnecting user : ${state.currentUser?.id}');
// resetting state // resetting state.
state.dispose(); state.dispose();
state = ClientState(this); state = ClientState(this);
_lastSyncedAt = null; _lastSyncedAt = null;
// resetting credentials // resetting credentials.
_tokenManager.reset(); _tokenManager.reset();
_connectionIdManager.reset(); _connectionIdManager.reset();
// disconnecting persistence client // closing persistence connection.
await _chatPersistenceClient?.disconnect(flush: flushChatPersistence); await closePersistenceConnection(flush: flushChatPersistence);
_chatPersistenceClient = null;
// closing web-socket connection // closing web-socket connection
closeConnection(); return closeConnection();
} }
/// Call this function to dispose the client /// Call this function to dispose the client
Future<void> dispose() async { Future<void> dispose() async {
logger.info('Disposing new StreamChatClient'); logger.info('Disposing new StreamChatClient');
// disposing state // disposing state.
state.dispose(); state.dispose();
// disconnecting persistence client // closing persistence connection.
await _chatPersistenceClient?.disconnect(); await closePersistenceConnection();
// closing web-socket connection // closing web-socket connection.
closeConnection(); closeConnection();
await _eventController.close(); await _eventController.close();
@@ -1541,6 +1576,8 @@ class ClientState {
currentUser = currentUser?.copyWith(totalUnreadCount: count); currentUser = currentUser?.copyWith(totalUnreadCount: count);
})); }));
_listenChannelLeft();
_listenChannelDeleted(); _listenChannelDeleted();
_listenChannelHidden(); _listenChannelHidden();
@@ -1601,12 +1638,30 @@ class ClientState {
); );
} }
void _listenChannelLeft() {
_eventsSubscription?.add(
_client
.on(
EventType.memberRemoved,
EventType.notificationRemovedFromChannel,
)
.listen((event) async {
final isCurrentUser = event.user!.id == currentUser!.id;
if (isCurrentUser) {
final eventChannel = event.channel!;
await _client.chatPersistenceClient
?.deleteChannels([eventChannel.cid]);
channels.remove(eventChannel.cid)?.dispose();
}
}),
);
}
void _listenChannelDeleted() { void _listenChannelDeleted() {
_eventsSubscription?.add( _eventsSubscription?.add(
_client _client
.on( .on(
EventType.channelDeleted, EventType.channelDeleted,
EventType.notificationRemovedFromChannel,
EventType.notificationChannelDeleted, EventType.notificationChannelDeleted,
) )
.listen((Event event) async { .listen((Event event) async {
@@ -89,8 +89,13 @@ class StreamChatNetworkError extends StreamChatError {
}) : super(message); }) : super(message);
/// ///
factory StreamChatNetworkError.fromDioError(DioError error) { @Deprecated('Use `StreamChatNetworkError.fromDioException` instead')
final response = error.response; factory StreamChatNetworkError.fromDioError(DioException error) =
StreamChatNetworkError.fromDioException;
///
factory StreamChatNetworkError.fromDioException(DioException exception) {
final response = exception.response;
ErrorResponse? errorResponse; ErrorResponse? errorResponse;
final data = response?.data; final data = response?.data;
if (data != null) { if (data != null) {
@@ -100,12 +105,12 @@ class StreamChatNetworkError extends StreamChatError {
code: errorResponse?.code ?? -1, code: errorResponse?.code ?? -1,
message: errorResponse?.message ?? message: errorResponse?.message ??
response?.statusMessage ?? response?.statusMessage ??
error.message ?? exception.message ??
'', '',
statusCode: errorResponse?.statusCode ?? response?.statusCode, statusCode: errorResponse?.statusCode ?? response?.statusCode,
data: errorResponse, data: errorResponse,
isRequestCancelledError: error.type == DioErrorType.cancel, isRequestCancelledError: exception.type == DioExceptionType.cancel,
)..stackTrace = error.stackTrace; )..stackTrace = exception.stackTrace;
} }
/// Error code /// Error code
@@ -46,26 +46,26 @@ class AuthInterceptor extends QueuedInterceptor {
@override @override
void onError( void onError(
DioError err, DioException exception,
ErrorInterceptorHandler handler, ErrorInterceptorHandler handler,
) async { ) async {
final data = err.response?.data; final data = exception.response?.data;
if (data == null || data is! Map<String, dynamic>) { if (data == null || data is! Map<String, dynamic>) {
return handler.next(err); return handler.next(exception);
} }
final error = ErrorResponse.fromJson(data); final error = ErrorResponse.fromJson(data);
if (error.code == ChatErrorCode.tokenExpired.code) { if (error.code == ChatErrorCode.tokenExpired.code) {
if (_tokenManager.isStatic) return handler.next(err); if (_tokenManager.isStatic) return handler.next(exception);
await _tokenManager.loadToken(refresh: true); await _tokenManager.loadToken(refresh: true);
try { try {
final options = err.requestOptions; final options = exception.requestOptions;
final response = await _client.fetch(options); final response = await _client.fetch(options);
return handler.resolve(response); return handler.resolve(response);
} on DioError catch (error) { } on DioException catch (exception) {
return handler.next(error); return handler.next(exception);
} }
} }
return handler.next(err); return handler.next(exception);
} }
} }
@@ -119,32 +119,32 @@ class LoggingInterceptor extends Interceptor {
} }
@override @override
void onError(DioError err, ErrorInterceptorHandler handler) { void onError(DioException exception, ErrorInterceptorHandler handler) {
if (error) { if (error) {
if (err.type == DioErrorType.badResponse) { if (exception.type == DioExceptionType.badResponse) {
final uri = err.response?.requestOptions.uri; final uri = exception.response?.requestOptions.uri;
_printBoxed( _printBoxed(
_logPrintError, _logPrintError,
header: header:
'DioError ║ Status: ${err.response?.statusCode} ${err.response?.statusMessage}', 'DioException ║ Status: ${exception.response?.statusCode} ${exception.response?.statusMessage}',
text: uri.toString(), text: uri.toString(),
); );
if (err.response != null && err.response?.data != null) { if (exception.response != null && exception.response?.data != null) {
_logPrintError('${err.type.toString()}'); _logPrintError('${exception.type.toString()}');
_printResponse(_logPrintError, err.response!); _printResponse(_logPrintError, exception.response!);
} }
_printLine(_logPrintError, ''); _printLine(_logPrintError, '');
_logPrintError(''); _logPrintError('');
} else { } else {
_printBoxed( _printBoxed(
_logPrintError, _logPrintError,
header: 'DioError${err.type}', header: 'DioException${exception.type}',
text: err.message, text: exception.message,
); );
_printRequestHeader(_logPrintError, err.requestOptions); _printRequestHeader(_logPrintError, exception.requestOptions);
} }
} }
super.onError(err, handler); super.onError(exception, handler);
} }
@override @override
@@ -2,7 +2,7 @@ import 'package:dio/dio.dart';
import 'package:stream_chat/src/core/error/error.dart'; import 'package:stream_chat/src/core/error/error.dart';
/// Error class specific to StreamChat and Dio /// Error class specific to StreamChat and Dio
class StreamChatDioError extends DioError { class StreamChatDioError extends DioException {
/// Initialize a stream chat dio error /// Initialize a stream chat dio error
StreamChatDioError({ StreamChatDioError({
required this.error, required this.error,
@@ -92,16 +92,16 @@ class StreamHttpClient {
/// calling [close] will throw an exception. /// calling [close] will throw an exception.
void close({bool force = false}) => httpClient.close(force: force); void close({bool force = false}) => httpClient.close(force: force);
StreamChatNetworkError _parseError(DioError err) { StreamChatNetworkError _parseError(DioException exception) {
StreamChatNetworkError error; StreamChatNetworkError error;
// locally thrown dio error // locally thrown dio error
if (err is StreamChatDioError) { if (exception is StreamChatDioError) {
error = err.error; error = exception.error;
} else { } else {
// real network request dio error // real network request dio error
error = StreamChatNetworkError.fromDioError(err); error = StreamChatNetworkError.fromDioException(exception);
} }
return error..stackTrace = err.stackTrace; return error..stackTrace = exception.stackTrace;
} }
/// Handy method to make http GET request with error parsing. /// Handy method to make http GET request with error parsing.
@@ -121,7 +121,7 @@ class StreamHttpClient {
cancelToken: cancelToken, cancelToken: cancelToken,
); );
return response; return response;
} on DioError catch (error) { } on DioException catch (error) {
throw _parseError(error); throw _parseError(error);
} }
} }
@@ -147,7 +147,7 @@ class StreamHttpClient {
cancelToken: cancelToken, cancelToken: cancelToken,
); );
return response; return response;
} on DioError catch (error) { } on DioException catch (error) {
throw _parseError(error); throw _parseError(error);
} }
} }
@@ -167,7 +167,7 @@ class StreamHttpClient {
cancelToken: cancelToken, cancelToken: cancelToken,
); );
return response; return response;
} on DioError catch (error) { } on DioException catch (error) {
throw _parseError(error); throw _parseError(error);
} }
} }
@@ -193,7 +193,7 @@ class StreamHttpClient {
cancelToken: cancelToken, cancelToken: cancelToken,
); );
return response; return response;
} on DioError catch (error) { } on DioException catch (error) {
throw _parseError(error); throw _parseError(error);
} }
} }
@@ -219,7 +219,7 @@ class StreamHttpClient {
cancelToken: cancelToken, cancelToken: cancelToken,
); );
return response; return response;
} on DioError catch (error) { } on DioException catch (error) {
throw _parseError(error); throw _parseError(error);
} }
} }
@@ -268,7 +268,7 @@ class StreamHttpClient {
cancelToken: cancelToken, cancelToken: cancelToken,
); );
return response; return response;
} on DioError catch (error) { } on DioException catch (error) {
throw _parseError(error); throw _parseError(error);
} }
} }
@@ -281,7 +281,7 @@ class StreamHttpClient {
try { try {
final response = await httpClient.fetch<T>(requestOptions); final response = await httpClient.fetch<T>(requestOptions);
return response; return response;
} on DioError catch (error) { } on DioException catch (error) {
throw _parseError(error); throw _parseError(error);
} }
} }
@@ -17,6 +17,11 @@ abstract class ChatPersistenceClient {
/// Whether the connection is established. /// Whether the connection is established.
bool get isConnected; bool get isConnected;
/// The current user id to which the client is connected.
///
/// Returns `null` if the client is not connected.
String? get userId;
/// Creates a new connection to the client /// Creates a new connection to the client
Future<void> connect(String userId); Future<void> connect(String userId);
@@ -97,9 +102,8 @@ abstract class ChatPersistenceClient {
/// for filtering out states. /// for filtering out states.
Future<List<ChannelState>> getChannelStates({ Future<List<ChannelState>> getChannelStates({
Filter? filter, Filter? filter,
@Deprecated(''' @Deprecated('Use channelStateSort instead.')
sort has been deprecated. List<SortOption<ChannelModel>>? sort,
Please use channelStateSort instead.''') List<SortOption<ChannelModel>>? sort,
List<SortOption<ChannelState>>? channelStateSort, List<SortOption<ChannelState>>? channelStateSort,
PaginationParams? paginationParams, PaginationParams? paginationParams,
}); });
+2 -2
View File
@@ -63,7 +63,7 @@ class EventType {
static const String notificationAddedToChannel = static const String notificationAddedToChannel =
'notification.added_to_channel'; 'notification.added_to_channel';
/// Event sent when the user is removed to a channel /// Event sent when the user is removed from a channel
static const String notificationRemovedFromChannel = static const String notificationRemovedFromChannel =
'notification.removed_from_channel'; 'notification.removed_from_channel';
@@ -76,7 +76,7 @@ class EventType {
/// Event sent when a member is added to a channel /// Event sent when a member is added to a channel
static const String memberAdded = 'member.added'; static const String memberAdded = 'member.added';
/// Event sent when a member is removed to a channel /// Event sent when a member is removed from a channel
static const String memberRemoved = 'member.removed'; static const String memberRemoved = 'member.removed';
/// Event sent when a member is updated in a channel /// Event sent when a member is updated in a channel
+11 -7
View File
@@ -1,12 +1,17 @@
library stream_chat; library stream_chat;
export 'package:async/async.dart'; export 'package:async/async.dart';
export 'package:dio/src/cancel_token.dart'; export 'package:dio/dio.dart'
export 'package:dio/src/dio_error.dart'; show
export 'package:dio/src/dio_mixin.dart' show Interceptor, InterceptorsWrapper; DioException,
export 'package:dio/src/multipart_file.dart'; DioExceptionType,
export 'package:dio/src/options.dart'; RequestOptions,
export 'package:dio/src/options.dart' show ProgressCallback; CancelToken,
Interceptor,
InterceptorsWrapper,
MultipartFile,
Options,
ProgressCallback;
export 'package:logging/logging.dart' show Logger, Level, LogRecord; export 'package:logging/logging.dart' show Logger, Level, LogRecord;
export 'package:rate_limiter/rate_limiter.dart'; export 'package:rate_limiter/rate_limiter.dart';
export 'package:uuid/uuid.dart'; export 'package:uuid/uuid.dart';
@@ -17,7 +22,6 @@ export 'src/client/key_stroke_handler.dart';
export 'src/core/api/attachment_file_uploader.dart'; export 'src/core/api/attachment_file_uploader.dart';
export 'src/core/api/requests.dart'; export 'src/core/api/requests.dart';
export 'src/core/api/responses.dart'; export 'src/core/api/responses.dart';
export 'src/core/api/stream_chat_api.dart' show PushProvider;
export 'src/core/api/stream_chat_api.dart'; export 'src/core/api/stream_chat_api.dart';
export 'src/core/error/error.dart'; export 'src/core/error/error.dart';
export 'src/core/http/interceptor/logging_interceptor.dart'; export 'src/core/http/interceptor/logging_interceptor.dart';
+1 -1
View File
@@ -3,4 +3,4 @@ import 'package:stream_chat/src/client/client.dart';
/// Current package version /// Current package version
/// Used in [StreamChatClient] to build the `x-stream-client` header /// Used in [StreamChatClient] to build the `x-stream-client` header
// ignore: constant_identifier_names // ignore: constant_identifier_names
const PACKAGE_VERSION = '6.2.0'; const PACKAGE_VERSION = '6.3.0';
+2 -2
View File
@@ -1,7 +1,7 @@
name: stream_chat name: stream_chat
homepage: https://getstream.io/ homepage: https://getstream.io/
description: The official Dart client for Stream Chat, a service for building chat applications. description: The official Dart client for Stream Chat, a service for building chat applications.
version: 6.2.0 version: 6.3.0
repository: https://github.com/GetStream/stream-chat-flutter repository: https://github.com/GetStream/stream-chat-flutter
issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues
@@ -11,7 +11,7 @@ environment:
dependencies: dependencies:
async: ^2.10.0 async: ^2.10.0
collection: ^1.17.0 collection: ^1.17.0
dio: ^5.1.1 dio: ^5.2.0
equatable: ^2.0.5 equatable: ^2.0.5
freezed_annotation: ^2.2.0 freezed_annotation: ^2.2.0
http_parser: ^4.0.2 http_parser: ^4.0.2
@@ -2544,4 +2544,124 @@ void main() {
}, },
); );
}); });
group('PersistenceConnectionTests', () {
const apiKey = 'test-api-key';
late final api = FakeChatApi();
late final ws = FakeWebSocket();
final user = User(id: 'test-user-id');
final token = Token.development(user.id).rawValue;
late StreamChatClient client;
setUp(() async {
client = StreamChatClient(apiKey, chatApi: api, ws: ws);
expect(client.persistenceEnabled, isFalse);
});
tearDown(() {
client.chatPersistenceClient = null;
expect(client.persistenceEnabled, isFalse);
client.dispose();
});
test('openPersistenceConnection connects the client to the user', () async {
client.chatPersistenceClient = MockPersistenceClient();
await client.openPersistenceConnection(user);
expect(client.persistenceEnabled, isTrue);
});
test(
'''multiple call to openPersistenceConnection does not throws an error if already connected to the same user''',
() async {
client.chatPersistenceClient = MockPersistenceClient();
await client.openPersistenceConnection(user);
expect(client.persistenceEnabled, isTrue);
await expectLater(client.openPersistenceConnection(user), completes);
await expectLater(client.openPersistenceConnection(user), completes);
await expectLater(client.openPersistenceConnection(user), completes);
},
);
test(
'''openPersistenceConnection throws an error if client is already connected to a different user''',
() async {
client.chatPersistenceClient = MockPersistenceClient();
await client.openPersistenceConnection(user);
expect(client.persistenceEnabled, isTrue);
await expectLater(
client.openPersistenceConnection(user.copyWith(id: 'new-id')),
throwsA(const TypeMatcher<StreamChatError>()),
);
},
);
test(
'''openPersistenceConnection throws an error if chatPersistenceClient is not set''',
() async {
await expectLater(
client.openPersistenceConnection(user),
throwsA(const TypeMatcher<StreamChatError>()),
);
},
);
test('closePersistenceConnection disconnects the client', () async {
client.chatPersistenceClient = MockPersistenceClient();
await client.openPersistenceConnection(user);
expect(client.persistenceEnabled, isTrue);
await client.closePersistenceConnection();
expect(client.persistenceEnabled, isFalse);
});
test(
'''closePersistenceConnection compeletes normally if chatPersistenceClient is not connected''',
() async {
client.chatPersistenceClient = MockPersistenceClient();
expect(client.chatPersistenceClient!.isConnected, isFalse);
await expectLater(client.closePersistenceConnection(), completes);
},
);
test(
'''closePersistenceConnection completes normally if chatPersistenceClient is not set''',
() async {
expect(client.persistenceEnabled, isFalse);
await expectLater(client.closePersistenceConnection(), completes);
},
);
test(
'''connectUser completes normally if the persistence connection is already connected to the same user''',
() async {
client.chatPersistenceClient = MockPersistenceClient();
await client.openPersistenceConnection(user);
expect(client.persistenceEnabled, isTrue);
await expectLater(
client.connectUser(user, token, connectWebSocket: false),
completes,
);
},
);
test(
'''connectUser should throw if the persistence connection if already connected to a different user''',
() async {
client.chatPersistenceClient = MockPersistenceClient();
await client.openPersistenceConnection(user.copyWith(id: 'new-id'));
expect(client.persistenceEnabled, isTrue);
await expectLater(
client.connectUser(user, token, connectWebSocket: false),
throwsA(const TypeMatcher<StreamChatError>()),
);
},
);
});
} }
@@ -60,7 +60,7 @@ void main() {
expect(error.message, message); expect(error.message, message);
}); });
test('.fromDioError', () { test('.fromDioException', () {
const code = 333; const code = 333;
const statusCode = 666; const statusCode = 666;
const message = 'test-error-message'; const message = 'test-error-message';
@@ -69,7 +69,7 @@ void main() {
..code = code ..code = code
..statusCode = statusCode ..statusCode = statusCode
..message = message; ..message = message;
final dioError = DioError( final dioError = DioException(
requestOptions: options, requestOptions: options,
response: Response( response: Response(
requestOptions: options, requestOptions: options,
@@ -77,7 +77,7 @@ void main() {
data: data.toJson(), data: data.toJson(),
), ),
); );
final error = StreamChatNetworkError.fromDioError(dioError); final error = StreamChatNetworkError.fromDioException(dioError);
expect(error, isNotNull); expect(error, isNotNull);
expect(error.code, code); expect(error.code, code);
expect(error.message, message); expect(error.message, message);
@@ -88,7 +88,7 @@ void main() {
requestOptions: options, requestOptions: options,
data: errorResponse.toJson(), data: errorResponse.toJson(),
); );
final err = DioError(requestOptions: options, response: response); final err = DioException(requestOptions: options, response: response);
final handler = ErrorInterceptorHandler(); final handler = ErrorInterceptorHandler();
when(() => tokenManager.isStatic).thenReturn(false); when(() => tokenManager.isStatic).thenReturn(false);
@@ -135,7 +135,7 @@ void main() {
requestOptions: options, requestOptions: options,
data: errorResponse.toJson(), data: errorResponse.toJson(),
); );
final err = DioError(requestOptions: options, response: response); final err = DioException(requestOptions: options, response: response);
final handler = ErrorInterceptorHandler(); final handler = ErrorInterceptorHandler();
when(() => tokenManager.isStatic).thenReturn(false); when(() => tokenManager.isStatic).thenReturn(false);
@@ -153,7 +153,7 @@ void main() {
} catch (e) { } catch (e) {
// need to cast it as the type is private in dio // need to cast it as the type is private in dio
final error = (e as dynamic).data; final error = (e as dynamic).data;
expect(error, isA<DioError>()); expect(error, isA<DioException>());
} }
verify(() => tokenManager.isStatic).called(1); verify(() => tokenManager.isStatic).called(1);
@@ -179,7 +179,7 @@ void main() {
requestOptions: options, requestOptions: options,
data: errorResponse.toJson(), data: errorResponse.toJson(),
); );
final err = DioError(requestOptions: options, response: response); final err = DioException(requestOptions: options, response: response);
final handler = ErrorInterceptorHandler(); final handler = ErrorInterceptorHandler();
when(() => tokenManager.isStatic).thenReturn(true); when(() => tokenManager.isStatic).thenReturn(true);
@@ -191,8 +191,8 @@ void main() {
} catch (e) { } catch (e) {
// need to cast it as the type is private in dio // need to cast it as the type is private in dio
final error = (e as dynamic).data; final error = (e as dynamic).data;
expect(error, isA<DioError>()); expect(error, isA<DioException>());
final response = StreamChatNetworkError.fromDioError(error); final response = StreamChatNetworkError.fromDioException(error);
expect(response.errorCode, code); expect(response.errorCode, code);
} }
@@ -207,7 +207,7 @@ void main() {
const path = 'test-request-path'; const path = 'test-request-path';
final options = RequestOptions(path: path); final options = RequestOptions(path: path);
final response = Response(requestOptions: options); final response = Response(requestOptions: options);
final err = DioError(requestOptions: options, response: response); final err = DioException(requestOptions: options, response: response);
final handler = ErrorInterceptorHandler(); final handler = ErrorInterceptorHandler();
authInterceptor.onError(err, handler); authInterceptor.onError(err, handler);
@@ -217,7 +217,7 @@ void main() {
} catch (e) { } catch (e) {
// need to cast it as the type is private in dio // need to cast it as the type is private in dio
final error = (e as dynamic).data; final error = (e as dynamic).data;
expect(error, isA<DioError>()); expect(error, isA<DioException>());
} }
}, },
); );
@@ -12,7 +12,7 @@ void main() {
requestOptions: options, requestOptions: options,
); );
expect(dioError, isA<DioError>()); expect(dioError, isA<DioException>());
expect(dioError, isNotNull); expect(dioError, isNotNull);
expect(dioError.error, error); expect(dioError.error, error);
expect(dioError.requestOptions, options); expect(dioError.requestOptions, options);
@@ -21,7 +21,7 @@ void main() {
statusCode: 200, statusCode: 200,
); );
DioError throwableError( DioException throwableError(
String path, { String path, {
StreamChatNetworkError? error, StreamChatNetworkError? error,
bool streamChatDioError = false, bool streamChatDioError = false,
@@ -32,11 +32,11 @@ void main() {
..code = error?.code ..code = error?.code
..statusCode = error?.statusCode ..statusCode = error?.statusCode
..message = error?.message; ..message = error?.message;
DioError? dioError; DioException? dioError;
if (streamChatDioError) { if (streamChatDioError) {
dioError = StreamChatDioError(error: error!, requestOptions: options); dioError = StreamChatDioError(error: error!, requestOptions: options);
} else { } else {
dioError = DioError( dioError = DioException(
error: error, error: error,
requestOptions: options, requestOptions: options,
response: Response( response: Response(
@@ -210,7 +210,7 @@ void main() {
await client.get(path); await client.get(path);
} catch (e) { } catch (e) {
expect(e, isA<StreamChatNetworkError>()); expect(e, isA<StreamChatNetworkError>());
expect(e, StreamChatNetworkError.fromDioError(error)); expect(e, StreamChatNetworkError.fromDioException(error));
} }
verify(() => dio.get( verify(() => dio.get(
@@ -263,7 +263,7 @@ void main() {
await client.post(path); await client.post(path);
} catch (e) { } catch (e) {
expect(e, isA<StreamChatNetworkError>()); expect(e, isA<StreamChatNetworkError>());
expect(e, StreamChatNetworkError.fromDioError(error)); expect(e, StreamChatNetworkError.fromDioException(error));
} }
verify(() => dio.post( verify(() => dio.post(
@@ -317,7 +317,7 @@ void main() {
await client.delete(path); await client.delete(path);
} catch (e) { } catch (e) {
expect(e, isA<StreamChatNetworkError>()); expect(e, isA<StreamChatNetworkError>());
expect(e, StreamChatNetworkError.fromDioError(error)); expect(e, StreamChatNetworkError.fromDioException(error));
} }
verify(() => dio.delete( verify(() => dio.delete(
@@ -371,7 +371,7 @@ void main() {
await client.patch(path); await client.patch(path);
} catch (e) { } catch (e) {
expect(e, isA<StreamChatNetworkError>()); expect(e, isA<StreamChatNetworkError>());
expect(e, StreamChatNetworkError.fromDioError(error)); expect(e, StreamChatNetworkError.fromDioException(error));
} }
verify(() => dio.patch( verify(() => dio.patch(
@@ -425,7 +425,7 @@ void main() {
await client.put(path); await client.put(path);
} catch (e) { } catch (e) {
expect(e, isA<StreamChatNetworkError>()); expect(e, isA<StreamChatNetworkError>());
expect(e, StreamChatNetworkError.fromDioError(error)); expect(e, StreamChatNetworkError.fromDioException(error));
} }
verify(() => dio.put( verify(() => dio.put(
@@ -486,7 +486,7 @@ void main() {
await client.postFile(path, file); await client.postFile(path, file);
} catch (e) { } catch (e) {
expect(e, isA<StreamChatNetworkError>()); expect(e, isA<StreamChatNetworkError>());
expect(e, StreamChatNetworkError.fromDioError(error)); expect(e, StreamChatNetworkError.fromDioException(error));
} }
verify(() => dio.post( verify(() => dio.post(
@@ -15,6 +15,9 @@ class TestPersistenceClient extends ChatPersistenceClient {
@override @override
bool get isConnected => throw UnimplementedError(); bool get isConnected => throw UnimplementedError();
@override
String? get userId => throw UnimplementedError();
@override @override
Future<void> connect(String userId) => throw UnimplementedError(); Future<void> connect(String userId) => throw UnimplementedError();
@@ -59,9 +62,8 @@ class TestPersistenceClient extends ChatPersistenceClient {
@override @override
Future<List<ChannelState>> getChannelStates( Future<List<ChannelState>> getChannelStates(
{Filter? filter, {Filter? filter,
@Deprecated(''' @Deprecated('Use channelStateSort instead.')
sort has been deprecated. List<SortOption<ChannelModel>>? sort,
Please use channelStateSort instead.''') List<SortOption<ChannelModel>>? sort,
List<SortOption<ChannelState>>? channelStateSort, List<SortOption<ChannelState>>? channelStateSort,
PaginationParams? paginationParams}) => PaginationParams? paginationParams}) =>
throw UnimplementedError(); throw UnimplementedError();
+18 -3
View File
@@ -64,11 +64,26 @@ class MockAttachmentFileUploader extends Mock
implements AttachmentFileUploader {} implements AttachmentFileUploader {}
class MockPersistenceClient extends Mock implements ChatPersistenceClient { class MockPersistenceClient extends Mock implements ChatPersistenceClient {
@override String? _userId;
Future<void> connect(String userId) => Future.value(); bool _isConnected = false;
@override @override
Future<void> disconnect({bool flush = false}) => Future.value(); bool get isConnected => _isConnected;
@override
String? get userId => _userId;
@override
Future<void> connect(String userId) async {
_userId = userId;
_isConnected = true;
}
@override
Future<void> disconnect({bool flush = false}) async {
_userId = null;
_isConnected = false;
}
} }
class MockStreamChatClient extends Mock implements StreamChatClient { class MockStreamChatClient extends Mock implements StreamChatClient {
+14
View File
@@ -1,3 +1,17 @@
## 6.3.0
🐞 Fixed
- [[#1592]](https://github.com/GetStream/stream-chat-flutter/issues/1592) Fixed broken attachment download on web.
- [[#1591]](https://github.com/GetStream/stream-chat-flutter/issues/1591) Fixed `StreamChannelInfoBottomSheet` not
rendering member list properly.
- [[#1427]](https://github.com/GetStream/stream-chat-flutter/issues/1427) Fixed unable to load asset error for
`packages/stream_chat_flutter/lib/svgs/video_call_icon.svg`.
🔄 Changed
- Updated `dio` dependency to `^5.2.0`.
## 6.2.0 ## 6.2.0
🐞 Fixed 🐞 Fixed
@@ -4,9 +4,41 @@ import 'package:dio/dio.dart';
import 'package:file_selector/file_selector.dart'; import 'package:file_selector/file_selector.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
/// Downloads the [attachment] to the device and returns /// Represents the url and bytes of an attachment.
/// the path to the file. class AttachmentData {
Future<String?> downloadWebOrDesktopAttachment( /// Creates a new [AttachmentData] instance.
const AttachmentData({
required this.bytes,
required this.downloadUrl,
required this.fileName,
this.mimeType,
});
/// The data downloaded from the [downloadUrl].
final Uint8List bytes;
/// The url of the attachment that was used to download the [bytes].
final String downloadUrl;
/// The name of the file to use when saving the [bytes].
final String fileName;
/// The mime type of the attachment.
final String? mimeType;
/// Creates an [XFile] from the [AttachmentData].
XFile toXFile({String? path}) {
return XFile.fromData(
bytes,
mimeType: mimeType,
name: fileName,
path: path,
);
}
}
/// Downloads the [attachment] and returns the [AttachmentData].
Future<AttachmentData> downloadAttachmentData(
Attachment attachment, { Attachment attachment, {
ProgressCallback? onReceiveProgress, ProgressCallback? onReceiveProgress,
Map<String, dynamic>? queryParameters, Map<String, dynamic>? queryParameters,
@@ -34,13 +66,14 @@ Future<String?> downloadWebOrDesktopAttachment(
fileName = attachment.title; fileName = attachment.title;
} }
assert( if (downloadUrl == null) {
downloadUrl != null, throw ArgumentError(
'Attachment must have an assetUrl or imageUrl or thumbUrl', 'Attachment must have an assetUrl or imageUrl or thumbUrl',
); );
}
final response = await Dio().get<List<int>>( final response = await Dio().get<List<int>>(
downloadUrl!, downloadUrl,
onReceiveProgress: onReceiveProgress, onReceiveProgress: onReceiveProgress,
queryParameters: queryParameters, queryParameters: queryParameters,
cancelToken: cancelToken, cancelToken: cancelToken,
@@ -49,23 +82,12 @@ Future<String?> downloadWebOrDesktopAttachment(
Options(responseType: ResponseType.bytes), Options(responseType: ResponseType.bytes),
); );
// Open the native file browser so the user can select the download path. final bytes = Uint8List.fromList(response.data!);
final path = await getSavePath(suggestedName: fileName);
if (path == null) { return AttachmentData(
// Operation was canceled by the user. bytes: bytes,
return null; downloadUrl: downloadUrl,
} fileName: fileName!,
// Create an XFile for proper file saving
final file = XFile.fromData(
Uint8List.fromList(response.data!),
mimeType: attachment.mimeType, mimeType: attachment.mimeType,
name: fileName,
path: path,
); );
// Save the file to the user's selected path.
await file.saveTo(path);
return path;
} }
@@ -50,13 +50,21 @@ class StreamAttachmentHandler extends StreamAttachmentHandlerBase {
Map<String, dynamic>? queryParameters, Map<String, dynamic>? queryParameters,
CancelToken? cancelToken, CancelToken? cancelToken,
Options? options, Options? options,
}) { }) async {
return downloadWebOrDesktopAttachment( final data = await downloadAttachmentData(
attachment, attachment,
onReceiveProgress: onReceiveProgress, onReceiveProgress: onReceiveProgress,
queryParameters: queryParameters, queryParameters: queryParameters,
cancelToken: cancelToken, cancelToken: cancelToken,
options: options, options: options,
); );
// Create an XFile for proper file saving.
final file = data.toXFile();
// Save the file. We are not using the path parameter because it is not
// supported on web.
await file.saveTo('');
return null;
} }
} }
@@ -1,8 +1,7 @@
import 'dart:io'; import 'dart:io';
import 'dart:typed_data';
import 'package:dio/dio.dart';
import 'package:file_picker/file_picker.dart'; import 'package:file_picker/file_picker.dart';
import 'package:file_selector/file_selector.dart';
import 'package:image_picker/image_picker.dart'; import 'package:image_picker/image_picker.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:stream_chat_flutter/src/attachment/handler/common.dart'; import 'package:stream_chat_flutter/src/attachment/handler/common.dart';
@@ -21,14 +20,29 @@ class StreamAttachmentHandlerDesktop extends StreamAttachmentHandler {
Map<String, dynamic>? queryParameters, Map<String, dynamic>? queryParameters,
CancelToken? cancelToken, CancelToken? cancelToken,
Options? options, Options? options,
}) { }) async {
return downloadWebOrDesktopAttachment( final data = await downloadAttachmentData(
attachment, attachment,
onReceiveProgress: onReceiveProgress, onReceiveProgress: onReceiveProgress,
queryParameters: queryParameters, queryParameters: queryParameters,
cancelToken: cancelToken, cancelToken: cancelToken,
options: options, options: options,
); );
// Open the native file browser so the user can select the download path.
final path = await getSavePath(suggestedName: data.fileName);
if (path == null) {
// Operation was canceled by the user.
return null;
}
// Create an XFile for proper file saving.
final file = data.toXFile(path: path);
// Save the file to the user's selected path.
await file.saveTo(path);
return path;
} }
} }
@@ -160,53 +174,20 @@ class StreamAttachmentHandler extends StreamAttachmentHandlerBase {
CancelToken? cancelToken, CancelToken? cancelToken,
Options? options, Options? options,
}) async { }) async {
final type = attachment.type; final data = await downloadAttachmentData(
attachment,
String? downloadUrl;
String? fileName;
/* ---IMAGES/GIFS--- */
if (type == 'image') {
downloadUrl = attachment.imageUrl ?? attachment.assetUrl;
fileName = attachment.title;
fileName ??= 'attachment.${attachment.mimeType ?? 'png'}';
}
/* ---GIPHY's--- */
else if (type == 'giphy') {
downloadUrl = attachment.thumbUrl;
fileName = '${attachment.title}.gif';
}
/* ---FILES AND VIDEOS--- */
else if (type == 'file' || type == 'video') {
downloadUrl = attachment.assetUrl;
fileName = attachment.title;
}
assert(
downloadUrl != null,
'Attachment must have an assetUrl or imageUrl or thumbUrl',
);
final response = await Dio().get<List<int>>(
downloadUrl!,
onReceiveProgress: onReceiveProgress, onReceiveProgress: onReceiveProgress,
queryParameters: queryParameters, queryParameters: queryParameters,
cancelToken: cancelToken, cancelToken: cancelToken,
// set responseType to `bytes` options: options,
options: options?.copyWith(responseType: ResponseType.bytes) ??
Options(responseType: ResponseType.bytes),
); );
final appDir = await getTemporaryDirectory(); final appDir = await getTemporaryDirectory();
final ext = Uri.parse(downloadUrl).pathSegments.last; final ext = Uri.parse(data.downloadUrl).pathSegments.last;
final path = '${appDir.path}/${attachment.id}.$ext'; final path = '${appDir.path}/${attachment.id}.$ext';
// Create an XFile for proper file saving // Create an XFile for proper file saving.
final file = XFile.fromData( final file = data.toXFile(path: path);
Uint8List.fromList(response.data!),
mimeType: attachment.mimeType,
name: fileName,
path: path,
);
// Save the file to the user's selected path. // Save the file to the user's selected path.
await file.saveTo(path); await file.saveTo(path);
@@ -91,12 +91,14 @@ class StreamChannelInfoBottomSheet extends StatelessWidget {
final member = members[index]; final member = members[index];
final user = member.user!; final user = member.user!;
return Column( return Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
StreamUserAvatar( StreamUserAvatar(
user: user, user: user,
constraints: const BoxConstraints( constraints: const BoxConstraints.tightFor(
maxHeight: 64, height: 64,
maxWidth: 64, width: 64,
), ),
borderRadius: BorderRadius.circular(32), borderRadius: BorderRadius.circular(32),
onlineIndicatorConstraints: BoxConstraints.tight( onlineIndicatorConstraints: BoxConstraints.tight(
@@ -83,7 +83,7 @@ abstract class Translations {
/// in the [StreamMessageListView] /// in the [StreamMessageListView]
String unreadMessagesSeparatorText( String unreadMessagesSeparatorText(
@Deprecated('unreadCount is not used anymore and will be removed ') @Deprecated('unreadCount is not used anymore and will be removed ')
int unreadCount, int unreadCount,
); );
/// The label for "connected" in [StreamConnectionStatusBuilder] /// The label for "connected" in [StreamConnectionStatusBuilder]
@@ -23,7 +23,7 @@ class StreamMessageThemeData with Diagnosticable {
this.avatarTheme, this.avatarTheme,
this.createdAtStyle, this.createdAtStyle,
@Deprecated('Use urlAttachmentBackgroundColor instead') @Deprecated('Use urlAttachmentBackgroundColor instead')
Color? linkBackgroundColor, Color? linkBackgroundColor,
Color? urlAttachmentBackgroundColor, Color? urlAttachmentBackgroundColor,
this.urlAttachmentHostStyle, this.urlAttachmentHostStyle,
this.urlAttachmentTitleStyle, this.urlAttachmentTitleStyle,
@@ -102,7 +102,7 @@ class StreamMessageThemeData with Diagnosticable {
Color? reactionsBorderColor, Color? reactionsBorderColor,
Color? reactionsMaskColor, Color? reactionsMaskColor,
@Deprecated('Use urlAttachmentBackgroundColor instead') @Deprecated('Use urlAttachmentBackgroundColor instead')
Color? linkBackgroundColor, Color? linkBackgroundColor,
Color? urlAttachmentBackgroundColor, Color? urlAttachmentBackgroundColor,
TextStyle? urlAttachmentHostStyle, TextStyle? urlAttachmentHostStyle,
TextStyle? urlAttachmentTitleStyle, TextStyle? urlAttachmentTitleStyle,
@@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M20.525 8.15C20.23 7.967 19.862 7.951 19.552 8.106L17 9.382V8C17 6.896 16.104 6 15 6H6C4.896 6 4 6.896 4 8V16C4 17.104 4.896 18 6 18H15C16.104 18 17 17.104 17 16V14.619L19.553 15.894C19.693 15.965 19.848 16 20 16C20.183 16 20.365 15.949 20.525 15.851C20.82 15.668 21 15.347 21 15V9C21 8.653 20.82 8.332 20.525 8.15Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 444 B

+3 -3
View File
@@ -1,7 +1,7 @@
name: stream_chat_flutter name: stream_chat_flutter
homepage: https://github.com/GetStream/stream-chat-flutter homepage: https://github.com/GetStream/stream-chat-flutter
description: Stream Chat official Flutter SDK. Build your own chat experience using Dart and Flutter. description: Stream Chat official Flutter SDK. Build your own chat experience using Dart and Flutter.
version: 6.2.0 version: 6.3.0
repository: https://github.com/GetStream/stream-chat-flutter repository: https://github.com/GetStream/stream-chat-flutter
issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues
@@ -17,7 +17,7 @@ dependencies:
dart_vlc: ^0.4.0 dart_vlc: ^0.4.0
desktop_drop: ^0.4.0 desktop_drop: ^0.4.0
diacritic: ^0.1.3 diacritic: ^0.1.3
dio: ^5.1.1 dio: ^5.2.0
ezanimation: ^0.6.0 ezanimation: ^0.6.0
file_picker: ^5.2.4 file_picker: ^5.2.4
file_selector: ^0.9.0 file_selector: ^0.9.0
@@ -38,7 +38,7 @@ dependencies:
rxdart: ^0.27.0 rxdart: ^0.27.0
share_plus: ^6.3.0 share_plus: ^6.3.0
shimmer: ^3.0.0 shimmer: ^3.0.0
stream_chat_flutter_core: ^6.2.0 stream_chat_flutter_core: ^6.3.0
synchronized: ^3.0.0 synchronized: ^3.0.0
thumblr: ^0.0.4 thumblr: ^0.0.4
url_launcher: ^6.1.0 url_launcher: ^6.1.0
@@ -1,3 +1,7 @@
## 6.3.0
- Updated `stream_chat` dependency to [`6.3.0`](https://pub.dev/packages/stream_chat/changelog).
## 6.2.0 ## 6.2.0
- Fixed `StreamMessageInputController.textPatternStyle` not matching case-insensitive patterns. - Fixed `StreamMessageInputController.textPatternStyle` not matching case-insensitive patterns.
@@ -430,16 +430,15 @@ class StreamChannelState extends State<StreamChannel> {
], ],
builder: (context, snapshot) { builder: (context, snapshot) {
if (snapshot.hasError) { if (snapshot.hasError) {
var message = snapshot.error.toString(); final error = snapshot.error;
if (snapshot.error is DioError) { if (error is DioException) {
final dioError = snapshot.error as DioError?; if (error.type == DioExceptionType.badResponse) {
if (dioError?.type == DioErrorType.badResponse) { return Center(child: Text(error.message ?? 'Bad response'));
message = dioError!.message ?? 'Bad response';
} else {
message = 'Check your connection and retry';
} }
return const Center(child: Text('Check your connection and retry'));
} }
return Center(child: Text(message));
return Center(child: Text(error.toString()));
} }
final dataLoaded = snapshot.data?.every((it) => it) == true; final dataLoaded = snapshot.data?.every((it) => it) == true;
@@ -1,7 +1,7 @@
name: stream_chat_flutter_core name: stream_chat_flutter_core
homepage: https://github.com/GetStream/stream-chat-flutter homepage: https://github.com/GetStream/stream-chat-flutter
description: Stream Chat official Flutter SDK Core. Build your own chat experience using Dart and Flutter. description: Stream Chat official Flutter SDK Core. Build your own chat experience using Dart and Flutter.
version: 6.2.0 version: 6.3.0
repository: https://github.com/GetStream/stream-chat-flutter repository: https://github.com/GetStream/stream-chat-flutter
issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues
@@ -17,7 +17,7 @@ dependencies:
freezed_annotation: ^2.0.3 freezed_annotation: ^2.0.3
meta: ^1.8.0 meta: ^1.8.0
rxdart: ^0.27.0 rxdart: ^0.27.0
stream_chat: ^6.2.0 stream_chat: ^6.3.0
dev_dependencies: dev_dependencies:
build_runner: ^2.3.3 build_runner: ^2.3.3
@@ -91,8 +91,8 @@ void main() {
); );
const errorMessage = 'Error! Error! Error!'; const errorMessage = 'Error! Error! Error!';
final error = DioError( final error = DioException(
type: DioErrorType.badResponse, type: DioExceptionType.badResponse,
message: errorMessage, message: errorMessage,
requestOptions: RequestOptions(), requestOptions: RequestOptions(),
); );
@@ -1,3 +1,7 @@
## 5.3.0
* Updated `stream_chat_flutter` dependency to [`6.3.0`](https://pub.dev/packages/stream_chat_flutter/changelog).
## 5.2.0 ## 5.2.0
* Updated `stream_chat_flutter` dependency to [`6.2.0`](https://pub.dev/packages/stream_chat_flutter/changelog). * Updated `stream_chat_flutter` dependency to [`6.2.0`](https://pub.dev/packages/stream_chat_flutter/changelog).
@@ -1,6 +1,6 @@
name: stream_chat_localizations name: stream_chat_localizations
description: The Official localizations for Stream Chat Flutter, a service for building chat applications description: The Official localizations for Stream Chat Flutter, a service for building chat applications
version: 5.2.0 version: 5.3.0
homepage: https://github.com/GetStream/stream-chat-flutter homepage: https://github.com/GetStream/stream-chat-flutter
repository: https://github.com/GetStream/stream-chat-flutter repository: https://github.com/GetStream/stream-chat-flutter
issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues
@@ -14,7 +14,7 @@ dependencies:
sdk: flutter sdk: flutter
flutter_localizations: flutter_localizations:
sdk: flutter sdk: flutter
stream_chat_flutter: ^6.2.0 stream_chat_flutter: ^6.3.0
dev_dependencies: dev_dependencies:
dart_code_metrics: ^5.7.2 dart_code_metrics: ^5.7.2
@@ -1,9 +1,15 @@
## 6.3.0
- Updated `stream_chat` dependency to [`6.3.0`](https://pub.dev/packages/stream_chat/changelog).
## 6.2.0 ## 6.2.0
- Added support for `StreamChatPersistenceClient.isConnected` for checking if the client is connected to the database. - Added support for `StreamChatPersistenceClient.isConnected` for checking if the client is connected to the database.
- [[#1422]](https://github.com/GetStream/stream-chat-flutter/issues/1422) Removed default values - [[#1422]](https://github.com/GetStream/stream-chat-flutter/issues/1422) Removed default values
from `UserEntity` `createdAt` and `updatedAt` fields. from `UserEntity` `createdAt` and `updatedAt` fields.
- Updated `stream_chat` dependency to [`6.2.0`](https://pub.dev/packages/stream_chat/changelog). - Updated `stream_chat` dependency to [`6.2.0`](https://pub.dev/packages/stream_chat/changelog).
- Added support for `StreamChatPersistenceClient.openPersistenceConnection`
and `StreamChatPersistenceClient.closePersistenceConnection` for opening and closing the database connection.
## 6.1.0 ## 6.1.0
@@ -82,6 +82,9 @@ class StreamChatPersistenceClient extends ChatPersistenceClient {
@override @override
bool get isConnected => db != null; bool get isConnected => db != null;
@override
String? get userId => db?.userId;
@override @override
Future<void> connect( Future<void> connect(
String userId, { String userId, {
@@ -248,9 +251,8 @@ class StreamChatPersistenceClient extends ChatPersistenceClient {
@override @override
Future<List<ChannelState>> getChannelStates({ Future<List<ChannelState>> getChannelStates({
Filter? filter, Filter? filter,
@Deprecated(''' @Deprecated('Use channelStateSort instead.')
sort has been deprecated. List<SortOption<ChannelModel>>? sort,
Please use channelStateSort instead.''') List<SortOption<ChannelModel>>? sort,
List<SortOption<ChannelState>>? channelStateSort, List<SortOption<ChannelState>>? channelStateSort,
PaginationParams? paginationParams, PaginationParams? paginationParams,
}) async { }) async {
@@ -1,7 +1,7 @@
name: stream_chat_persistence name: stream_chat_persistence
homepage: https://github.com/GetStream/stream-chat-flutter homepage: https://github.com/GetStream/stream-chat-flutter
description: Official Stream Chat Persistence library. Build your own chat experience using Dart and Flutter. description: Official Stream Chat Persistence library. Build your own chat experience using Dart and Flutter.
version: 6.2.0 version: 6.3.0
repository: https://github.com/GetStream/stream-chat-flutter repository: https://github.com/GetStream/stream-chat-flutter
issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues
@@ -18,7 +18,7 @@ dependencies:
path: ^1.8.2 path: ^1.8.2
path_provider: ^2.0.1 path_provider: ^2.0.1
sqlite3_flutter_libs: ^0.5.0 sqlite3_flutter_libs: ^0.5.0
stream_chat: ^6.2.0 stream_chat: ^6.3.0
dev_dependencies: dev_dependencies:
build_runner: ^2.3.3 build_runner: ^2.3.3
@@ -20,7 +20,7 @@ void main() {
await client.connect(userId, databaseProvider: testDatabaseProvider); await client.connect(userId, databaseProvider: testDatabaseProvider);
expect(client.isConnected, true); expect(client.isConnected, true);
expect(client.db, isA<DriftChatDatabase>()); expect(client.db, isA<DriftChatDatabase>());
expect(client.db!.userId, userId); expect(client.userId, userId);
addTearDown(() async { addTearDown(() async {
await client.disconnect(); await client.disconnect();
@@ -33,7 +33,7 @@ void main() {
await client.connect(userId, databaseProvider: testDatabaseProvider); await client.connect(userId, databaseProvider: testDatabaseProvider);
expect(client.isConnected, true); expect(client.isConnected, true);
expect(client.db, isA<DriftChatDatabase>()); expect(client.db, isA<DriftChatDatabase>());
expect(client.db!.userId, userId); expect(client.userId, userId);
expect( expect(
() => client.connect(userId, databaseProvider: testDatabaseProvider), () => client.connect(userId, databaseProvider: testDatabaseProvider),
throwsException, throwsException,