Merge pull request #1598 from GetStream/release/v6.3.0
This commit is contained in:
@@ -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:
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-2
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+24
-43
@@ -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);
|
||||||
|
|||||||
+5
-3
@@ -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 |
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user