Merge branch 'develop' into cds-189
This commit is contained in:
@@ -1,3 +1,30 @@
|
||||
## 2.1.1
|
||||
|
||||
🐞 Fixed
|
||||
|
||||
- Mutes were not working correctly in 2.1.0
|
||||
|
||||
## 2.1.0
|
||||
|
||||
🛑️ Removed
|
||||
|
||||
- The `MessageTranslation` class has been removed. Use the new `i18n` field in the `Message` class instead.
|
||||
|
||||
✅ Added
|
||||
|
||||
- The `Message` class now has an `i18n` field for translations
|
||||
- The `User` class now has a `language` field for the user's language preference.
|
||||
|
||||
🔄 Changed
|
||||
|
||||
- `client.user` is now deprecated in favor of `client.currentUser`.
|
||||
- `client.userStream` is now deprecated in favor of `client.currentUserStream`.
|
||||
|
||||
🐞 Fixed
|
||||
|
||||
- [#563](https://github.com/GetStream/stream-chat-flutter/issues/563): `Channel.stopWatching()` not working
|
||||
- [#575](https://github.com/GetStream/stream-chat-flutter/issues/575): Wrong `OwnUser.*`
|
||||
|
||||
## 2.0.0
|
||||
|
||||
🛑️ Breaking Changes from `1.5.3`
|
||||
@@ -619,4 +646,4 @@
|
||||
|
||||
## 0.0.2
|
||||
|
||||
- first beta version
|
||||
- first beta version
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
- [Register](https://getstream.io/chat/trial/) to get an API key for Stream Chat
|
||||
- [Flutter Chat Tutorial](https://getstream.io/chat/flutter/tutorial/)
|
||||
- [Chat UI Kit](https://getstream.io/chat/ui-kit/)
|
||||
- [Chat Client Docs](https://getstream.io/chat/docs/flutter-dart/?language=dart)
|
||||
|
||||
### Changelog
|
||||
|
||||
|
||||
@@ -245,5 +245,5 @@ class _MessageViewState extends State<MessageView> {
|
||||
/// Helper extension for quickly retrieving
|
||||
/// the current user id from a [StreamChatClient].
|
||||
extension on StreamChatClient {
|
||||
String get uid => state.user!.id;
|
||||
String get uid => state.currentUser!.id;
|
||||
}
|
||||
|
||||
@@ -65,12 +65,12 @@ class Channel {
|
||||
|
||||
/// Returns true if the channel is muted
|
||||
bool get isMuted =>
|
||||
_client.state.user?.channelMutes
|
||||
_client.state.currentUser?.channelMutes
|
||||
.any((element) => element.channel.cid == cid) ==
|
||||
true;
|
||||
|
||||
/// Returns true if the channel is muted as a stream
|
||||
Stream<bool>? get isMutedStream => _client.state.userStream
|
||||
Stream<bool>? get isMutedStream => _client.state.currentUserStream
|
||||
.map((event) =>
|
||||
event!.channelMutes.any((element) => element.channel.cid == cid) ==
|
||||
true)
|
||||
@@ -397,7 +397,7 @@ class Channel {
|
||||
// ignore: parameter_assignments
|
||||
message = message.copyWith(
|
||||
createdAt: message.createdAt,
|
||||
user: _client.state.user,
|
||||
user: _client.state.currentUser,
|
||||
quotedMessage: quotedMessage,
|
||||
status: MessageSendingStatus.sending,
|
||||
attachments: message.attachments.map(
|
||||
@@ -711,7 +711,7 @@ class Channel {
|
||||
_checkInitialized();
|
||||
final messageId = message.id;
|
||||
final now = DateTime.now();
|
||||
final user = _client.state.user;
|
||||
final user = _client.state.currentUser;
|
||||
|
||||
final latestReactions = [...message.latestReactions ?? <Reaction>[]];
|
||||
if (enforceUnique) {
|
||||
@@ -768,7 +768,7 @@ class Channel {
|
||||
Future<EmptyResponse> deleteReaction(
|
||||
Message message, Reaction reaction) async {
|
||||
final type = reaction.type;
|
||||
final user = _client.state.user;
|
||||
final user = _client.state.currentUser;
|
||||
|
||||
final reactionCounts = {...message.reactionCounts ?? <String, int>{}};
|
||||
if (reactionCounts.containsKey(type)) {
|
||||
@@ -1346,7 +1346,7 @@ class ChannelClientState {
|
||||
|
||||
void _computeInitialUnread() {
|
||||
final userRead = channelState.read.firstWhereOrNull(
|
||||
(r) => r.user.id == _channel._client.state.user?.id,
|
||||
(r) => r.user.id == _channel._client.state.currentUser?.id,
|
||||
);
|
||||
if (userRead != null) {
|
||||
unreadCount = userRead.unreadMessages;
|
||||
@@ -1463,7 +1463,7 @@ class ChannelClientState {
|
||||
|
||||
void _listenReactionDeleted() {
|
||||
_subscriptions.add(_channel.on(EventType.reactionDeleted).listen((event) {
|
||||
final userId = _channel.client.state.user!.id;
|
||||
final userId = _channel.client.state.currentUser!.id;
|
||||
final message = event.message!.copyWith(
|
||||
ownReactions: [...event.message!.latestReactions!]
|
||||
..removeWhere((it) => it.userId != userId),
|
||||
@@ -1474,7 +1474,7 @@ class ChannelClientState {
|
||||
|
||||
void _listenReactions() {
|
||||
_subscriptions.add(_channel.on(EventType.reactionNew).listen((event) {
|
||||
final userId = _channel.client.state.user!.id;
|
||||
final userId = _channel.client.state.currentUser!.id;
|
||||
final message = event.message!.copyWith(
|
||||
ownReactions: [...event.message!.latestReactions!]
|
||||
..removeWhere((it) => it.userId != userId),
|
||||
@@ -1490,7 +1490,7 @@ class ChannelClientState {
|
||||
EventType.reactionUpdated,
|
||||
)
|
||||
.listen((event) {
|
||||
final userId = _channel.client.state.user!.id;
|
||||
final userId = _channel.client.state.currentUser!.id;
|
||||
final message = event.message!.copyWith(
|
||||
ownReactions: [...event.message!.latestReactions!]
|
||||
..removeWhere((it) => it.userId != userId),
|
||||
@@ -1584,7 +1584,7 @@ class ChannelClientState {
|
||||
|
||||
if (userReadIndex != null && userReadIndex != -1) {
|
||||
final userRead = readList.removeAt(userReadIndex);
|
||||
if (userRead.user.id == _channel._client.state.user!.id) {
|
||||
if (userRead.user.id == _channel._client.state.currentUser!.id) {
|
||||
unreadCount = 0;
|
||||
}
|
||||
readList.add(Read(
|
||||
@@ -1674,11 +1674,12 @@ class ChannelClientState {
|
||||
int get unreadCount => _unreadCountController.value;
|
||||
|
||||
bool _countMessageAsUnread(Message message) {
|
||||
final userId = _channel.client.state.user?.id;
|
||||
final userIsMuted = _channel.client.state.user?.mutes.firstWhereOrNull(
|
||||
(m) => m.user.id == message.user?.id,
|
||||
) !=
|
||||
null;
|
||||
final userId = _channel.client.state.currentUser?.id;
|
||||
final userIsMuted =
|
||||
_channel.client.state.currentUser?.mutes.firstWhereOrNull(
|
||||
(m) => m.user.id == message.user?.id,
|
||||
) !=
|
||||
null;
|
||||
return message.silent != true &&
|
||||
message.shadowed != true &&
|
||||
message.user?.id != userId &&
|
||||
@@ -1827,7 +1828,7 @@ class ChannelClientState {
|
||||
(event) {
|
||||
if (event.user != null) {
|
||||
final user = event.user!;
|
||||
if (user.id != _channel.client.state.user?.id) {
|
||||
if (user.id != _channel.client.state.currentUser?.id) {
|
||||
_typings[user] = event;
|
||||
_typingEventsController.add(_typings);
|
||||
}
|
||||
@@ -1840,7 +1841,7 @@ class ChannelClientState {
|
||||
(event) {
|
||||
if (event.user != null) {
|
||||
final user = event.user!;
|
||||
if (user.id != _channel.client.state.user?.id) {
|
||||
if (user.id != _channel.client.state.currentUser?.id) {
|
||||
_typings.remove(event.user);
|
||||
_typingEventsController.add(_typings);
|
||||
}
|
||||
|
||||
@@ -25,12 +25,14 @@ import 'package:stream_chat/src/core/models/member.dart';
|
||||
import 'package:stream_chat/src/core/models/message.dart';
|
||||
import 'package:stream_chat/src/core/models/own_user.dart';
|
||||
import 'package:stream_chat/src/core/models/user.dart';
|
||||
import 'package:stream_chat/src/core/platform_detector/platform_detector.dart';
|
||||
import 'package:stream_chat/src/core/util/utils.dart';
|
||||
import 'package:stream_chat/src/db/chat_persistence_client.dart';
|
||||
import 'package:stream_chat/src/event_type.dart';
|
||||
import 'package:stream_chat/src/location.dart';
|
||||
import 'package:stream_chat/src/ws/connection_status.dart';
|
||||
import 'package:stream_chat/src/ws/websocket.dart';
|
||||
import 'package:stream_chat/version.dart';
|
||||
|
||||
/// Handler function used for logging records. Function requires a single
|
||||
/// [LogRecord] as the only parameter.
|
||||
@@ -42,6 +44,10 @@ final _levelEmojiMapper = {
|
||||
Level.SEVERE: '🚨',
|
||||
};
|
||||
|
||||
final _userAgent = 'stream-chat-dart-client-'
|
||||
'${CurrentPlatform.name}-'
|
||||
'${PACKAGE_VERSION.split('+')[0]}';
|
||||
|
||||
/// The official Dart client for Stream Chat,
|
||||
/// a service for building chat applications.
|
||||
/// This library can be used on any Dart project and on both mobile and web apps
|
||||
@@ -80,6 +86,7 @@ class StreamChatClient {
|
||||
location: location,
|
||||
connectTimeout: connectTimeout,
|
||||
receiveTimeout: receiveTimeout,
|
||||
headers: {'X-Stream-Client': _userAgent},
|
||||
);
|
||||
|
||||
_chatApi = chatApi ??
|
||||
@@ -99,6 +106,7 @@ class StreamChatClient {
|
||||
tokenManager: _tokenManager,
|
||||
handler: handleEvent,
|
||||
logger: detachedLogger('🔌'),
|
||||
queryParameters: {'X-Stream-Client': _userAgent},
|
||||
);
|
||||
|
||||
_retryPolicy = retryPolicy ??
|
||||
@@ -308,23 +316,21 @@ class StreamChatClient {
|
||||
);
|
||||
|
||||
final ownUser = OwnUser.fromUser(user);
|
||||
state.user = ownUser;
|
||||
state.currentUser = ownUser;
|
||||
|
||||
if (!connectWebSocket) {
|
||||
return ownUser;
|
||||
}
|
||||
if (!connectWebSocket) return ownUser;
|
||||
|
||||
try {
|
||||
if (_originalChatPersistenceClient != null) {
|
||||
_chatPersistenceClient = _originalChatPersistenceClient;
|
||||
await _chatPersistenceClient!.connect(ownUser.id);
|
||||
}
|
||||
final res = await openConnection();
|
||||
return res;
|
||||
final connectedUser = await openConnection();
|
||||
return state.currentUser = connectedUser;
|
||||
} catch (e, stk) {
|
||||
if (e is StreamWebSocketError && e.isRetriable) {
|
||||
final event = await _chatPersistenceClient?.getConnectionInfo();
|
||||
if (event != null) return event.me?.merge(ownUser) ?? ownUser;
|
||||
if (event != null) return ownUser.merge(event.me);
|
||||
}
|
||||
logger.severe('error connecting user : ${ownUser.id}', e, stk);
|
||||
rethrow;
|
||||
@@ -334,12 +340,12 @@ class StreamChatClient {
|
||||
/// Creates a new WebSocket connection with the current user.
|
||||
Future<OwnUser> openConnection() async {
|
||||
assert(
|
||||
state.user != null,
|
||||
state.currentUser != null,
|
||||
'User is not set on client, '
|
||||
'use `connectUser` or `connectAnonymousUser` instead',
|
||||
);
|
||||
|
||||
final user = state.user!;
|
||||
final user = state.currentUser!;
|
||||
|
||||
logger.info('Opening web-socket connection for ${user.id}');
|
||||
|
||||
@@ -363,7 +369,7 @@ class StreamChatClient {
|
||||
|
||||
try {
|
||||
final event = await _ws.connect(user);
|
||||
return event.me?.merge(user) ?? user;
|
||||
return user.merge(event.me);
|
||||
} catch (e, stk) {
|
||||
logger.severe('error connecting ws', e, stk);
|
||||
rethrow;
|
||||
@@ -378,7 +384,7 @@ class StreamChatClient {
|
||||
void closeConnection() {
|
||||
if (wsConnectionStatus == ConnectionStatus.disconnected) return;
|
||||
|
||||
logger.info('Closing web-socket connection for ${state.user?.id}');
|
||||
logger.info('Closing web-socket connection for ${state.currentUser?.id}');
|
||||
_wsConnectionStatus = ConnectionStatus.disconnected;
|
||||
|
||||
_connectionStatusSubscription?.cancel();
|
||||
@@ -388,9 +394,6 @@ class StreamChatClient {
|
||||
}
|
||||
|
||||
void _handleHealthCheckEvent(Event event) {
|
||||
final user = event.me;
|
||||
if (user != null) state.user = user;
|
||||
|
||||
final connectionId = event.connectionId;
|
||||
if (connectionId != null) {
|
||||
_connectionIdManager.setConnectionId(connectionId);
|
||||
@@ -1312,7 +1315,7 @@ class StreamChatClient {
|
||||
/// If [flushChatPersistence] is true the client deletes all offline
|
||||
/// user's data.
|
||||
Future<void> disconnectUser({bool flushChatPersistence = false}) async {
|
||||
logger.info('Disconnecting user : ${state.user?.id}');
|
||||
logger.info('Disconnecting user : ${state.currentUser?.id}');
|
||||
|
||||
// resetting state
|
||||
state.dispose();
|
||||
@@ -1355,16 +1358,15 @@ class ClientState {
|
||||
_subscriptions.addAll([
|
||||
_client
|
||||
.on()
|
||||
.where((event) => event.me != null)
|
||||
.map((e) => e.me)
|
||||
.where((event) =>
|
||||
event.me != null && event.type != EventType.healthCheck)
|
||||
.map((e) => e.me!)
|
||||
.listen((user) {
|
||||
_userController.add(user);
|
||||
final totalUnreadCount = user?.totalUnreadCount;
|
||||
if (totalUnreadCount != null) {
|
||||
_totalUnreadCountController.add(totalUnreadCount);
|
||||
}
|
||||
currentUser = currentUser?.merge(user) ?? user;
|
||||
final totalUnreadCount = user.totalUnreadCount;
|
||||
_totalUnreadCountController.add(totalUnreadCount);
|
||||
|
||||
final unreadChannels = user?.unreadChannels;
|
||||
final unreadChannels = user.unreadChannels;
|
||||
if (unreadChannels != null) {
|
||||
_unreadChannelsController.add(unreadChannels);
|
||||
}
|
||||
@@ -1408,8 +1410,8 @@ class ClientState {
|
||||
|
||||
void _listenUserUpdated() {
|
||||
_subscriptions.add(_client.on(EventType.userUpdated).listen((event) {
|
||||
if (event.user!.id == user!.id) {
|
||||
user = OwnUser.fromJson(event.user!.toJson());
|
||||
if (event.user!.id == currentUser!.id) {
|
||||
currentUser = OwnUser.fromJson(event.user!.toJson());
|
||||
}
|
||||
updateUser(event.user);
|
||||
}));
|
||||
@@ -1431,9 +1433,10 @@ class ClientState {
|
||||
|
||||
final StreamChatClient _client;
|
||||
|
||||
/// Update user information
|
||||
set user(OwnUser? user) {
|
||||
_userController.add(user);
|
||||
/// Sets the user currently interacting with the client
|
||||
/// note: this fully overrides the [currentUser]
|
||||
set currentUser(OwnUser? user) {
|
||||
_currentUserController.add(user);
|
||||
}
|
||||
|
||||
/// Update all the [users] with the provided [userList]
|
||||
@@ -1450,10 +1453,24 @@ class ClientState {
|
||||
void updateUser(User? user) => updateUsers([user]);
|
||||
|
||||
/// The current user
|
||||
OwnUser? get user => _userController.valueOrNull;
|
||||
OwnUser? get currentUser => _currentUserController.valueOrNull;
|
||||
|
||||
/// The current user as a stream
|
||||
Stream<OwnUser?> get userStream => _userController.stream;
|
||||
Stream<OwnUser?> get currentUserStream => _currentUserController.stream;
|
||||
|
||||
// coverage:ignore-start
|
||||
|
||||
/// The current user
|
||||
@Deprecated('Use `.currentUser` instead, Will be removed in future releases')
|
||||
OwnUser? get user => _currentUserController.valueOrNull;
|
||||
|
||||
/// The current user as a stream
|
||||
@Deprecated(
|
||||
'Use `.currentUserStream` instead, Will be removed in future releases',
|
||||
)
|
||||
Stream<OwnUser?> get userStream => _currentUserController.stream;
|
||||
|
||||
// coverage:ignore-end
|
||||
|
||||
/// The current user
|
||||
Map<String, User> get users => _usersController.value;
|
||||
@@ -1485,7 +1502,7 @@ class ClientState {
|
||||
}
|
||||
|
||||
final _channelsController = BehaviorSubject<Map<String, Channel>>.seeded({});
|
||||
final _userController = BehaviorSubject<OwnUser?>();
|
||||
final _currentUserController = BehaviorSubject<OwnUser?>();
|
||||
final _usersController = BehaviorSubject<Map<String, User>>.seeded({});
|
||||
final _unreadChannelsController = BehaviorSubject<int>.seeded(0);
|
||||
final _totalUnreadCountController = BehaviorSubject<int>.seeded(0);
|
||||
@@ -1493,7 +1510,7 @@ class ClientState {
|
||||
/// Call this method to dispose this object
|
||||
void dispose() {
|
||||
_subscriptions.forEach((s) => s.cancel());
|
||||
_userController.close();
|
||||
_currentUserController.close();
|
||||
_unreadChannelsController.close();
|
||||
_totalUnreadCountController.close();
|
||||
channels.values.forEach((c) => c.dispose());
|
||||
|
||||
@@ -75,7 +75,7 @@ class QueryChannelsResponse extends _BaseResponse {
|
||||
@JsonSerializable(createToJson: false)
|
||||
class TranslateMessageResponse extends _BaseResponse {
|
||||
/// Translated message
|
||||
late TranslatedMessage message;
|
||||
late Message message;
|
||||
|
||||
/// Create a new instance from a json
|
||||
static TranslateMessageResponse fromJson(Map<String, dynamic> json) =>
|
||||
|
||||
@@ -47,8 +47,7 @@ TranslateMessageResponse _$TranslateMessageResponseFromJson(
|
||||
Map<String, dynamic> json) {
|
||||
return TranslateMessageResponse()
|
||||
..duration = json['duration'] as String?
|
||||
..message =
|
||||
TranslatedMessage.fromJson(json['message'] as Map<String, dynamic>);
|
||||
..message = Message.fromJson(json['message'] as Map<String, dynamic>);
|
||||
}
|
||||
|
||||
QueryMembersResponse _$QueryMembersResponseFromJson(Map<String, dynamic> json) {
|
||||
|
||||
@@ -10,9 +10,7 @@ import 'package:stream_chat/src/core/http/interceptor/connection_id_interceptor.
|
||||
import 'package:stream_chat/src/core/http/interceptor/logging_interceptor.dart';
|
||||
import 'package:stream_chat/src/core/http/stream_chat_dio_error.dart';
|
||||
import 'package:stream_chat/src/core/http/token_manager.dart';
|
||||
import 'package:stream_chat/src/core/platform_detector/platform_detector.dart';
|
||||
import 'package:stream_chat/src/location.dart';
|
||||
import 'package:stream_chat/version.dart';
|
||||
|
||||
part 'stream_http_client_options.dart';
|
||||
|
||||
@@ -33,11 +31,14 @@ class StreamHttpClient {
|
||||
..options.baseUrl = _options.baseUrl
|
||||
..options.receiveTimeout = _options.receiveTimeout.inMilliseconds
|
||||
..options.connectTimeout = _options.connectTimeout.inMilliseconds
|
||||
..options.queryParameters = {'api_key': apiKey}
|
||||
..options.queryParameters = {
|
||||
'api_key': apiKey,
|
||||
..._options.queryParameters,
|
||||
}
|
||||
..options.headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Stream-Client': _options.userAgent,
|
||||
'Content-Encoding': 'application/gzip',
|
||||
..._options.headers,
|
||||
}
|
||||
..interceptors.addAll([
|
||||
if (tokenManager != null) AuthInterceptor(this, tokenManager),
|
||||
|
||||
@@ -10,6 +10,8 @@ class StreamHttpClientOptions {
|
||||
this.location,
|
||||
this.connectTimeout = const Duration(seconds: 6),
|
||||
this.receiveTimeout = const Duration(seconds: 6),
|
||||
this.queryParameters = const {},
|
||||
this.headers = const {},
|
||||
}) : _baseUrl = baseUrl ?? _defaultBaseURL;
|
||||
|
||||
final String _baseUrl;
|
||||
@@ -32,8 +34,20 @@ class StreamHttpClientOptions {
|
||||
/// received timeout, default to 6s
|
||||
final Duration receiveTimeout;
|
||||
|
||||
/// Get the current user agent
|
||||
String get userAgent => 'stream-chat-dart-client-'
|
||||
'${CurrentPlatform.name}-'
|
||||
'${PACKAGE_VERSION.split('+')[0]}';
|
||||
/// Common query parameters.
|
||||
///
|
||||
/// List values use the default [ListFormat.multiCompatible].
|
||||
///
|
||||
/// The value can be overridden per parameter by adding a [MultiParam]
|
||||
/// object wrapping the actual List value and the desired format.
|
||||
final Map<String, Object?> queryParameters;
|
||||
|
||||
/// Http request headers.
|
||||
/// The keys of initial headers will be converted to lowercase,
|
||||
/// for example 'Content-Type' will be converted to 'content-type'.
|
||||
///
|
||||
/// The key of Header Map is case-insensitive
|
||||
/// eg: content-type and Content-Type are
|
||||
/// regard as the same key.
|
||||
final Map<String, Object?> headers;
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@ import 'package:equatable/equatable.dart';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
import 'package:stream_chat/src/core/models/attachment.dart';
|
||||
import 'package:stream_chat/src/core/models/reaction.dart';
|
||||
import 'package:stream_chat/src/core/util/serializer.dart';
|
||||
import 'package:stream_chat/src/core/models/user.dart';
|
||||
import 'package:stream_chat/src/core/util/serializer.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
part 'message.g.dart';
|
||||
@@ -73,6 +73,7 @@ class Message extends Equatable {
|
||||
this.extraData = const {},
|
||||
this.deletedAt,
|
||||
this.status = MessageSendingStatus.sent,
|
||||
this.i18n,
|
||||
}) : id = id ?? const Uuid().v4(),
|
||||
pinExpires = pinExpires?.toUtc(),
|
||||
createdAt = createdAt ?? DateTime.now(),
|
||||
@@ -218,6 +219,10 @@ class Message extends Equatable {
|
||||
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
|
||||
final DateTime? deletedAt;
|
||||
|
||||
/// A Map of translations.
|
||||
@JsonKey(includeIfNull: false)
|
||||
final Map<String, String>? i18n;
|
||||
|
||||
/// Known top level fields.
|
||||
/// Useful for [Serializer] methods.
|
||||
static const topLevelFields = [
|
||||
@@ -248,6 +253,7 @@ class Message extends Equatable {
|
||||
'pinned_at',
|
||||
'pin_expires',
|
||||
'pinned_by',
|
||||
'i18n',
|
||||
];
|
||||
|
||||
/// Serialize to json
|
||||
@@ -285,6 +291,7 @@ class Message extends Equatable {
|
||||
User? pinnedBy,
|
||||
Map<String, Object?>? extraData,
|
||||
MessageSendingStatus? status,
|
||||
Map<String, String>? i18n,
|
||||
}) {
|
||||
assert(() {
|
||||
if (pinExpires is! DateTime &&
|
||||
@@ -324,6 +331,7 @@ class Message extends Equatable {
|
||||
pinnedBy: pinnedBy ?? this.pinnedBy,
|
||||
pinExpires:
|
||||
pinExpires == _pinExpires ? this.pinExpires : pinExpires as DateTime?,
|
||||
i18n: i18n ?? this.i18n,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -358,6 +366,7 @@ class Message extends Equatable {
|
||||
pinnedAt: other.pinnedAt,
|
||||
pinExpires: other.pinExpires,
|
||||
pinnedBy: other.pinnedBy,
|
||||
i18n: other.i18n,
|
||||
);
|
||||
|
||||
@override
|
||||
@@ -390,35 +399,6 @@ class Message extends Equatable {
|
||||
pinnedBy,
|
||||
extraData,
|
||||
status,
|
||||
i18n,
|
||||
];
|
||||
}
|
||||
|
||||
/// A translated message
|
||||
/// It has an additional property called [i18n]
|
||||
@JsonSerializable()
|
||||
class TranslatedMessage extends Message {
|
||||
/// Constructor used for json serialization
|
||||
TranslatedMessage(this.i18n) : super();
|
||||
|
||||
/// Create a new instance from a json
|
||||
factory TranslatedMessage.fromJson(Map<String, dynamic> json) =>
|
||||
_$TranslatedMessageFromJson(
|
||||
Serializer.moveToExtraDataFromRoot(json, topLevelFields),
|
||||
);
|
||||
|
||||
/// A Map of
|
||||
final Map<String, String>? i18n;
|
||||
|
||||
/// Known top level fields.
|
||||
/// Useful for [Serializer] methods.
|
||||
static final topLevelFields = [
|
||||
'i18n',
|
||||
...Message.topLevelFields,
|
||||
];
|
||||
|
||||
/// Serialize to json
|
||||
@override
|
||||
Map<String, dynamic> toJson() => Serializer.moveFromExtraDataToRoot(
|
||||
_$TranslatedMessageToJson(this),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -67,6 +67,9 @@ Message _$MessageFromJson(Map<String, dynamic> json) {
|
||||
deletedAt: json['deleted_at'] == null
|
||||
? null
|
||||
: DateTime.parse(json['deleted_at'] as String),
|
||||
i18n: (json['i18n'] as Map<String, dynamic>?)?.map(
|
||||
(k, e) => MapEntry(k, e as String),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -107,18 +110,6 @@ Map<String, dynamic> _$MessageToJson(Message instance) {
|
||||
val['pinned_by'] = readonly(instance.pinnedBy);
|
||||
val['extra_data'] = instance.extraData;
|
||||
writeNotNull('deleted_at', readonly(instance.deletedAt));
|
||||
writeNotNull('i18n', instance.i18n);
|
||||
return val;
|
||||
}
|
||||
|
||||
TranslatedMessage _$TranslatedMessageFromJson(Map<String, dynamic> json) {
|
||||
return TranslatedMessage(
|
||||
(json['i18n'] as Map<String, dynamic>?)?.map(
|
||||
(k, e) => MapEntry(k, e as String),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> _$TranslatedMessageToJson(TranslatedMessage instance) =>
|
||||
<String, dynamic>{
|
||||
'i18n': instance.i18n,
|
||||
};
|
||||
|
||||
@@ -27,6 +27,7 @@ class OwnUser extends User {
|
||||
Map<String, Object?> extraData = const {},
|
||||
bool banned = false,
|
||||
List<String> teams = const [],
|
||||
String? language,
|
||||
}) : super(
|
||||
id: id,
|
||||
role: role,
|
||||
@@ -37,6 +38,7 @@ class OwnUser extends User {
|
||||
extraData: extraData,
|
||||
banned: banned,
|
||||
teams: teams,
|
||||
language: language,
|
||||
);
|
||||
|
||||
/// Create a new instance from a json
|
||||
@@ -54,6 +56,7 @@ class OwnUser extends User {
|
||||
banned: user.banned,
|
||||
extraData: user.extraData,
|
||||
teams: user.teams,
|
||||
language: user.language,
|
||||
);
|
||||
|
||||
/// Creates a copy of [OwnUser] with specified attributes overridden.
|
||||
@@ -73,6 +76,7 @@ class OwnUser extends User {
|
||||
List<Mute>? mutes,
|
||||
int? totalUnreadCount,
|
||||
int? unreadChannels,
|
||||
String? language,
|
||||
}) =>
|
||||
OwnUser(
|
||||
id: id ?? this.id,
|
||||
@@ -89,15 +93,13 @@ class OwnUser extends User {
|
||||
mutes: mutes ?? this.mutes,
|
||||
totalUnreadCount: totalUnreadCount ?? this.totalUnreadCount,
|
||||
unreadChannels: unreadChannels ?? this.unreadChannels,
|
||||
language: language ?? this.language,
|
||||
);
|
||||
|
||||
/// Returns a new [OwnUser] that is a combination of this ownUser
|
||||
/// and the given [other] ownUser.
|
||||
OwnUser merge(OwnUser? other) {
|
||||
if (other == null) {
|
||||
return this;
|
||||
}
|
||||
|
||||
if (other == null) return this;
|
||||
return copyWith(
|
||||
banned: other.banned,
|
||||
channelMutes: other.channelMutes,
|
||||
@@ -113,6 +115,7 @@ class OwnUser extends User {
|
||||
totalUnreadCount: other.totalUnreadCount,
|
||||
unreadChannels: other.unreadChannels,
|
||||
updatedAt: other.updatedAt,
|
||||
language: other.language,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -39,5 +39,6 @@ OwnUser _$OwnUserFromJson(Map<String, dynamic> json) {
|
||||
teams:
|
||||
(json['teams'] as List<dynamic>?)?.map((e) => e as String).toList() ??
|
||||
[],
|
||||
language: json['language'] as String?,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ class User extends Equatable {
|
||||
this.extraData = const {},
|
||||
this.banned = false,
|
||||
this.teams = const [],
|
||||
this.language,
|
||||
}) : createdAt = createdAt ?? DateTime.now(),
|
||||
updatedAt = updatedAt ?? DateTime.now();
|
||||
|
||||
@@ -36,6 +37,7 @@ class User extends Equatable {
|
||||
'online',
|
||||
'banned',
|
||||
'teams',
|
||||
'language',
|
||||
];
|
||||
|
||||
/// User id
|
||||
@@ -82,8 +84,11 @@ class User extends Equatable {
|
||||
)
|
||||
final Map<String, Object?> extraData;
|
||||
|
||||
@override
|
||||
int get hashCode => id.hashCode;
|
||||
/// The language this user prefers.
|
||||
///
|
||||
/// Defaults to 'en'.
|
||||
@JsonKey(includeIfNull: false)
|
||||
final String? language;
|
||||
|
||||
/// Shortcut for user name
|
||||
String get name {
|
||||
@@ -98,11 +103,6 @@ class User extends Equatable {
|
||||
static List<String>? toIds(List<User>? users) =>
|
||||
users?.map((u) => u.id).toList();
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is User && runtimeType == other.runtimeType && id == other.id;
|
||||
|
||||
/// Serialize to json
|
||||
Map<String, dynamic> toJson() => Serializer.moveFromExtraDataToRoot(
|
||||
_$UserToJson(this),
|
||||
@@ -119,6 +119,7 @@ class User extends Equatable {
|
||||
Map<String, Object?>? extraData,
|
||||
bool? banned,
|
||||
List<String>? teams,
|
||||
String? language,
|
||||
}) =>
|
||||
User(
|
||||
id: id ?? this.id,
|
||||
@@ -130,18 +131,9 @@ class User extends Equatable {
|
||||
extraData: extraData ?? this.extraData,
|
||||
banned: banned ?? this.banned,
|
||||
teams: teams ?? this.teams,
|
||||
language: language ?? this.language,
|
||||
);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
id,
|
||||
role,
|
||||
teams,
|
||||
createdAt,
|
||||
updatedAt,
|
||||
lastActive,
|
||||
online,
|
||||
banned,
|
||||
extraData,
|
||||
];
|
||||
List<Object?> get props => [id];
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ User _$UserFromJson(Map<String, dynamic> json) {
|
||||
teams:
|
||||
(json['teams'] as List<dynamic>?)?.map((e) => e as String).toList() ??
|
||||
[],
|
||||
language: json['language'] as String?,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -47,5 +48,6 @@ Map<String, dynamic> _$UserToJson(User instance) {
|
||||
writeNotNull('online', readonly(instance.online));
|
||||
writeNotNull('banned', readonly(instance.banned));
|
||||
val['extra_data'] = instance.extraData;
|
||||
writeNotNull('language', instance.language);
|
||||
return val;
|
||||
}
|
||||
|
||||
@@ -41,11 +41,15 @@ class WebSocket with TimerHelper {
|
||||
this.reconnectionMonitorInterval = 10,
|
||||
this.healthCheckInterval = 20,
|
||||
this.reconnectionMonitorTimeout = 40,
|
||||
this.queryParameters = const {},
|
||||
}) : _logger = logger;
|
||||
|
||||
///
|
||||
final String apiKey;
|
||||
|
||||
/// Additional query parameters to be added to the websocket url
|
||||
final Map<String, Object?> queryParameters;
|
||||
|
||||
/// WS base url
|
||||
final String baseUrl;
|
||||
|
||||
@@ -156,6 +160,7 @@ class WebSocket with TimerHelper {
|
||||
'api_key': apiKey,
|
||||
'authorization': token.rawValue,
|
||||
'stream-auth-type': token.authType.raw,
|
||||
...queryParameters,
|
||||
};
|
||||
final scheme = baseUrl.startsWith('https') ? 'wss' : 'ws';
|
||||
final host = baseUrl.replaceAll(RegExp(r'(^\w+:|^)\/\/'), '');
|
||||
|
||||
@@ -3,4 +3,4 @@ import 'package:stream_chat/src/client/client.dart';
|
||||
/// Current package version
|
||||
/// Used in [StreamChatClient] to build the `x-stream-client` header
|
||||
// ignore: constant_identifier_names
|
||||
const PACKAGE_VERSION = '2.0.0';
|
||||
const PACKAGE_VERSION = '2.1.1';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
name: stream_chat
|
||||
homepage: https://getstream.io/
|
||||
description: The official Dart client for Stream Chat, a service for building chat applications.
|
||||
version: 2.0.0
|
||||
version: 2.1.1
|
||||
repository: https://github.com/GetStream/stream-chat-flutter
|
||||
issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues
|
||||
|
||||
|
||||
+2
-1
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"id": "bbb19d9a-ee50-45bc-84e5-0584e79d0c9e",
|
||||
"role": "test-role",
|
||||
"name": "John"
|
||||
"name": "John",
|
||||
"language": "en"
|
||||
}
|
||||
@@ -1610,9 +1610,12 @@ void main() {
|
||||
const messageId = 'test-message-id';
|
||||
const language = 'hi'; // Hindi
|
||||
const translatedMessageText = 'नमस्ते';
|
||||
final translatedMessage = TranslatedMessage(const {
|
||||
language: translatedMessageText,
|
||||
});
|
||||
|
||||
final translatedMessage = Message(
|
||||
i18n: const {
|
||||
language: translatedMessageText,
|
||||
},
|
||||
);
|
||||
|
||||
when(() => client.translateMessage(messageId, language)).thenAnswer(
|
||||
(_) async => TranslateMessageResponse()..message = translatedMessage,
|
||||
|
||||
@@ -155,7 +155,7 @@ void main() {
|
||||
|
||||
group('`.openConnection`', () {
|
||||
test('should throw if state does not contain user', () async {
|
||||
expect(client.state.user, isNull);
|
||||
expect(client.state.currentUser, isNull);
|
||||
try {
|
||||
await client.openConnection();
|
||||
} catch (e) {
|
||||
@@ -164,7 +164,7 @@ void main() {
|
||||
});
|
||||
|
||||
test('should throw if connection is already in progress', () async {
|
||||
expect(client.state.user, isNull);
|
||||
expect(client.state.currentUser, isNull);
|
||||
try {
|
||||
await client.connectAnonymousUser();
|
||||
await client.openConnection();
|
||||
@@ -179,7 +179,7 @@ void main() {
|
||||
});
|
||||
|
||||
test('should throw if connection is already available', () async {
|
||||
expect(client.state.user, isNull);
|
||||
expect(client.state.currentUser, isNull);
|
||||
try {
|
||||
await client.connectAnonymousUser();
|
||||
// waiting 300ms for `wsConnectionStatusStream` to emit
|
||||
@@ -799,7 +799,7 @@ void main() {
|
||||
});
|
||||
|
||||
test('`.disconnectUser` should reset state and user', () async {
|
||||
expect(client.state.user, isNotNull);
|
||||
expect(client.state.currentUser, isNotNull);
|
||||
expect(client.wsConnectionStatus, ConnectionStatus.connected);
|
||||
|
||||
expectLater(
|
||||
@@ -810,7 +810,7 @@ void main() {
|
||||
|
||||
await client.disconnectUser();
|
||||
|
||||
expect(client.state.user, isNull);
|
||||
expect(client.state.currentUser, isNull);
|
||||
expect(client.wsConnectionStatus, ConnectionStatus.disconnected);
|
||||
});
|
||||
});
|
||||
@@ -2109,9 +2109,11 @@ void main() {
|
||||
const messageId = 'test-message-id';
|
||||
const language = 'hi'; // Hindi
|
||||
const translatedMessageText = 'नमस्ते';
|
||||
final translatedMessage = TranslatedMessage(const {
|
||||
language: translatedMessageText,
|
||||
});
|
||||
final translatedMessage = Message(
|
||||
i18n: const {
|
||||
language: translatedMessageText,
|
||||
},
|
||||
);
|
||||
|
||||
when(() => api.message.translateMessage(messageId, language)).thenAnswer(
|
||||
(_) async => TranslateMessageResponse()..message = translatedMessage,
|
||||
|
||||
@@ -371,9 +371,11 @@ void main() {
|
||||
final path = '/messages/${message.id}/translate';
|
||||
|
||||
const translatedMessageText = 'नमस्ते';
|
||||
final translatedMessage = TranslatedMessage(const {
|
||||
language: translatedMessageText,
|
||||
});
|
||||
final translatedMessage = Message(
|
||||
i18n: const {
|
||||
language: translatedMessageText,
|
||||
},
|
||||
);
|
||||
|
||||
when(() => client.post(
|
||||
path,
|
||||
|
||||
@@ -9,6 +9,8 @@ void main() {
|
||||
expect(options.baseUrl, 'https://chat-us-east-1.stream-io-api.com');
|
||||
expect(options.connectTimeout, const Duration(seconds: 6));
|
||||
expect(options.receiveTimeout, const Duration(seconds: 6));
|
||||
expect(options.queryParameters, const {});
|
||||
expect(options.headers, const {});
|
||||
});
|
||||
|
||||
test('should override all the default set params', () {
|
||||
@@ -16,11 +18,15 @@ void main() {
|
||||
baseUrl: 'base-url',
|
||||
connectTimeout: Duration(seconds: 3),
|
||||
receiveTimeout: Duration(seconds: 3),
|
||||
headers: {'test': 'test'},
|
||||
queryParameters: {'123': '123'},
|
||||
);
|
||||
expect(options.location, isNull);
|
||||
expect(options.baseUrl, 'base-url');
|
||||
expect(options.connectTimeout, const Duration(seconds: 3));
|
||||
expect(options.receiveTimeout, const Duration(seconds: 3));
|
||||
expect(options.headers, {'test': 'test'});
|
||||
expect(options.queryParameters, {'123': '123'});
|
||||
});
|
||||
|
||||
group('should create baseUrl according to provided location', () {
|
||||
|
||||
@@ -96,7 +96,7 @@ void main() {
|
||||
await client.get('path');
|
||||
} catch (_) {}
|
||||
|
||||
verify(() => logger.info(any())).called(16);
|
||||
verify(() => logger.info(any())).called(greaterThan(0));
|
||||
});
|
||||
|
||||
test('loggingInterceptor should log error', () async {
|
||||
@@ -108,7 +108,7 @@ void main() {
|
||||
await client.get('path');
|
||||
} catch (_) {}
|
||||
|
||||
verify(() => logger.severe(any())).called(8);
|
||||
verify(() => logger.severe(any())).called(greaterThan(0));
|
||||
});
|
||||
|
||||
test('`.lock` should lock the dio client', () async {
|
||||
|
||||
@@ -28,6 +28,7 @@ void main() {
|
||||
expect(message.pinnedAt, null);
|
||||
expect(message.pinExpires, null);
|
||||
expect(message.pinnedBy, null);
|
||||
expect(message.i18n, null);
|
||||
});
|
||||
|
||||
test('should serialize to json correctly', () {
|
||||
|
||||
@@ -89,7 +89,7 @@ class FakeChatApi extends Fake implements StreamChatApi {
|
||||
|
||||
class FakeClientState extends Fake implements ClientState {
|
||||
@override
|
||||
OwnUser? get user => OwnUser(id: 'test-user-id');
|
||||
OwnUser? get currentUser => OwnUser(id: 'test-user-id');
|
||||
|
||||
@override
|
||||
int totalUnreadCount = 0;
|
||||
|
||||
Reference in New Issue
Block a user