Merge branch 'v4' into feat/stream-channel-listview
This commit is contained in:
@@ -1,28 +1,47 @@
|
|||||||
## Upcoming
|
## 3.5.0
|
||||||
|
|
||||||
|
✅ Added
|
||||||
|
|
||||||
|
- You can now pass `score` to `client.sendReaction` and `channel.sendReaction` functions.
|
||||||
|
- Added new `client.partialUpdateUsers` function in order to partially update users.
|
||||||
|
|
||||||
|
🐞 Fixed
|
||||||
|
|
||||||
|
- [[#890]](https://github.com/GetStream/stream-chat-flutter/pull/890) Fixed Reactions not updating on thread messages.
|
||||||
|
Thanks [bstolinski](https://github.com/bstolinski).
|
||||||
|
- [[#897]](https://github.com/GetStream/stream-chat-flutter/issues/897) Fixed error type mis-match in `AuthInterceptor`.
|
||||||
|
- [[#891]](https://github.com/GetStream/stream-chat-flutter/pull/891) Fixed reply counter for parent message not
|
||||||
|
updating correctly after deleting thread message.
|
||||||
|
- Fix `channelState.copyWith` with respect to pinnedMessages.
|
||||||
|
|
||||||
|
## 3.4.0
|
||||||
|
|
||||||
🐞 Fixed
|
🐞 Fixed
|
||||||
|
|
||||||
- [[#857]](https://github.com/GetStream/stream-chat-flutter/issues/857) Channel now listens for member ban/unban and
|
- [[#857]](https://github.com/GetStream/stream-chat-flutter/issues/857) Channel now listens for member ban/unban and
|
||||||
updates the channel state with the latest data.
|
updates the channel state with the latest data.
|
||||||
- [[#748]](https://github.com/GetStream/stream-chat-flutter/issues/748) `Message.user` are now also included while saving users in persistence.
|
- [[#748]](https://github.com/GetStream/stream-chat-flutter/issues/748) `Message.user` is now also included while saving
|
||||||
|
users in persistence.
|
||||||
|
- [[#871]](https://github.com/GetStream/stream-chat-flutter/issues/871) Fixed thread message deletion.
|
||||||
|
- [[#846]](https://github.com/GetStream/stream-chat-flutter/issues/846) Fixed `message.ownReactions` getting truncated
|
||||||
|
when receiving a reaction event.
|
||||||
|
- Add check for invalid image URLs
|
||||||
|
- Fix `channelState.pinnedMessagesStream` getting reset to `0` after a channel update.
|
||||||
|
- Fixed `unreadCount` after removing user from a channel.
|
||||||
|
|
||||||
🔄 Changed
|
🔄 Changed
|
||||||
|
|
||||||
- `client.location` is now deprecated in favor of the
|
- `client.location` is now deprecated in favor of the
|
||||||
new [edge server](https://getstream.io/blog/chat-edge-infrastructure) and will be removed in v4.0.0.
|
new [edge server](https://getstream.io/blog/chat-edge-infrastructure) and will be removed in v4.0.0.
|
||||||
- `channel.banUser`, `channel.unbanUser` is now deprecated in favor of the new `channel.banMember`
|
- `channel.banUser`, `channel.unbanUser` is now deprecated in favor of the new `channel.banMember`
|
||||||
and `channel.unbanMember` and will be removed in v4.0.0.
|
and `channel.unbanMember`. These deprecated methods will be removed in v4.0.0.
|
||||||
|
- Added `banExpires` property of type `DateTime` on the `Member`, `OwnUser`, and `User` models.
|
||||||
|
|
||||||
✅ Added
|
✅ Added
|
||||||
|
|
||||||
- Added `client.enrichUrl` endpoint for enriching URLs with metadata.
|
- Added `client.enrichUrl` endpoint for enriching URLs with metadata.
|
||||||
- Fixed `unreadCount` after removing user from a channel.
|
|
||||||
- Added `client.queryBannedUsers`, `channel.queryBannedUsers` endpoint for querying banned users.
|
- Added `client.queryBannedUsers`, `channel.queryBannedUsers` endpoint for querying banned users.
|
||||||
|
|
||||||
🐞 Fixed
|
|
||||||
|
|
||||||
- [[#871]](https://github.com/GetStream/stream-chat-flutter/issues/871) Fixed thread message deletion.
|
|
||||||
|
|
||||||
## 3.3.1
|
## 3.3.1
|
||||||
|
|
||||||
🐞 Fixed
|
🐞 Fixed
|
||||||
@@ -45,6 +64,12 @@
|
|||||||
- Fixed user presence indicator not updating correctly.
|
- Fixed user presence indicator not updating correctly.
|
||||||
- `ChannelEvent.membersCount` defaults to 0 avoiding parsing errors due to missing `members_count` field.
|
- `ChannelEvent.membersCount` defaults to 0 avoiding parsing errors due to missing `members_count` field.
|
||||||
|
|
||||||
|
## 3.2.1
|
||||||
|
|
||||||
|
🐞 Fixed
|
||||||
|
|
||||||
|
- Fixed `StreamChatClient.markAllRead` api call
|
||||||
|
|
||||||
## 3.2.0
|
## 3.2.0
|
||||||
|
|
||||||
🐞 Fixed
|
🐞 Fixed
|
||||||
|
|||||||
@@ -419,7 +419,7 @@ class Channel {
|
|||||||
if (index != -1) {
|
if (index != -1) {
|
||||||
final newAttachments = [...message!.attachments]..[index] = attachment;
|
final newAttachments = [...message!.attachments]..[index] = attachment;
|
||||||
final updatedMessage = message!.copyWith(attachments: newAttachments);
|
final updatedMessage = message!.copyWith(attachments: newAttachments);
|
||||||
state?.addMessage(updatedMessage);
|
state?.updateMessage(updatedMessage);
|
||||||
// updating original message for next iteration
|
// updating original message for next iteration
|
||||||
message = message!.merge(updatedMessage);
|
message = message!.merge(updatedMessage);
|
||||||
}
|
}
|
||||||
@@ -525,7 +525,7 @@ class Channel {
|
|||||||
).toList(),
|
).toList(),
|
||||||
);
|
);
|
||||||
|
|
||||||
state!.addMessage(message);
|
state!.updateMessage(message);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (message.attachments.any((it) => !it.uploadState.isSuccess)) {
|
if (message.attachments.any((it) => !it.uploadState.isSuccess)) {
|
||||||
@@ -549,7 +549,7 @@ class Channel {
|
|||||||
skipPush: skipPush,
|
skipPush: skipPush,
|
||||||
skipEnrichUrl: skipEnrichUrl,
|
skipEnrichUrl: skipEnrichUrl,
|
||||||
);
|
);
|
||||||
state!.addMessage(response.message);
|
state!.updateMessage(response.message);
|
||||||
if (cooldown > 0) cooldownStartedAt = DateTime.now();
|
if (cooldown > 0) cooldownStartedAt = DateTime.now();
|
||||||
return response;
|
return response;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -588,7 +588,7 @@ class Channel {
|
|||||||
).toList(),
|
).toList(),
|
||||||
);
|
);
|
||||||
|
|
||||||
state?.addMessage(message);
|
state?.updateMessage(message);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (message.attachments.any((it) => !it.uploadState.isSuccess)) {
|
if (message.attachments.any((it) => !it.uploadState.isSuccess)) {
|
||||||
@@ -614,7 +614,7 @@ class Channel {
|
|||||||
ownReactions: message.ownReactions,
|
ownReactions: message.ownReactions,
|
||||||
);
|
);
|
||||||
|
|
||||||
state?.addMessage(m);
|
state?.updateMessage(m);
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -622,7 +622,7 @@ class Channel {
|
|||||||
if (e.isRetriable) {
|
if (e.isRetriable) {
|
||||||
state!._retryQueue.add([message]);
|
state!._retryQueue.add([message]);
|
||||||
} else {
|
} else {
|
||||||
state?.addMessage(originalMessage);
|
state?.updateMessage(originalMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rethrow;
|
rethrow;
|
||||||
@@ -652,7 +652,7 @@ class Channel {
|
|||||||
ownReactions: message.ownReactions,
|
ownReactions: message.ownReactions,
|
||||||
);
|
);
|
||||||
|
|
||||||
state?.addMessage(updatedMessage);
|
state?.updateMessage(updatedMessage);
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -665,13 +665,18 @@ class Channel {
|
|||||||
|
|
||||||
/// Deletes the [message] from the channel.
|
/// Deletes the [message] from the channel.
|
||||||
Future<EmptyResponse> deleteMessage(Message message, {bool? hard}) async {
|
Future<EmptyResponse> deleteMessage(Message message, {bool? hard}) async {
|
||||||
|
final hardDelete = hard ?? false;
|
||||||
|
|
||||||
// Directly deleting the local messages which are not yet sent to server
|
// Directly deleting the local messages which are not yet sent to server
|
||||||
if (message.status == MessageSendingStatus.sending ||
|
if (message.status == MessageSendingStatus.sending ||
|
||||||
message.status == MessageSendingStatus.failed) {
|
message.status == MessageSendingStatus.failed) {
|
||||||
state!.addMessage(message.copyWith(
|
state!.deleteMessage(
|
||||||
type: 'deleted',
|
message.copyWith(
|
||||||
status: MessageSendingStatus.sent,
|
type: 'deleted',
|
||||||
));
|
status: MessageSendingStatus.sent,
|
||||||
|
),
|
||||||
|
hardDelete: hardDelete,
|
||||||
|
);
|
||||||
|
|
||||||
// Removing the attachments upload completer to stop the `sendMessage`
|
// Removing the attachments upload completer to stop the `sendMessage`
|
||||||
// waiting for attachments to complete.
|
// waiting for attachments to complete.
|
||||||
@@ -689,11 +694,14 @@ class Channel {
|
|||||||
deletedAt: message.deletedAt ?? DateTime.now(),
|
deletedAt: message.deletedAt ?? DateTime.now(),
|
||||||
);
|
);
|
||||||
|
|
||||||
state?.addMessage(message);
|
state?.deleteMessage(message, hardDelete: hardDelete);
|
||||||
|
|
||||||
final response = await _client.deleteMessage(message.id, hard: hard);
|
final response = await _client.deleteMessage(message.id, hard: hard);
|
||||||
|
|
||||||
state?.addMessage(message.copyWith(status: MessageSendingStatus.sent));
|
state?.deleteMessage(
|
||||||
|
message.copyWith(status: MessageSendingStatus.sent),
|
||||||
|
hardDelete: hardDelete,
|
||||||
|
);
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -833,6 +841,7 @@ class Channel {
|
|||||||
Future<SendReactionResponse> sendReaction(
|
Future<SendReactionResponse> sendReaction(
|
||||||
Message message,
|
Message message,
|
||||||
String type, {
|
String type, {
|
||||||
|
int score = 1,
|
||||||
Map<String, Object?> extraData = const {},
|
Map<String, Object?> extraData = const {},
|
||||||
bool enforceUnique = false,
|
bool enforceUnique = false,
|
||||||
}) async {
|
}) async {
|
||||||
@@ -841,7 +850,7 @@ class Channel {
|
|||||||
final now = DateTime.now();
|
final now = DateTime.now();
|
||||||
final user = _client.state.currentUser;
|
final user = _client.state.currentUser;
|
||||||
|
|
||||||
final latestReactions = [...message.latestReactions ?? <Reaction>[]];
|
var latestReactions = [...message.latestReactions ?? <Reaction>[]];
|
||||||
if (enforceUnique) {
|
if (enforceUnique) {
|
||||||
latestReactions.removeWhere((it) => it.userId == user!.id);
|
latestReactions.removeWhere((it) => it.userId == user!.id);
|
||||||
}
|
}
|
||||||
@@ -851,14 +860,21 @@ class Channel {
|
|||||||
createdAt: now,
|
createdAt: now,
|
||||||
type: type,
|
type: type,
|
||||||
user: user,
|
user: user,
|
||||||
score: 1,
|
score: score,
|
||||||
extraData: extraData,
|
extraData: extraData,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Inserting at the 0th index as it's the latest reaction
|
latestReactions = (latestReactions
|
||||||
latestReactions.insert(0, newReaction);
|
// Inserting at the 0th index as it's the latest reaction
|
||||||
final ownReactions = [...latestReactions]
|
..insert(0, newReaction))
|
||||||
..removeWhere((it) => it.userId != user!.id);
|
.take(10)
|
||||||
|
.toList();
|
||||||
|
final ownReactions = enforceUnique
|
||||||
|
? <Reaction>[newReaction]
|
||||||
|
: <Reaction>[
|
||||||
|
...message.ownReactions ?? [],
|
||||||
|
newReaction,
|
||||||
|
];
|
||||||
|
|
||||||
final newMessage = message.copyWith(
|
final newMessage = message.copyWith(
|
||||||
reactionCounts: {...message.reactionCounts ?? <String, int>{}}
|
reactionCounts: {...message.reactionCounts ?? <String, int>{}}
|
||||||
@@ -875,19 +891,20 @@ class Channel {
|
|||||||
ownReactions: ownReactions,
|
ownReactions: ownReactions,
|
||||||
);
|
);
|
||||||
|
|
||||||
state?.addMessage(newMessage);
|
state?.updateMessage(newMessage);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final reactionResp = await _client.sendReaction(
|
final reactionResp = await _client.sendReaction(
|
||||||
messageId,
|
messageId,
|
||||||
type,
|
type,
|
||||||
|
score: score,
|
||||||
extraData: extraData,
|
extraData: extraData,
|
||||||
enforceUnique: enforceUnique,
|
enforceUnique: enforceUnique,
|
||||||
);
|
);
|
||||||
return reactionResp;
|
return reactionResp;
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
// Reset the message if the update fails
|
// Reset the message if the update fails
|
||||||
state?.addMessage(message);
|
state?.updateMessage(message);
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -898,7 +915,6 @@ class Channel {
|
|||||||
Reaction reaction,
|
Reaction reaction,
|
||||||
) async {
|
) async {
|
||||||
final type = reaction.type;
|
final type = reaction.type;
|
||||||
final user = _client.state.currentUser;
|
|
||||||
|
|
||||||
final reactionCounts = {...message.reactionCounts ?? <String, int>{}};
|
final reactionCounts = {...message.reactionCounts ?? <String, int>{}};
|
||||||
if (reactionCounts.containsKey(type)) {
|
if (reactionCounts.containsKey(type)) {
|
||||||
@@ -915,8 +931,11 @@ class Channel {
|
|||||||
r.type == reaction.type &&
|
r.type == reaction.type &&
|
||||||
r.messageId == reaction.messageId);
|
r.messageId == reaction.messageId);
|
||||||
|
|
||||||
final ownReactions = [...latestReactions]
|
final ownReactions = message.ownReactions
|
||||||
..removeWhere((it) => it.userId != user!.id);
|
?..removeWhere((r) =>
|
||||||
|
r.userId == reaction.userId &&
|
||||||
|
r.type == reaction.type &&
|
||||||
|
r.messageId == reaction.messageId);
|
||||||
|
|
||||||
final newMessage = message.copyWith(
|
final newMessage = message.copyWith(
|
||||||
reactionCounts: reactionCounts..removeWhere((_, value) => value == 0),
|
reactionCounts: reactionCounts..removeWhere((_, value) => value == 0),
|
||||||
@@ -925,7 +944,7 @@ class Channel {
|
|||||||
ownReactions: ownReactions,
|
ownReactions: ownReactions,
|
||||||
);
|
);
|
||||||
|
|
||||||
state?.addMessage(newMessage);
|
state?.updateMessage(newMessage);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final deleteResponse = await _client.deleteReaction(
|
final deleteResponse = await _client.deleteReaction(
|
||||||
@@ -935,7 +954,7 @@ class Channel {
|
|||||||
return deleteResponse;
|
return deleteResponse;
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
// Reset the message if the update fails
|
// Reset the message if the update fails
|
||||||
state?.addMessage(message);
|
state?.updateMessage(message);
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1092,7 +1111,7 @@ class Channel {
|
|||||||
|
|
||||||
// update the passed message with response message
|
// update the passed message with response message
|
||||||
if (res.message != null) {
|
if (res.message != null) {
|
||||||
state!.addMessage(res.message!);
|
state!.updateMessage(res.message!);
|
||||||
} else {
|
} else {
|
||||||
// remove the passed message if response does
|
// remove the passed message if response does
|
||||||
// not contain message
|
// not contain message
|
||||||
@@ -1312,7 +1331,7 @@ class Channel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Bans the user with given [userID] from the channel.
|
/// Bans the user with given [userID] from the channel.
|
||||||
@Deprecated("Use 'banMember' instead")
|
@Deprecated("Use 'banMember' instead. This method will be removed in v4.0.0")
|
||||||
Future<EmptyResponse> banUser(
|
Future<EmptyResponse> banUser(
|
||||||
String userID,
|
String userID,
|
||||||
Map<String, dynamic> options,
|
Map<String, dynamic> options,
|
||||||
@@ -1334,7 +1353,9 @@ class Channel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Remove the ban for the user with given [userID] in the channel.
|
/// Remove the ban for the user with given [userID] in the channel.
|
||||||
@Deprecated("Use 'unbanMember' instead")
|
@Deprecated(
|
||||||
|
"Use 'unbanMember' instead. This method will be removed in v4.0.0",
|
||||||
|
)
|
||||||
Future<EmptyResponse> unbanUser(String userID) => unbanMember(userID);
|
Future<EmptyResponse> unbanUser(String userID) => unbanMember(userID);
|
||||||
|
|
||||||
/// Remove the ban for the member with given [userID] in the channel.
|
/// Remove the ban for the member with given [userID] in the channel.
|
||||||
@@ -1545,16 +1566,21 @@ class ChannelClientState {
|
|||||||
if (url == null || !url.contains('')) {
|
if (url == null || !url.contains('')) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
final uri = Uri.parse(url);
|
try {
|
||||||
if (!uri.host.endsWith('stream-io-cdn.com') ||
|
final uri = Uri.parse(url);
|
||||||
uri.queryParameters['Expires'] == null) {
|
if (!uri.host.endsWith('stream-io-cdn.com') ||
|
||||||
|
uri.queryParameters['Expires'] == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final secondsFromEpoch =
|
||||||
|
int.parse(uri.queryParameters['Expires']!);
|
||||||
|
final expiration = DateTime.fromMillisecondsSinceEpoch(
|
||||||
|
secondsFromEpoch * 1000,
|
||||||
|
);
|
||||||
|
return expiration.isBefore(DateTime.now());
|
||||||
|
} catch (_) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
final secondsFromEpoch =
|
|
||||||
int.parse(uri.queryParameters['Expires']!);
|
|
||||||
final expiration =
|
|
||||||
DateTime.fromMillisecondsSinceEpoch(secondsFromEpoch * 1000);
|
|
||||||
return expiration.isBefore(DateTime.now());
|
|
||||||
}))
|
}))
|
||||||
.map((e) => e.id)
|
.map((e) => e.id)
|
||||||
.toList();
|
.toList();
|
||||||
@@ -1702,23 +1728,36 @@ class ChannelClientState {
|
|||||||
|
|
||||||
void _listenReactionDeleted() {
|
void _listenReactionDeleted() {
|
||||||
_subscriptions.add(_channel.on(EventType.reactionDeleted).listen((event) {
|
_subscriptions.add(_channel.on(EventType.reactionDeleted).listen((event) {
|
||||||
final userId = _channel.client.state.currentUser!.id;
|
final oldMessage =
|
||||||
|
messages.firstWhereOrNull((it) => it.id == event.message?.id) ??
|
||||||
|
threads[event.message?.parentId]
|
||||||
|
?.firstWhereOrNull((e) => e.id == event.message?.id);
|
||||||
|
final reaction = event.reaction;
|
||||||
|
final ownReactions = oldMessage?.ownReactions
|
||||||
|
?.whereNot((it) =>
|
||||||
|
it.type == reaction?.type &&
|
||||||
|
it.score == reaction?.score &&
|
||||||
|
it.messageId == reaction?.messageId &&
|
||||||
|
it.userId == reaction?.userId &&
|
||||||
|
it.extraData == reaction?.extraData)
|
||||||
|
.toList(growable: false);
|
||||||
final message = event.message!.copyWith(
|
final message = event.message!.copyWith(
|
||||||
ownReactions: [...event.message!.latestReactions!]
|
ownReactions: ownReactions,
|
||||||
..removeWhere((it) => it.userId != userId),
|
|
||||||
);
|
);
|
||||||
addMessage(message);
|
updateMessage(message);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
void _listenReactions() {
|
void _listenReactions() {
|
||||||
_subscriptions.add(_channel.on(EventType.reactionNew).listen((event) {
|
_subscriptions.add(_channel.on(EventType.reactionNew).listen((event) {
|
||||||
final userId = _channel.client.state.currentUser!.id;
|
final oldMessage =
|
||||||
|
messages.firstWhereOrNull((it) => it.id == event.message?.id) ??
|
||||||
|
threads[event.message?.parentId]
|
||||||
|
?.firstWhereOrNull((e) => e.id == event.message?.id);
|
||||||
final message = event.message!.copyWith(
|
final message = event.message!.copyWith(
|
||||||
ownReactions: [...event.message!.latestReactions!]
|
ownReactions: oldMessage?.ownReactions,
|
||||||
..removeWhere((it) => it.userId != userId),
|
|
||||||
);
|
);
|
||||||
addMessage(message);
|
updateMessage(message);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1729,12 +1768,14 @@ class ChannelClientState {
|
|||||||
EventType.reactionUpdated,
|
EventType.reactionUpdated,
|
||||||
)
|
)
|
||||||
.listen((event) {
|
.listen((event) {
|
||||||
final userId = _channel.client.state.currentUser!.id;
|
final oldMessage =
|
||||||
|
messages.firstWhereOrNull((it) => it.id == event.message?.id) ??
|
||||||
|
threads[event.message?.parentId]
|
||||||
|
?.firstWhereOrNull((e) => e.id == event.message?.id);
|
||||||
final message = event.message!.copyWith(
|
final message = event.message!.copyWith(
|
||||||
ownReactions: [...event.message!.latestReactions!]
|
ownReactions: oldMessage?.ownReactions,
|
||||||
..removeWhere((it) => it.userId != userId),
|
|
||||||
);
|
);
|
||||||
addMessage(message);
|
updateMessage(message);
|
||||||
|
|
||||||
if (message.pinned) {
|
if (message.pinned) {
|
||||||
_channelState = _channelState.copyWith(
|
_channelState = _channelState.copyWith(
|
||||||
@@ -1751,9 +1792,9 @@ class ChannelClientState {
|
|||||||
_subscriptions.add(_channel.on(EventType.messageDeleted).listen((event) {
|
_subscriptions.add(_channel.on(EventType.messageDeleted).listen((event) {
|
||||||
final message = event.message!;
|
final message = event.message!;
|
||||||
if (event.hardDelete == true) {
|
if (event.hardDelete == true) {
|
||||||
removeMessage(message, hardDelete: true);
|
removeMessage(message);
|
||||||
} else {
|
} else {
|
||||||
addMessage(message);
|
updateMessage(message);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@@ -1768,7 +1809,7 @@ class ChannelClientState {
|
|||||||
final message = event.message!;
|
final message = event.message!;
|
||||||
if (isUpToDate ||
|
if (isUpToDate ||
|
||||||
(message.parentId != null && message.showInChannel != true)) {
|
(message.parentId != null && message.showInChannel != true)) {
|
||||||
addMessage(message);
|
updateMessage(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_countMessageAsUnread(message)) {
|
if (_countMessageAsUnread(message)) {
|
||||||
@@ -1778,9 +1819,13 @@ class ChannelClientState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Add a [message] to this [channelState].
|
/// Add a [message] to this [channelState].
|
||||||
void addMessage(Message message) {
|
@Deprecated('Use updateMessage instead')
|
||||||
|
void addMessage(Message message) => updateMessage(message);
|
||||||
|
|
||||||
|
/// Updates the [message] in the state if it exists. Adds it otherwise.
|
||||||
|
void updateMessage(Message message) {
|
||||||
if (message.parentId == null || message.showInChannel == true) {
|
if (message.parentId == null || message.showInChannel == true) {
|
||||||
final newMessages = List<Message>.from(_channelState.messages);
|
final newMessages = [...messages];
|
||||||
final oldIndex = newMessages.indexWhere((m) => m.id == message.id);
|
final oldIndex = newMessages.indexWhere((m) => m.id == message.id);
|
||||||
if (oldIndex != -1) {
|
if (oldIndex != -1) {
|
||||||
Message? m;
|
Message? m;
|
||||||
@@ -1795,8 +1840,24 @@ class ChannelClientState {
|
|||||||
newMessages.add(message);
|
newMessages.add(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final newPinnedMessages = [...pinnedMessages];
|
||||||
|
final oldPinnedIndex =
|
||||||
|
newPinnedMessages.indexWhere((m) => m.id == message.id);
|
||||||
|
|
||||||
|
// Handle pinned messages
|
||||||
|
if (message.pinned) {
|
||||||
|
if (oldPinnedIndex != -1) {
|
||||||
|
newPinnedMessages[oldPinnedIndex] = message;
|
||||||
|
} else {
|
||||||
|
newPinnedMessages.add(message);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
newPinnedMessages.removeWhere((m) => m.id == message.id);
|
||||||
|
}
|
||||||
|
|
||||||
_channelState = _channelState.copyWith(
|
_channelState = _channelState.copyWith(
|
||||||
messages: newMessages..sort(_sortByCreatedAt),
|
messages: newMessages..sort(_sortByCreatedAt),
|
||||||
|
pinnedMessages: newPinnedMessages,
|
||||||
channel: _channelState.channel?.copyWith(
|
channel: _channelState.channel?.copyWith(
|
||||||
lastMessageAt: message.createdAt,
|
lastMessageAt: message.createdAt,
|
||||||
),
|
),
|
||||||
@@ -1809,41 +1870,35 @@ class ChannelClientState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Remove a [message] from this [channelState].
|
/// Remove a [message] from this [channelState].
|
||||||
void removeMessage(Message message, {bool hardDelete = false}) {
|
void removeMessage(Message message) {
|
||||||
final parentId = message.parentId;
|
final parentId = message.parentId;
|
||||||
// i.e. it's a thread message
|
// i.e. it's a thread message, Remove it
|
||||||
// 1. Remove the thread message
|
|
||||||
// 2. Reduce total reply count of parent message
|
|
||||||
if (parentId != null) {
|
if (parentId != null) {
|
||||||
final allMessages = [...messages];
|
final newThreads = {...threads};
|
||||||
final parentMessage = allMessages.firstWhereOrNull(
|
// Early return in case the thread is not available
|
||||||
(it) => it.id == parentId,
|
if (!newThreads.containsKey(parentId)) return;
|
||||||
);
|
|
||||||
|
|
||||||
// return if message not available in the memory
|
_threads = newThreads
|
||||||
if (parentMessage == null) return;
|
..update(
|
||||||
final replyCount = parentMessage.replyCount;
|
parentId,
|
||||||
// return if reply count is null or zero
|
(messages) => messages..removeWhere((e) => e.id == message.id),
|
||||||
if (replyCount == null || replyCount == 0) return;
|
);
|
||||||
|
|
||||||
addMessage(parentMessage.copyWith(replyCount: replyCount - 1));
|
// Early return if the thread message is not shown in channel.
|
||||||
updateThreadInfo(
|
if (message.showInChannel == false) return;
|
||||||
parentId,
|
|
||||||
threads[parentId]!
|
|
||||||
..removeWhere(
|
|
||||||
(e) => e.id == message.id,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// Remove regular message
|
|
||||||
final allMessages = [...messages];
|
|
||||||
if (hardDelete) {
|
|
||||||
allMessages.removeWhere((e) => e.id == message.id);
|
|
||||||
_channelState = _channelState.copyWith(messages: allMessages);
|
|
||||||
} else if (allMessages.remove(message)) {
|
|
||||||
_channelState = _channelState.copyWith(messages: allMessages);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove regular message, thread message shown in channel
|
||||||
|
final allMessages = [...messages];
|
||||||
|
_channelState = _channelState.copyWith(
|
||||||
|
messages: allMessages..removeWhere((e) => e.id == message.id),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes/Updates the [message] based on the [hardDelete] value.
|
||||||
|
void deleteMessage(Message message, {bool hardDelete = false}) {
|
||||||
|
if (hardDelete) return removeMessage(message);
|
||||||
|
return updateMessage(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _listenReadEvents() {
|
void _listenReadEvents() {
|
||||||
@@ -1889,11 +1944,12 @@ class ChannelClientState {
|
|||||||
.distinct(const ListEquality().equals);
|
.distinct(const ListEquality().equals);
|
||||||
|
|
||||||
/// Channel pinned message list.
|
/// Channel pinned message list.
|
||||||
List<Message> get pinnedMessages => _channelState.pinnedMessages.toList();
|
List<Message> get pinnedMessages => _channelState.pinnedMessages;
|
||||||
|
|
||||||
/// Channel pinned message list as a stream.
|
/// Channel pinned message list as a stream.
|
||||||
Stream<List<Message>> get pinnedMessagesStream =>
|
Stream<List<Message>> get pinnedMessagesStream => channelStateStream
|
||||||
channelStateStream.map((cs) => cs.pinnedMessages.toList());
|
.map((cs) => cs.pinnedMessages)
|
||||||
|
.distinct(const ListEquality().equals);
|
||||||
|
|
||||||
/// Get channel last message.
|
/// Get channel last message.
|
||||||
Message? get lastMessage =>
|
Message? get lastMessage =>
|
||||||
@@ -2212,7 +2268,7 @@ class ChannelClientState {
|
|||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
updateChannelState(_channelState.copyWith(
|
updateChannelState(_channelState.copyWith(
|
||||||
pinnedMessages: pinnedMessages.where(_pinIsValid()).toList(),
|
pinnedMessages: pinnedMessages.where(_pinIsValid).toList(),
|
||||||
messages: expiredMessages,
|
messages: expiredMessages,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@@ -2249,7 +2305,7 @@ class ChannelClientState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Function(Message) _pinIsValid() {
|
bool _pinIsValid(Message message) {
|
||||||
final now = DateTime.now();
|
final now = DateTime.now();
|
||||||
return (Message m) => m.pinExpires!.isAfter(now);
|
return message.pinExpires!.isAfter(now);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1073,6 +1073,28 @@ class StreamChatClient {
|
|||||||
Future<UpdateUsersResponse> updateUsers(List<User> users) =>
|
Future<UpdateUsersResponse> updateUsers(List<User> users) =>
|
||||||
_chatApi.user.updateUsers(users);
|
_chatApi.user.updateUsers(users);
|
||||||
|
|
||||||
|
/// Partially update the given user with [id].
|
||||||
|
/// Use [set] to define values to be set.
|
||||||
|
/// Use [unset] to define values to be unset.
|
||||||
|
Future<UpdateUsersResponse> partialUpdateUser(
|
||||||
|
String id, {
|
||||||
|
Map<String, Object?>? set,
|
||||||
|
List<String>? unset,
|
||||||
|
}) {
|
||||||
|
final user = PartialUpdateUserRequest(
|
||||||
|
id: id,
|
||||||
|
set: set,
|
||||||
|
unset: unset,
|
||||||
|
);
|
||||||
|
return partialUpdateUsers([user]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Batch partial updates the [users].
|
||||||
|
Future<UpdateUsersResponse> partialUpdateUsers(
|
||||||
|
List<PartialUpdateUserRequest> users,
|
||||||
|
) =>
|
||||||
|
_chatApi.user.partialUpdateUsers(users);
|
||||||
|
|
||||||
/// Bans a user from all channels
|
/// Bans a user from all channels
|
||||||
Future<EmptyResponse> banUser(
|
Future<EmptyResponse> banUser(
|
||||||
String targetUserId, [
|
String targetUserId, [
|
||||||
@@ -1157,15 +1179,22 @@ class StreamChatClient {
|
|||||||
Future<SendReactionResponse> sendReaction(
|
Future<SendReactionResponse> sendReaction(
|
||||||
String messageId,
|
String messageId,
|
||||||
String reactionType, {
|
String reactionType, {
|
||||||
|
int score = 1,
|
||||||
Map<String, Object?> extraData = const {},
|
Map<String, Object?> extraData = const {},
|
||||||
bool enforceUnique = false,
|
bool enforceUnique = false,
|
||||||
}) =>
|
}) {
|
||||||
_chatApi.message.sendReaction(
|
final _extraData = {
|
||||||
messageId,
|
'score': score,
|
||||||
reactionType,
|
...extraData,
|
||||||
extraData: extraData,
|
};
|
||||||
enforceUnique: enforceUnique,
|
|
||||||
);
|
return _chatApi.message.sendReaction(
|
||||||
|
messageId,
|
||||||
|
reactionType,
|
||||||
|
extraData: _extraData,
|
||||||
|
enforceUnique: enforceUnique,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// Delete a [reactionType] from this [messageId]
|
/// Delete a [reactionType] from this [messageId]
|
||||||
Future<EmptyResponse> deleteReaction(
|
Future<EmptyResponse> deleteReaction(
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ class RetryQueue {
|
|||||||
: message.status == MessageSendingStatus.updating
|
: message.status == MessageSendingStatus.updating
|
||||||
? MessageSendingStatus.failed_update
|
? MessageSendingStatus.failed_update
|
||||||
: MessageSendingStatus.failed_delete;
|
: MessageSendingStatus.failed_delete;
|
||||||
channel.state?.addMessage(message.copyWith(status: newStatus));
|
channel.state?.updateMessage(message.copyWith(status: newStatus));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _retryMessage(Message message) async {
|
Future<void> _retryMessage(Message message) async {
|
||||||
|
|||||||
@@ -84,7 +84,10 @@ class ChannelApi {
|
|||||||
|
|
||||||
/// Mark all channels for this user as read
|
/// Mark all channels for this user as read
|
||||||
Future<EmptyResponse> markAllRead() async {
|
Future<EmptyResponse> markAllRead() async {
|
||||||
final response = await _client.post('/channels/read');
|
final response = await _client.post(
|
||||||
|
'/channels/read',
|
||||||
|
data: {},
|
||||||
|
);
|
||||||
return EmptyResponse.fromJson(response.data);
|
return EmptyResponse.fromJson(response.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -156,3 +156,29 @@ class PaginationParams extends Equatable {
|
|||||||
lessThanOrEqual,
|
lessThanOrEqual,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Request model for the [client.partialUpdateUser] api call.
|
||||||
|
@JsonSerializable(createFactory: false)
|
||||||
|
class PartialUpdateUserRequest extends Equatable {
|
||||||
|
/// Creates a new PartialUpdateUserRequest instance.
|
||||||
|
const PartialUpdateUserRequest({
|
||||||
|
required this.id,
|
||||||
|
this.set,
|
||||||
|
this.unset,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// User ID.
|
||||||
|
final String id;
|
||||||
|
|
||||||
|
/// Fields to set.
|
||||||
|
final Map<String, Object?>? set;
|
||||||
|
|
||||||
|
/// Fields to unset.
|
||||||
|
final List<String>? unset;
|
||||||
|
|
||||||
|
/// Serialize model to json
|
||||||
|
Map<String, dynamic> toJson() => _$PartialUpdateUserRequestToJson(this);
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [id, set, unset];
|
||||||
|
}
|
||||||
|
|||||||
@@ -54,3 +54,14 @@ Map<String, dynamic> _$PaginationParamsToJson(PaginationParams instance) {
|
|||||||
writeNotNull('id_lte', instance.lessThanOrEqual);
|
writeNotNull('id_lte', instance.lessThanOrEqual);
|
||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> _$PartialUpdateUserRequestToJson(
|
||||||
|
PartialUpdateUserRequest instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'stringify': instance.stringify,
|
||||||
|
'hash_code': instance.hashCode,
|
||||||
|
'id': instance.id,
|
||||||
|
'set': instance.set,
|
||||||
|
'unset': instance.unset,
|
||||||
|
'props': instance.props,
|
||||||
|
};
|
||||||
|
|||||||
@@ -46,4 +46,17 @@ class UserApi {
|
|||||||
);
|
);
|
||||||
return UpdateUsersResponse.fromJson(response.data);
|
return UpdateUsersResponse.fromJson(response.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Batch partial update of [users].
|
||||||
|
Future<UpdateUsersResponse> partialUpdateUsers(
|
||||||
|
List<PartialUpdateUserRequest> users,
|
||||||
|
) async {
|
||||||
|
final response = await _client.patch(
|
||||||
|
'/users',
|
||||||
|
data: {
|
||||||
|
'users': users,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return UpdateUsersResponse.fromJson(response.data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,10 +49,13 @@ class AuthInterceptor extends Interceptor {
|
|||||||
DioError err,
|
DioError err,
|
||||||
ErrorInterceptorHandler handler,
|
ErrorInterceptorHandler handler,
|
||||||
) async {
|
) async {
|
||||||
ErrorResponse? error;
|
|
||||||
final data = err.response?.data;
|
final data = err.response?.data;
|
||||||
if (data != null) error = ErrorResponse.fromJson(data);
|
if (data == null || data is! Map<String, dynamic>) {
|
||||||
if (error?.code == ChatErrorCode.tokenExpired.code) {
|
return handler.next(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
final error = ErrorResponse.fromJson(data);
|
||||||
|
if (error.code == ChatErrorCode.tokenExpired.code) {
|
||||||
if (_tokenManager.isStatic) return handler.next(err);
|
if (_tokenManager.isStatic) return handler.next(err);
|
||||||
_client.lock();
|
_client.lock();
|
||||||
await _tokenManager.loadToken(refresh: true);
|
await _tokenManager.loadToken(refresh: true);
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import 'package:stream_chat/src/core/models/user.dart';
|
|||||||
|
|
||||||
part 'channel_state.g.dart';
|
part 'channel_state.g.dart';
|
||||||
|
|
||||||
|
const _emptyPinnedMessages = <Message>[];
|
||||||
|
|
||||||
/// The class that contains the information about a channel
|
/// The class that contains the information about a channel
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
class ChannelState {
|
class ChannelState {
|
||||||
@@ -15,7 +17,7 @@ class ChannelState {
|
|||||||
this.channel,
|
this.channel,
|
||||||
this.messages = const [],
|
this.messages = const [],
|
||||||
this.members = const [],
|
this.members = const [],
|
||||||
this.pinnedMessages = const [],
|
this.pinnedMessages = _emptyPinnedMessages,
|
||||||
this.watcherCount,
|
this.watcherCount,
|
||||||
this.watchers = const [],
|
this.watchers = const [],
|
||||||
this.read = const [],
|
this.read = const [],
|
||||||
@@ -54,7 +56,7 @@ class ChannelState {
|
|||||||
ChannelModel? channel,
|
ChannelModel? channel,
|
||||||
List<Message>? messages,
|
List<Message>? messages,
|
||||||
List<Member>? members,
|
List<Member>? members,
|
||||||
List<Message>? pinnedMessages,
|
List<Message> pinnedMessages = _emptyPinnedMessages,
|
||||||
int? watcherCount,
|
int? watcherCount,
|
||||||
List<User>? watchers,
|
List<User>? watchers,
|
||||||
List<Read>? read,
|
List<Read>? read,
|
||||||
@@ -63,7 +65,11 @@ class ChannelState {
|
|||||||
channel: channel ?? this.channel,
|
channel: channel ?? this.channel,
|
||||||
messages: messages ?? this.messages,
|
messages: messages ?? this.messages,
|
||||||
members: members ?? this.members,
|
members: members ?? this.members,
|
||||||
pinnedMessages: pinnedMessages ?? this.pinnedMessages,
|
// Hack to avoid using the default value in case nothing is provided.
|
||||||
|
// FIXME: Use non-nullable by default instead of empty list.
|
||||||
|
pinnedMessages: pinnedMessages == _emptyPinnedMessages
|
||||||
|
? this.pinnedMessages
|
||||||
|
: pinnedMessages,
|
||||||
watcherCount: watcherCount ?? this.watcherCount,
|
watcherCount: watcherCount ?? this.watcherCount,
|
||||||
watchers: watchers ?? this.watchers,
|
watchers: watchers ?? this.watchers,
|
||||||
read: read ?? this.read,
|
read: read ?? this.read,
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ ChannelState _$ChannelStateFromJson(Map<String, dynamic> json) => ChannelState(
|
|||||||
pinnedMessages: (json['pinned_messages'] as List<dynamic>?)
|
pinnedMessages: (json['pinned_messages'] as List<dynamic>?)
|
||||||
?.map((e) => Message.fromJson(e as Map<String, dynamic>))
|
?.map((e) => Message.fromJson(e as Map<String, dynamic>))
|
||||||
.toList() ??
|
.toList() ??
|
||||||
const [],
|
_emptyPinnedMessages,
|
||||||
watcherCount: json['watcher_count'] as int?,
|
watcherCount: json['watcher_count'] as int?,
|
||||||
watchers: (json['watchers'] as List<dynamic>?)
|
watchers: (json['watchers'] as List<dynamic>?)
|
||||||
?.map((e) => User.fromJson(e as Map<String, dynamic>))
|
?.map((e) => User.fromJson(e as Map<String, dynamic>))
|
||||||
|
|||||||
@@ -69,7 +69,6 @@ class User extends Equatable {
|
|||||||
'online',
|
'online',
|
||||||
'banned',
|
'banned',
|
||||||
'ban_expires',
|
'ban_expires',
|
||||||
'dashboard_ban_channel_cid',
|
|
||||||
'teams',
|
'teams',
|
||||||
'language',
|
'language',
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -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 = '3.3.1';
|
const PACKAGE_VERSION = '3.5.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: 3.3.1
|
version: 3.5.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
|
||||||
|
|
||||||
|
|||||||
@@ -1130,6 +1130,135 @@ void main() {
|
|||||||
verify(() => client.sendReaction(message.id, type)).called(1);
|
verify(() => client.sendReaction(message.id, type)).called(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should work fine with score passed explicitly', () async {
|
||||||
|
const type = 'test-reaction-type';
|
||||||
|
final message = Message(id: 'test-message-id');
|
||||||
|
|
||||||
|
const score = 5;
|
||||||
|
final reaction = Reaction(
|
||||||
|
type: type,
|
||||||
|
messageId: message.id,
|
||||||
|
score: score,
|
||||||
|
);
|
||||||
|
|
||||||
|
when(() => client.sendReaction(
|
||||||
|
message.id,
|
||||||
|
type,
|
||||||
|
score: score,
|
||||||
|
)).thenAnswer(
|
||||||
|
(_) async => SendReactionResponse()
|
||||||
|
..message = message
|
||||||
|
..reaction = reaction,
|
||||||
|
);
|
||||||
|
|
||||||
|
expectLater(
|
||||||
|
// skipping first seed message list -> [] messages
|
||||||
|
channel.state?.messagesStream.skip(1),
|
||||||
|
emitsInOrder([
|
||||||
|
[
|
||||||
|
isSameMessageAs(
|
||||||
|
message.copyWith(
|
||||||
|
status: MessageSendingStatus.sent,
|
||||||
|
reactionCounts: {type: 1},
|
||||||
|
reactionScores: {type: score},
|
||||||
|
latestReactions: [reaction],
|
||||||
|
ownReactions: [reaction],
|
||||||
|
),
|
||||||
|
matchReactions: true,
|
||||||
|
matchSendingStatus: true,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
|
||||||
|
final res = await channel.sendReaction(
|
||||||
|
message,
|
||||||
|
type,
|
||||||
|
score: score,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(res, isNotNull);
|
||||||
|
expect(res.reaction.type, type);
|
||||||
|
expect(res.reaction.messageId, message.id);
|
||||||
|
expect(res.reaction.score, score);
|
||||||
|
|
||||||
|
verify(() => client.sendReaction(
|
||||||
|
message.id,
|
||||||
|
type,
|
||||||
|
score: score,
|
||||||
|
)).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should work fine with score passed explicitly and in extraData',
|
||||||
|
() async {
|
||||||
|
const type = 'test-reaction-type';
|
||||||
|
final message = Message(id: 'test-message-id');
|
||||||
|
|
||||||
|
const score = 5;
|
||||||
|
const extraDataScore = 3;
|
||||||
|
const extraData = {
|
||||||
|
'score': extraDataScore,
|
||||||
|
};
|
||||||
|
final reaction = Reaction(
|
||||||
|
type: type,
|
||||||
|
messageId: message.id,
|
||||||
|
score: extraDataScore,
|
||||||
|
);
|
||||||
|
|
||||||
|
when(() => client.sendReaction(
|
||||||
|
message.id,
|
||||||
|
type,
|
||||||
|
score: score,
|
||||||
|
extraData: extraData,
|
||||||
|
)).thenAnswer(
|
||||||
|
(_) async => SendReactionResponse()
|
||||||
|
..message = message
|
||||||
|
..reaction = reaction,
|
||||||
|
);
|
||||||
|
|
||||||
|
expectLater(
|
||||||
|
// skipping first seed message list -> [] messages
|
||||||
|
channel.state?.messagesStream.skip(1),
|
||||||
|
emitsInOrder([
|
||||||
|
[
|
||||||
|
isSameMessageAs(
|
||||||
|
message.copyWith(
|
||||||
|
status: MessageSendingStatus.sent,
|
||||||
|
reactionCounts: {type: 1},
|
||||||
|
reactionScores: {type: extraDataScore},
|
||||||
|
latestReactions: [reaction],
|
||||||
|
ownReactions: [reaction],
|
||||||
|
),
|
||||||
|
matchReactions: true,
|
||||||
|
matchSendingStatus: true,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
|
||||||
|
final res = await channel.sendReaction(
|
||||||
|
message,
|
||||||
|
type,
|
||||||
|
score: score,
|
||||||
|
extraData: extraData,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(res, isNotNull);
|
||||||
|
expect(res.reaction.type, type);
|
||||||
|
expect(res.reaction.messageId, message.id);
|
||||||
|
expect(
|
||||||
|
res.reaction.score,
|
||||||
|
extraDataScore,
|
||||||
|
);
|
||||||
|
|
||||||
|
verify(() => client.sendReaction(
|
||||||
|
message.id,
|
||||||
|
type,
|
||||||
|
score: score,
|
||||||
|
extraData: extraData,
|
||||||
|
)).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
test(
|
test(
|
||||||
'should restore previous message if `client.sendReaction` throws',
|
'should restore previous message if `client.sendReaction` throws',
|
||||||
() async {
|
() async {
|
||||||
@@ -1257,6 +1386,189 @@ void main() {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
group('`.sendReaction in thread`', () {
|
||||||
|
test('should work fine', () async {
|
||||||
|
const type = 'test-reaction-type';
|
||||||
|
final message = Message(
|
||||||
|
id: 'test-message-id',
|
||||||
|
parentId: 'test-parent-id', // is thread message
|
||||||
|
);
|
||||||
|
|
||||||
|
final reaction = Reaction(type: type, messageId: message.id);
|
||||||
|
|
||||||
|
when(() => client.sendReaction(message.id, type)).thenAnswer(
|
||||||
|
(_) async => SendReactionResponse()
|
||||||
|
..message = message
|
||||||
|
..reaction = reaction,
|
||||||
|
);
|
||||||
|
|
||||||
|
expectLater(
|
||||||
|
channel.state?.threadsStream
|
||||||
|
// skipping first seed message list -> [] messages
|
||||||
|
.skip(1)
|
||||||
|
.map((event) => event['test-parent-id']),
|
||||||
|
emitsInOrder([
|
||||||
|
[
|
||||||
|
isSameMessageAs(
|
||||||
|
message.copyWith(
|
||||||
|
status: MessageSendingStatus.sent,
|
||||||
|
reactionCounts: {type: 1},
|
||||||
|
reactionScores: {type: 1},
|
||||||
|
latestReactions: [reaction],
|
||||||
|
ownReactions: [reaction],
|
||||||
|
),
|
||||||
|
matchReactions: true,
|
||||||
|
matchSendingStatus: true,
|
||||||
|
matchParentId: true,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
|
||||||
|
final res = await channel.sendReaction(message, type);
|
||||||
|
|
||||||
|
expect(res, isNotNull);
|
||||||
|
expect(res.reaction.type, type);
|
||||||
|
expect(res.reaction.messageId, message.id);
|
||||||
|
|
||||||
|
verify(() => client.sendReaction(message.id, type)).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test(
|
||||||
|
'''should restore previous thread message if `client.sendReaction` throws''',
|
||||||
|
() async {
|
||||||
|
const type = 'test-reaction-type';
|
||||||
|
final message = Message(
|
||||||
|
id: 'test-message-id',
|
||||||
|
parentId: 'test-parent-id', // is thread message
|
||||||
|
);
|
||||||
|
|
||||||
|
final reaction = Reaction(type: type, messageId: message.id);
|
||||||
|
|
||||||
|
when(() => client.sendReaction(message.id, type))
|
||||||
|
.thenThrow(StreamChatNetworkError(ChatErrorCode.inputError));
|
||||||
|
|
||||||
|
expectLater(
|
||||||
|
// skipping first seed message list -> [] messages
|
||||||
|
channel.state?.threadsStream
|
||||||
|
.skip(1)
|
||||||
|
.map((event) => event['test-parent-id']),
|
||||||
|
emitsInOrder([
|
||||||
|
[
|
||||||
|
isSameMessageAs(
|
||||||
|
message.copyWith(
|
||||||
|
status: MessageSendingStatus.sent,
|
||||||
|
reactionCounts: {type: 1},
|
||||||
|
reactionScores: {type: 1},
|
||||||
|
latestReactions: [reaction],
|
||||||
|
ownReactions: [reaction],
|
||||||
|
),
|
||||||
|
matchReactions: true,
|
||||||
|
matchSendingStatus: true,
|
||||||
|
matchParentId: true,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
isSameMessageAs(
|
||||||
|
message,
|
||||||
|
matchReactions: true,
|
||||||
|
matchSendingStatus: true,
|
||||||
|
matchParentId: true,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await channel.sendReaction(message, type);
|
||||||
|
} catch (e) {
|
||||||
|
expect(e, isA<StreamChatNetworkError>());
|
||||||
|
}
|
||||||
|
|
||||||
|
verify(() => client.sendReaction(message.id, type)).called(1);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
test(
|
||||||
|
'''should override previous thread reaction if present and `enforceUnique` is true''',
|
||||||
|
() async {
|
||||||
|
const userId = 'test-user-id';
|
||||||
|
const messageId = 'test-message-id';
|
||||||
|
const parentId = 'test-parent-id';
|
||||||
|
const prevType = 'test-reaction-type';
|
||||||
|
final prevReaction = Reaction(
|
||||||
|
type: prevType,
|
||||||
|
messageId: messageId,
|
||||||
|
userId: userId,
|
||||||
|
);
|
||||||
|
final message = Message(
|
||||||
|
id: messageId,
|
||||||
|
parentId: parentId,
|
||||||
|
ownReactions: [prevReaction],
|
||||||
|
latestReactions: [prevReaction],
|
||||||
|
reactionScores: const {prevType: 1},
|
||||||
|
reactionCounts: const {prevType: 1},
|
||||||
|
);
|
||||||
|
|
||||||
|
const type = 'test-reaction-type-2';
|
||||||
|
final newReaction = Reaction(
|
||||||
|
type: type,
|
||||||
|
messageId: messageId,
|
||||||
|
userId: userId,
|
||||||
|
);
|
||||||
|
final newMessage = message.copyWith(
|
||||||
|
ownReactions: [newReaction],
|
||||||
|
latestReactions: [newReaction],
|
||||||
|
);
|
||||||
|
|
||||||
|
const enforceUnique = true;
|
||||||
|
|
||||||
|
when(() => client.sendReaction(
|
||||||
|
messageId,
|
||||||
|
type,
|
||||||
|
enforceUnique: enforceUnique,
|
||||||
|
)).thenAnswer(
|
||||||
|
(_) async => SendReactionResponse()
|
||||||
|
..message = newMessage
|
||||||
|
..reaction = newReaction,
|
||||||
|
);
|
||||||
|
|
||||||
|
expectLater(
|
||||||
|
// skipping first seed message list -> [] messages
|
||||||
|
channel.state?.threadsStream
|
||||||
|
.skip(1)
|
||||||
|
.map((event) => event['test-parent-id']),
|
||||||
|
emitsInOrder([
|
||||||
|
[
|
||||||
|
isSameMessageAs(
|
||||||
|
newMessage.copyWith(status: MessageSendingStatus.sent),
|
||||||
|
matchReactions: true,
|
||||||
|
matchSendingStatus: true,
|
||||||
|
matchParentId: true,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
|
||||||
|
final res = await channel.sendReaction(
|
||||||
|
message,
|
||||||
|
type,
|
||||||
|
enforceUnique: enforceUnique,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(res, isNotNull);
|
||||||
|
expect(res.reaction.type, type);
|
||||||
|
expect(res.reaction.messageId, messageId);
|
||||||
|
|
||||||
|
verify(() => client.sendReaction(
|
||||||
|
messageId,
|
||||||
|
type,
|
||||||
|
enforceUnique: enforceUnique,
|
||||||
|
)).called(1);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
group('`.deleteReaction`', () {
|
group('`.deleteReaction`', () {
|
||||||
test('should work fine', () async {
|
test('should work fine', () async {
|
||||||
const userId = 'test-user-id';
|
const userId = 'test-user-id';
|
||||||
@@ -1363,6 +1675,121 @@ void main() {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
group('`.deleteReaction in thread`', () {
|
||||||
|
test('should work fine', () async {
|
||||||
|
const userId = 'test-user-id';
|
||||||
|
const messageId = 'test-message-id';
|
||||||
|
const parentId = 'test-parent-id';
|
||||||
|
const type = 'test-reaction-type';
|
||||||
|
final reaction = Reaction(
|
||||||
|
type: type,
|
||||||
|
messageId: messageId,
|
||||||
|
userId: userId,
|
||||||
|
);
|
||||||
|
final message = Message(
|
||||||
|
id: messageId,
|
||||||
|
parentId: parentId, // is thread
|
||||||
|
ownReactions: [reaction],
|
||||||
|
latestReactions: [reaction],
|
||||||
|
reactionScores: const {type: 1},
|
||||||
|
reactionCounts: const {type: 1},
|
||||||
|
);
|
||||||
|
|
||||||
|
when(() => client.deleteReaction(messageId, type))
|
||||||
|
.thenAnswer((_) async => EmptyResponse());
|
||||||
|
|
||||||
|
expectLater(
|
||||||
|
// skipping first seed message list -> [] messages
|
||||||
|
channel.state?.threadsStream
|
||||||
|
.skip(1)
|
||||||
|
.map((event) => event['test-parent-id']),
|
||||||
|
emitsInOrder([
|
||||||
|
[
|
||||||
|
isSameMessageAs(
|
||||||
|
message.copyWith(
|
||||||
|
status: MessageSendingStatus.sent,
|
||||||
|
latestReactions: [],
|
||||||
|
ownReactions: [],
|
||||||
|
),
|
||||||
|
matchReactions: true,
|
||||||
|
matchSendingStatus: true,
|
||||||
|
matchParentId: true,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
|
||||||
|
final res = await channel.deleteReaction(message, reaction);
|
||||||
|
|
||||||
|
expect(res, isNotNull);
|
||||||
|
|
||||||
|
verify(() => client.deleteReaction(messageId, type)).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test(
|
||||||
|
'should restore prev message state if `client.deleteReaction` throws',
|
||||||
|
() async {
|
||||||
|
const userId = 'test-user-id';
|
||||||
|
const messageId = 'test-message-id';
|
||||||
|
const parentId = 'test-parent-id';
|
||||||
|
const type = 'test-reaction-type';
|
||||||
|
final reaction = Reaction(
|
||||||
|
type: type,
|
||||||
|
messageId: messageId,
|
||||||
|
userId: userId,
|
||||||
|
);
|
||||||
|
final message = Message(
|
||||||
|
id: messageId,
|
||||||
|
parentId: parentId,
|
||||||
|
ownReactions: [reaction],
|
||||||
|
latestReactions: [reaction],
|
||||||
|
reactionScores: const {type: 1},
|
||||||
|
reactionCounts: const {type: 1},
|
||||||
|
);
|
||||||
|
|
||||||
|
when(() => client.deleteReaction(messageId, type))
|
||||||
|
.thenThrow(StreamChatNetworkError(ChatErrorCode.inputError));
|
||||||
|
|
||||||
|
expectLater(
|
||||||
|
// skipping first seed message list -> [] messages
|
||||||
|
channel.state?.threadsStream
|
||||||
|
.skip(1)
|
||||||
|
.map((event) => event['test-parent-id']),
|
||||||
|
emitsInOrder([
|
||||||
|
[
|
||||||
|
isSameMessageAs(
|
||||||
|
message.copyWith(
|
||||||
|
status: MessageSendingStatus.sent,
|
||||||
|
latestReactions: [],
|
||||||
|
ownReactions: [],
|
||||||
|
),
|
||||||
|
matchReactions: true,
|
||||||
|
matchSendingStatus: true,
|
||||||
|
matchParentId: true,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
isSameMessageAs(
|
||||||
|
message,
|
||||||
|
matchReactions: true,
|
||||||
|
matchSendingStatus: true,
|
||||||
|
matchParentId: true,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await channel.deleteReaction(message, reaction);
|
||||||
|
} catch (e) {
|
||||||
|
expect(e, isA<StreamChatNetworkError>());
|
||||||
|
}
|
||||||
|
|
||||||
|
verify(() => client.deleteReaction(messageId, type)).called(1);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
test('`.update`', () async {
|
test('`.update`', () async {
|
||||||
const channelData = {
|
const channelData = {
|
||||||
'name': 'Stream Team',
|
'name': 'Stream Team',
|
||||||
|
|||||||
@@ -1772,6 +1772,46 @@ void main() {
|
|||||||
verifyNoMoreInteractions(api.user);
|
verifyNoMoreInteractions(api.user);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('`.partialUpdateUser`', () async {
|
||||||
|
const userId = 'test-user-id';
|
||||||
|
|
||||||
|
final set = {'color': 'yellow'};
|
||||||
|
final unset = <String>[];
|
||||||
|
|
||||||
|
final partialUpdateRequest = PartialUpdateUserRequest(
|
||||||
|
id: userId,
|
||||||
|
set: set,
|
||||||
|
unset: unset,
|
||||||
|
);
|
||||||
|
|
||||||
|
final updatedUser = User(
|
||||||
|
id: userId,
|
||||||
|
extraData: {'color': set['color']},
|
||||||
|
);
|
||||||
|
|
||||||
|
when(() => api.user.partialUpdateUsers([partialUpdateRequest]))
|
||||||
|
.thenAnswer(
|
||||||
|
(_) async => UpdateUsersResponse()
|
||||||
|
..users = {
|
||||||
|
updatedUser.id: updatedUser,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
final res = await client.partialUpdateUser(
|
||||||
|
userId,
|
||||||
|
set: set,
|
||||||
|
unset: unset,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(res, isNotNull);
|
||||||
|
expect(res.users, {updatedUser.id: updatedUser});
|
||||||
|
|
||||||
|
verify(
|
||||||
|
() => api.user.partialUpdateUsers([partialUpdateRequest]),
|
||||||
|
).called(1);
|
||||||
|
verifyNoMoreInteractions(api.user);
|
||||||
|
});
|
||||||
|
|
||||||
test('`.banUser`', () async {
|
test('`.banUser`', () async {
|
||||||
const userId = 'test-user-id';
|
const userId = 'test-user-id';
|
||||||
|
|
||||||
@@ -1956,23 +1996,109 @@ void main() {
|
|||||||
verifyNoMoreInteractions(api.channel);
|
verifyNoMoreInteractions(api.channel);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('`.sendReaction`', () async {
|
group('`.sendReaction`', () {
|
||||||
const messageId = 'test-message-id';
|
test('`.sendReaction with default params`', () async {
|
||||||
const reactionType = 'like';
|
const messageId = 'test-message-id';
|
||||||
|
const reactionType = 'like';
|
||||||
|
const extraData = {'score': 1};
|
||||||
|
|
||||||
when(() => api.message.sendReaction(messageId, reactionType))
|
when(() => api.message.sendReaction(
|
||||||
.thenAnswer((_) async => SendReactionResponse()
|
messageId,
|
||||||
..message = Message(id: messageId)
|
reactionType,
|
||||||
..reaction = Reaction(type: reactionType, messageId: messageId));
|
extraData: extraData,
|
||||||
|
)).thenAnswer((_) async => SendReactionResponse()
|
||||||
|
..message = Message(id: messageId)
|
||||||
|
..reaction = Reaction(type: reactionType, messageId: messageId));
|
||||||
|
|
||||||
final res = await client.sendReaction(messageId, reactionType);
|
final res = await client.sendReaction(messageId, reactionType);
|
||||||
expect(res, isNotNull);
|
expect(res, isNotNull);
|
||||||
expect(res.message.id, messageId);
|
expect(res.message.id, messageId);
|
||||||
expect(res.reaction.type, reactionType);
|
expect(res.reaction.type, reactionType);
|
||||||
expect(res.reaction.messageId, messageId);
|
expect(res.reaction.messageId, messageId);
|
||||||
|
|
||||||
verify(() => api.message.sendReaction(messageId, reactionType)).called(1);
|
verify(() => api.message.sendReaction(
|
||||||
verifyNoMoreInteractions(api.message);
|
messageId,
|
||||||
|
reactionType,
|
||||||
|
extraData: extraData,
|
||||||
|
)).called(1);
|
||||||
|
verifyNoMoreInteractions(api.message);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('`.sendReaction with score`', () async {
|
||||||
|
const messageId = 'test-message-id';
|
||||||
|
const reactionType = 'like';
|
||||||
|
const score = 3;
|
||||||
|
const extraData = {'score': score};
|
||||||
|
|
||||||
|
when(() => api.message.sendReaction(
|
||||||
|
messageId,
|
||||||
|
reactionType,
|
||||||
|
extraData: extraData,
|
||||||
|
)).thenAnswer((_) async => SendReactionResponse()
|
||||||
|
..message = Message(id: messageId)
|
||||||
|
..reaction = Reaction(
|
||||||
|
type: reactionType,
|
||||||
|
messageId: messageId,
|
||||||
|
score: score,
|
||||||
|
));
|
||||||
|
|
||||||
|
final res = await client.sendReaction(
|
||||||
|
messageId,
|
||||||
|
reactionType,
|
||||||
|
score: score,
|
||||||
|
);
|
||||||
|
expect(res, isNotNull);
|
||||||
|
expect(res.message.id, messageId);
|
||||||
|
expect(res.reaction.type, reactionType);
|
||||||
|
expect(res.reaction.messageId, messageId);
|
||||||
|
expect(res.reaction.score, score);
|
||||||
|
|
||||||
|
verify(() => api.message.sendReaction(
|
||||||
|
messageId,
|
||||||
|
reactionType,
|
||||||
|
extraData: extraData,
|
||||||
|
)).called(1);
|
||||||
|
verifyNoMoreInteractions(api.message);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('`.sendReaction with score passed in extradata also`', () async {
|
||||||
|
const messageId = 'test-message-id';
|
||||||
|
const reactionType = 'like';
|
||||||
|
const score = 3;
|
||||||
|
const extraDataScore = 5;
|
||||||
|
const extraData = {'score': extraDataScore};
|
||||||
|
|
||||||
|
when(() => api.message.sendReaction(
|
||||||
|
messageId,
|
||||||
|
reactionType,
|
||||||
|
extraData: extraData,
|
||||||
|
)).thenAnswer((_) async => SendReactionResponse()
|
||||||
|
..message = Message(id: messageId)
|
||||||
|
..reaction = Reaction(
|
||||||
|
type: reactionType,
|
||||||
|
messageId: messageId,
|
||||||
|
score: extraDataScore,
|
||||||
|
));
|
||||||
|
|
||||||
|
final res = await client.sendReaction(
|
||||||
|
messageId,
|
||||||
|
reactionType,
|
||||||
|
score: score,
|
||||||
|
extraData: extraData,
|
||||||
|
);
|
||||||
|
expect(res, isNotNull);
|
||||||
|
expect(res.message.id, messageId);
|
||||||
|
expect(res.reaction.type, reactionType);
|
||||||
|
expect(res.reaction.messageId, messageId);
|
||||||
|
expect(res.reaction.score, extraDataScore);
|
||||||
|
|
||||||
|
verify(() => api.message.sendReaction(
|
||||||
|
messageId,
|
||||||
|
reactionType,
|
||||||
|
extraData: extraData,
|
||||||
|
)).called(1);
|
||||||
|
verifyNoMoreInteractions(api.message);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('`.deleteReaction`', () async {
|
test('`.deleteReaction`', () async {
|
||||||
|
|||||||
@@ -175,14 +175,14 @@ void main() {
|
|||||||
|
|
||||||
test('markAllRead', () async {
|
test('markAllRead', () async {
|
||||||
const path = '/channels/read';
|
const path = '/channels/read';
|
||||||
when(() => client.post(path)).thenAnswer(
|
when(() => client.post(path, data: {})).thenAnswer(
|
||||||
(_) async => successResponse(path, data: <String, dynamic>{}));
|
(_) async => successResponse(path, data: <String, dynamic>{}));
|
||||||
|
|
||||||
final res = await channelApi.markAllRead();
|
final res = await channelApi.markAllRead();
|
||||||
|
|
||||||
expect(res, isNotNull);
|
expect(res, isNotNull);
|
||||||
|
|
||||||
verify(() => client.post(path)).called(1);
|
verify(() => client.post(path, data: {})).called(1);
|
||||||
verifyNoMoreInteractions(client);
|
verifyNoMoreInteractions(client);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -82,4 +82,37 @@ void main() {
|
|||||||
verify(() => client.post(path, data: any(named: 'data'))).called(1);
|
verify(() => client.post(path, data: any(named: 'data'))).called(1);
|
||||||
verifyNoMoreInteractions(client);
|
verifyNoMoreInteractions(client);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('partialUpdateUsers', () async {
|
||||||
|
const user = PartialUpdateUserRequest(
|
||||||
|
id: 'test-user-id',
|
||||||
|
set: {'color': 'yellow'},
|
||||||
|
);
|
||||||
|
|
||||||
|
const path = '/users';
|
||||||
|
|
||||||
|
final updatedUser = {user.id: User(id: user.id, extraData: user.set!)};
|
||||||
|
|
||||||
|
when(() => client.patch(path, data: {
|
||||||
|
'users': [user],
|
||||||
|
})).thenAnswer(
|
||||||
|
(_) async => successResponse(
|
||||||
|
path,
|
||||||
|
data: {
|
||||||
|
'users':
|
||||||
|
updatedUser.map((key, value) => MapEntry(key, value.toJson()))
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final res = await userApi.partialUpdateUsers([user]);
|
||||||
|
|
||||||
|
expect(res, isNotNull);
|
||||||
|
expect(res.users.length, updatedUser.length);
|
||||||
|
|
||||||
|
verify(() => client.patch(path, data: {
|
||||||
|
'users': [user]
|
||||||
|
})).called(1);
|
||||||
|
verifyNoMoreInteractions(client);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ Matcher isSameMessageAs(
|
|||||||
bool matchSendingStatus = false,
|
bool matchSendingStatus = false,
|
||||||
bool matchAttachments = false,
|
bool matchAttachments = false,
|
||||||
bool matchAttachmentsUploadState = false,
|
bool matchAttachmentsUploadState = false,
|
||||||
|
bool matchParentId = false,
|
||||||
}) =>
|
}) =>
|
||||||
_IsSameMessageAs(
|
_IsSameMessageAs(
|
||||||
targetMessage: targetMessage,
|
targetMessage: targetMessage,
|
||||||
@@ -57,6 +58,7 @@ Matcher isSameMessageAs(
|
|||||||
matchSendingStatus: matchSendingStatus,
|
matchSendingStatus: matchSendingStatus,
|
||||||
matchAttachments: matchAttachments,
|
matchAttachments: matchAttachments,
|
||||||
matchAttachmentsUploadState: matchAttachmentsUploadState,
|
matchAttachmentsUploadState: matchAttachmentsUploadState,
|
||||||
|
matchParentId: matchParentId,
|
||||||
);
|
);
|
||||||
|
|
||||||
class _IsSameMessageAs extends Matcher {
|
class _IsSameMessageAs extends Matcher {
|
||||||
@@ -67,6 +69,7 @@ class _IsSameMessageAs extends Matcher {
|
|||||||
this.matchSendingStatus = false,
|
this.matchSendingStatus = false,
|
||||||
this.matchAttachments = false,
|
this.matchAttachments = false,
|
||||||
this.matchAttachmentsUploadState = false,
|
this.matchAttachmentsUploadState = false,
|
||||||
|
this.matchParentId = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
final Message targetMessage;
|
final Message targetMessage;
|
||||||
@@ -75,6 +78,7 @@ class _IsSameMessageAs extends Matcher {
|
|||||||
final bool matchSendingStatus;
|
final bool matchSendingStatus;
|
||||||
final bool matchAttachments;
|
final bool matchAttachments;
|
||||||
final bool matchAttachmentsUploadState;
|
final bool matchAttachmentsUploadState;
|
||||||
|
final bool matchParentId;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Description describe(Description description) =>
|
Description describe(Description description) =>
|
||||||
@@ -123,6 +127,9 @@ class _IsSameMessageAs extends Matcher {
|
|||||||
|
|
||||||
matches &= matchAttachments();
|
matches &= matchAttachments();
|
||||||
}
|
}
|
||||||
|
if (matchParentId) {
|
||||||
|
matches &= message.parentId == targetMessage.parentId;
|
||||||
|
}
|
||||||
return matches;
|
return matches;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,22 @@
|
|||||||
|
|
||||||
🐞 Fixed
|
🐞 Fixed
|
||||||
|
|
||||||
|
- Mentions overlay now doesn't overflow when not enough height available
|
||||||
|
|
||||||
|
## 3.5.0
|
||||||
|
|
||||||
|
🐞 Fixed
|
||||||
|
|
||||||
|
- [[#888]](https://github.com/GetStream/stream-chat-flutter/issues/888) Fix `unban` command not working in `MessageInput`.
|
||||||
|
- [[#805]](https://github.com/GetStream/stream-chat-flutter/issues/805) Updated chewie dependency version to 1.3.0
|
||||||
|
- Fix `showScrollToBottom` in `MessageListView` not respecting false value.
|
||||||
|
- Fix default `Channel` route not opening from `ChannelListView` when `ChannelAvatar` is tapped
|
||||||
|
|
||||||
|
## 3.4.0
|
||||||
|
- Updated `stream_chat_flutter_core` dependency to [`3.4.0`](https://pub.dev/packages/stream_chat_flutter_core/changelog).
|
||||||
|
|
||||||
|
🐞 Fixed
|
||||||
|
|
||||||
- SVG rendering fixes.
|
- SVG rendering fixes.
|
||||||
- Use file extension instead of mimeType for downloading files.
|
- Use file extension instead of mimeType for downloading files.
|
||||||
- [[#860]](https://github.com/GetStream/stream-chat-flutter/issues/860) CastError while compressing Videos.
|
- [[#860]](https://github.com/GetStream/stream-chat-flutter/issues/860) CastError while compressing Videos.
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ apply plugin: 'kotlin-android'
|
|||||||
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 30
|
compileSdkVersion 31
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
main.java.srcDirs += 'src/main/kotlin'
|
main.java.srcDirs += 'src/main/kotlin'
|
||||||
@@ -41,7 +41,7 @@ android {
|
|||||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||||
applicationId "com.example.example"
|
applicationId "com.example.example"
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 30
|
targetSdkVersion 31
|
||||||
versionCode flutterVersionCode.toInteger()
|
versionCode flutterVersionCode.toInteger()
|
||||||
versionName flutterVersionName
|
versionName flutterVersionName
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
android:theme="@style/LaunchTheme"
|
android:theme="@style/LaunchTheme"
|
||||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||||
android:hardwareAccelerated="true"
|
android:hardwareAccelerated="true"
|
||||||
|
android:exported="true"
|
||||||
android:windowSoftInputMode="adjustResize">
|
android:windowSoftInputMode="adjustResize">
|
||||||
<!-- Specifies an Android theme to apply to this Activity as soon as
|
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||||
the Android process has started. This theme is visible to the user
|
the Android process has started. This theme is visible to the user
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
buildscript {
|
buildscript {
|
||||||
ext.kotlin_version = '1.5.20'
|
ext.kotlin_version = '1.6.0'
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
jcenter()
|
jcenter()
|
||||||
|
|||||||
@@ -596,7 +596,9 @@ class _ChannelListViewState extends State<ChannelListView> {
|
|||||||
child: ChannelPreview(
|
child: ChannelPreview(
|
||||||
onLongPress: widget.onChannelLongPress,
|
onLongPress: widget.onChannelLongPress,
|
||||||
channel: channel,
|
channel: channel,
|
||||||
onImageTap: () => widget.onImageTap?.call(channel),
|
onImageTap: widget.onImageTap != null
|
||||||
|
? () => widget.onImageTap!(channel)
|
||||||
|
: null,
|
||||||
onTap: (channel) => onTap(channel, widget.channelWidget),
|
onTap: (channel) => onTap(channel, widget.channelWidget),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -609,7 +611,7 @@ class _ChannelListViewState extends State<ChannelListView> {
|
|||||||
if (widget.onChannelTap != null) {
|
if (widget.onChannelTap != null) {
|
||||||
onTap = widget.onChannelTap!;
|
onTap = widget.onChannelTap!;
|
||||||
} else {
|
} else {
|
||||||
onTap = (client, _) {
|
onTap = (channel, _) {
|
||||||
if (widget.channelWidget == null) {
|
if (widget.channelWidget == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -617,7 +619,7 @@ class _ChannelListViewState extends State<ChannelListView> {
|
|||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => StreamChannel(
|
builder: (context) => StreamChannel(
|
||||||
channel: client,
|
channel: channel,
|
||||||
child: widget.channelWidget!,
|
child: widget.channelWidget!,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -65,24 +65,35 @@ class FullScreenMedia extends StatefulWidget {
|
|||||||
|
|
||||||
class _FullScreenMediaState extends State<FullScreenMedia>
|
class _FullScreenMediaState extends State<FullScreenMedia>
|
||||||
with SingleTickerProviderStateMixin {
|
with SingleTickerProviderStateMixin {
|
||||||
bool _optionsShown = true;
|
late final AnimationController _animationController;
|
||||||
|
|
||||||
late final AnimationController _controller;
|
|
||||||
late final PageController _pageController;
|
late final PageController _pageController;
|
||||||
|
|
||||||
late int _currentPage;
|
late final _curvedAnimation = CurvedAnimation(
|
||||||
|
parent: _animationController,
|
||||||
|
curve: Curves.easeOut,
|
||||||
|
reverseCurve: Curves.easeIn,
|
||||||
|
);
|
||||||
|
|
||||||
|
final _opacityTween = Tween<double>(begin: 1, end: 0);
|
||||||
|
late final _opacityAnimation = _opacityTween.animate(
|
||||||
|
CurvedAnimation(
|
||||||
|
parent: _animationController,
|
||||||
|
curve: const Interval(0, 0.6, curve: Curves.easeOut),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
late final ValueNotifier<int> _currentPage = ValueNotifier(widget.startIndex);
|
||||||
|
|
||||||
final videoPackages = <String, VideoPackage>{};
|
final videoPackages = <String, VideoPackage>{};
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_controller = AnimationController(
|
_animationController = AnimationController(
|
||||||
vsync: this,
|
vsync: this,
|
||||||
duration: const Duration(milliseconds: 300),
|
duration: const Duration(milliseconds: 300),
|
||||||
);
|
);
|
||||||
_pageController = PageController(initialPage: widget.startIndex);
|
_pageController = PageController(initialPage: widget.startIndex);
|
||||||
_currentPage = widget.startIndex;
|
|
||||||
for (var i = 0; i < widget.mediaAttachments.length; i++) {
|
for (var i = 0; i < widget.mediaAttachments.length; i++) {
|
||||||
final attachment = widget.mediaAttachments[i];
|
final attachment = widget.mediaAttachments[i];
|
||||||
if (attachment.type != 'video') continue;
|
if (attachment.type != 'video') continue;
|
||||||
@@ -116,41 +127,38 @@ class _FullScreenMediaState extends State<FullScreenMedia>
|
|||||||
resizeToAvoidBottomInset: false,
|
resizeToAvoidBottomInset: false,
|
||||||
body: Stack(
|
body: Stack(
|
||||||
children: [
|
children: [
|
||||||
AnimatedBuilder(
|
PageView.builder(
|
||||||
animation: _controller,
|
controller: _pageController,
|
||||||
builder: (context, snapshot) => PageView.builder(
|
onPageChanged: (val) {
|
||||||
controller: _pageController,
|
_currentPage.value = val;
|
||||||
onPageChanged: (val) {
|
|
||||||
setState(() {
|
|
||||||
_currentPage = val;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (videoPackages.isEmpty) {
|
if (videoPackages.isEmpty) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final currentAttachment = widget.mediaAttachments[val];
|
||||||
|
|
||||||
|
for (final e in videoPackages.values) {
|
||||||
|
if (e._attachment != currentAttachment) {
|
||||||
|
e._chewieController?.pause();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final currentAttachment = widget.mediaAttachments[val];
|
if (widget.autoplayVideos &&
|
||||||
|
currentAttachment.type == 'video') {
|
||||||
for (final e in videoPackages.values) {
|
final controller = videoPackages[currentAttachment.id]!;
|
||||||
if (e._attachment != currentAttachment) {
|
controller._chewieController?.play();
|
||||||
e._chewieController?.pause();
|
}
|
||||||
}
|
},
|
||||||
}
|
itemBuilder: (context, index) {
|
||||||
|
final attachment = widget.mediaAttachments[index];
|
||||||
if (widget.autoplayVideos &&
|
if (attachment.type == 'image' || attachment.type == 'giphy') {
|
||||||
currentAttachment.type == 'video') {
|
final imageUrl = attachment.imageUrl ??
|
||||||
final controller = videoPackages[currentAttachment.id]!;
|
attachment.assetUrl ??
|
||||||
controller._chewieController?.play();
|
attachment.thumbUrl;
|
||||||
}
|
return AnimatedBuilder(
|
||||||
},
|
animation: _curvedAnimation,
|
||||||
itemBuilder: (context, index) {
|
builder: (context, child) => PhotoView(
|
||||||
final attachment = widget.mediaAttachments[index];
|
|
||||||
if (attachment.type == 'image' ||
|
|
||||||
attachment.type == 'giphy') {
|
|
||||||
final imageUrl = attachment.imageUrl ??
|
|
||||||
attachment.assetUrl ??
|
|
||||||
attachment.thumbUrl;
|
|
||||||
return PhotoView(
|
|
||||||
loadingBuilder: (context, image) => const Offstage(),
|
loadingBuilder: (context, image) => const Offstage(),
|
||||||
imageProvider: (imageUrl == null &&
|
imageProvider: (imageUrl == null &&
|
||||||
attachment.localUri != null &&
|
attachment.localUri != null &&
|
||||||
@@ -166,97 +174,91 @@ class _FullScreenMediaState extends State<FullScreenMedia>
|
|||||||
color: ColorTween(
|
color: ColorTween(
|
||||||
begin: ChannelHeaderTheme.of(context).color,
|
begin: ChannelHeaderTheme.of(context).color,
|
||||||
end: Colors.black,
|
end: Colors.black,
|
||||||
).lerp(_controller.value),
|
).lerp(_curvedAnimation.value),
|
||||||
),
|
),
|
||||||
onTapUp: (a, b, c) {
|
onTapUp: (a, b, c) {
|
||||||
setState(() {
|
if (_animationController.isCompleted) {
|
||||||
_optionsShown = !_optionsShown;
|
_animationController.reverse();
|
||||||
});
|
|
||||||
if (_controller.isCompleted) {
|
|
||||||
_controller.reverse();
|
|
||||||
} else {
|
} else {
|
||||||
_controller.forward();
|
_animationController.forward();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
),
|
||||||
} else if (attachment.type == 'video') {
|
);
|
||||||
final controller = videoPackages[attachment.id]!;
|
} else if (attachment.type == 'video') {
|
||||||
if (!controller.initialized) {
|
final controller = videoPackages[attachment.id]!;
|
||||||
return const Center(
|
if (!controller.initialized) {
|
||||||
child: CircularProgressIndicator(),
|
return const Center(
|
||||||
);
|
child: CircularProgressIndicator(),
|
||||||
}
|
|
||||||
return InkWell(
|
|
||||||
onTap: () {
|
|
||||||
setState(() {
|
|
||||||
_optionsShown = !_optionsShown;
|
|
||||||
});
|
|
||||||
if (_controller.isCompleted) {
|
|
||||||
_controller.reverse();
|
|
||||||
} else {
|
|
||||||
_controller.forward();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
vertical: 50,
|
|
||||||
),
|
|
||||||
child: Chewie(
|
|
||||||
controller: controller.chewieController!,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return Container();
|
return InkWell(
|
||||||
},
|
onTap: () {
|
||||||
itemCount: widget.mediaAttachments.length,
|
if (_animationController.isCompleted) {
|
||||||
),
|
_animationController.reverse();
|
||||||
),
|
} else {
|
||||||
AnimatedOpacity(
|
_animationController.forward();
|
||||||
opacity: _optionsShown ? 1.0 : 0.0,
|
}
|
||||||
duration: const Duration(milliseconds: 300),
|
},
|
||||||
child: Column(
|
child: Padding(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
padding: const EdgeInsets.symmetric(
|
||||||
children: [
|
vertical: 50,
|
||||||
GalleryHeader(
|
),
|
||||||
userName: widget.userName,
|
child: Chewie(
|
||||||
sentAt: context.translations.sentAtText(
|
controller: controller.chewieController!,
|
||||||
date: widget.message.createdAt,
|
),
|
||||||
time: widget.message.createdAt,
|
|
||||||
),
|
),
|
||||||
onBackPressed: () {
|
);
|
||||||
Navigator.of(context).pop();
|
}
|
||||||
},
|
return const SizedBox();
|
||||||
message: widget.message,
|
},
|
||||||
currentIndex: _currentPage,
|
itemCount: widget.mediaAttachments.length,
|
||||||
onShowMessage: () {
|
),
|
||||||
widget.onShowMessage?.call(
|
FadeTransition(
|
||||||
widget.message,
|
opacity: _opacityAnimation,
|
||||||
StreamChannel.of(context).channel,
|
child: ValueListenableBuilder<int>(
|
||||||
);
|
valueListenable: _currentPage,
|
||||||
},
|
builder: (context, value, child) => Column(
|
||||||
attachmentActionsModalBuilder:
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
widget.attachmentActionsModalBuilder,
|
children: [
|
||||||
),
|
GalleryHeader(
|
||||||
if (!widget.message.isEphemeral)
|
userName: widget.userName,
|
||||||
GalleryFooter(
|
sentAt: context.translations.sentAtText(
|
||||||
currentPage: _currentPage,
|
date: widget.message.createdAt,
|
||||||
totalPages: widget.mediaAttachments.length,
|
time: widget.message.createdAt,
|
||||||
mediaAttachments: widget.mediaAttachments,
|
),
|
||||||
|
onBackPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
message: widget.message,
|
message: widget.message,
|
||||||
mediaSelectedCallBack: (val) {
|
currentIndex: value,
|
||||||
setState(() {
|
onShowMessage: () {
|
||||||
_currentPage = val;
|
widget.onShowMessage?.call(
|
||||||
|
widget.message,
|
||||||
|
StreamChannel.of(context).channel,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
attachmentActionsModalBuilder:
|
||||||
|
widget.attachmentActionsModalBuilder,
|
||||||
|
),
|
||||||
|
if (!widget.message.isEphemeral)
|
||||||
|
GalleryFooter(
|
||||||
|
currentPage: value,
|
||||||
|
totalPages: widget.mediaAttachments.length,
|
||||||
|
mediaAttachments: widget.mediaAttachments,
|
||||||
|
message: widget.message,
|
||||||
|
mediaSelectedCallBack: (val) {
|
||||||
|
_currentPage.value = val;
|
||||||
_pageController.animateToPage(
|
_pageController.animateToPage(
|
||||||
val,
|
val,
|
||||||
duration: const Duration(milliseconds: 300),
|
duration: const Duration(milliseconds: 300),
|
||||||
curve: Curves.easeInOut,
|
curve: Curves.easeInOut,
|
||||||
);
|
);
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
});
|
},
|
||||||
},
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -264,9 +266,11 @@ class _FullScreenMediaState extends State<FullScreenMedia>
|
|||||||
);
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() async {
|
void dispose() {
|
||||||
|
_animationController.dispose();
|
||||||
|
_pageController.dispose();
|
||||||
for (final package in videoPackages.values) {
|
for (final package in videoPackages.values) {
|
||||||
await package.dispose();
|
package.dispose();
|
||||||
}
|
}
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -356,6 +356,7 @@ class MessageInputState extends State<MessageInput>
|
|||||||
with RestorationMixin<MessageInput> {
|
with RestorationMixin<MessageInput> {
|
||||||
final _imagePicker = ImagePicker();
|
final _imagePicker = ImagePicker();
|
||||||
late FocusNode _focusNode = widget.focusNode ?? FocusNode();
|
late FocusNode _focusNode = widget.focusNode ?? FocusNode();
|
||||||
|
late final _isInternalFocusNode = widget.focusNode == null;
|
||||||
bool _inputEnabled = true;
|
bool _inputEnabled = true;
|
||||||
|
|
||||||
bool get _commandEnabled => _effectiveController.value.command != null;
|
bool get _commandEnabled => _effectiveController.value.command != null;
|
||||||
@@ -1168,26 +1169,31 @@ class MessageInputState extends State<MessageInput>
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return UserMentionsOverlay(
|
return LayoutBuilder(
|
||||||
query: query,
|
builder: (context, snapshot) => UserMentionsOverlay(
|
||||||
mentionAllAppUsers: widget.mentionAllAppUsers,
|
query: query,
|
||||||
client: StreamChat.of(context).client,
|
mentionAllAppUsers: widget.mentionAllAppUsers,
|
||||||
channel: channel,
|
client: StreamChat.of(context).client,
|
||||||
size: Size(renderObject.size.width - 16, 400),
|
channel: channel,
|
||||||
mentionsTileBuilder: tileBuilder,
|
size: Size(
|
||||||
onMentionUserTap: (user) {
|
renderObject.size.width - 16,
|
||||||
_effectiveController.addMentionedUser(user);
|
min(400, (snapshot.maxHeight - renderObject.size.height - 16).abs()),
|
||||||
splits[splits.length - 1] = user.name;
|
),
|
||||||
final rejoin = splits.join('@');
|
mentionsTileBuilder: tileBuilder,
|
||||||
|
onMentionUserTap: (user) {
|
||||||
|
_effectiveController.addMentionedUser(user);
|
||||||
|
splits[splits.length - 1] = user.name;
|
||||||
|
final rejoin = splits.join('@');
|
||||||
|
|
||||||
_effectiveController.text = rejoin +
|
_effectiveController.text = rejoin +
|
||||||
_effectiveController.text.substring(
|
_effectiveController.text.substring(
|
||||||
_effectiveController.selectionStart,
|
_effectiveController.selectionStart,
|
||||||
);
|
);
|
||||||
|
|
||||||
_onChangedDebounced.cancel();
|
_onChangedDebounced.cancel();
|
||||||
setState(() => _showMentionsOverlay = false);
|
setState(() => _showMentionsOverlay = false);
|
||||||
},
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1816,6 +1822,7 @@ class MessageInputState extends State<MessageInput>
|
|||||||
.removeListener(_onChangedDebounced);
|
.removeListener(_onChangedDebounced);
|
||||||
_controller?.dispose();
|
_controller?.dispose();
|
||||||
_focusNode.removeListener(_focusNodeListener);
|
_focusNode.removeListener(_focusNodeListener);
|
||||||
|
if (_isInternalFocusNode) _focusNode.dispose();
|
||||||
_stopSlowMode();
|
_stopSlowMode();
|
||||||
_onChangedDebounced.cancel();
|
_onChangedDebounced.cancel();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
|
|||||||
@@ -709,20 +709,21 @@ class _MessageListViewState extends State<MessageListView> {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
BetterStreamBuilder<bool>(
|
if (widget.showScrollToBottom)
|
||||||
stream: streamChannel!.channel.state!.isUpToDateStream,
|
BetterStreamBuilder<bool>(
|
||||||
initialData: streamChannel!.channel.state!.isUpToDate,
|
stream: streamChannel!.channel.state!.isUpToDateStream,
|
||||||
builder: (context, snapshot) => ValueListenableBuilder<bool>(
|
initialData: streamChannel!.channel.state!.isUpToDate,
|
||||||
valueListenable: _showScrollToBottom,
|
builder: (context, snapshot) => ValueListenableBuilder<bool>(
|
||||||
child: _buildScrollToBottom(),
|
valueListenable: _showScrollToBottom,
|
||||||
builder: (context, value, child) {
|
child: _buildScrollToBottom(),
|
||||||
if (!snapshot || value) {
|
builder: (context, value, child) {
|
||||||
return child!;
|
if (!snapshot || value) {
|
||||||
}
|
return child!;
|
||||||
return const Offstage();
|
}
|
||||||
},
|
return const Offstage();
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
if (widget.showFloatingDateDivider)
|
if (widget.showFloatingDateDivider)
|
||||||
_buildFloatingDateDivider(itemCount),
|
_buildFloatingDateDivider(itemCount),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -84,9 +84,10 @@ class MessageText extends StatelessWidget {
|
|||||||
String _replaceMentions(String text) {
|
String _replaceMentions(String text) {
|
||||||
var messageTextToRender = text;
|
var messageTextToRender = text;
|
||||||
for (final user in message.mentionedUsers.toSet()) {
|
for (final user in message.mentionedUsers.toSet()) {
|
||||||
|
final userId = user.id;
|
||||||
final userName = user.name;
|
final userName = user.name;
|
||||||
messageTextToRender = messageTextToRender.replaceAll(
|
messageTextToRender = messageTextToRender.replaceAll(
|
||||||
'@$userName',
|
'@$userId',
|
||||||
'[@$userName](@${userName.replaceAll(' ', '')})',
|
'[@$userName](@${userName.replaceAll(' ', '')})',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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: 3.3.2
|
version: 3.5.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
|
||||||
|
|
||||||
@@ -12,22 +12,22 @@ environment:
|
|||||||
dependencies:
|
dependencies:
|
||||||
cached_network_image: ^3.0.0
|
cached_network_image: ^3.0.0
|
||||||
characters: ^1.1.0
|
characters: ^1.1.0
|
||||||
chewie: ^1.2.0
|
chewie: ^1.3.0
|
||||||
collection: ^1.15.0
|
collection: ^1.15.0
|
||||||
diacritic: ^0.1.3
|
diacritic: ^0.1.3
|
||||||
dio: ^4.0.0
|
dio: ^4.0.0
|
||||||
ezanimation: ^0.5.0
|
ezanimation: ^0.6.0
|
||||||
file_picker: ^4.1.3
|
file_picker: ^4.1.3
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
flutter_markdown: ^0.6.1
|
flutter_markdown: ^0.6.1
|
||||||
flutter_portal: ^0.4.0
|
flutter_portal: ^0.4.0
|
||||||
flutter_slidable: ^0.6.0
|
flutter_slidable: ^0.6.0
|
||||||
flutter_svg: ^0.23.0+1
|
flutter_svg: ^1.0.1
|
||||||
http_parser: ^4.0.0
|
http_parser: ^4.0.0
|
||||||
image_gallery_saver: ^1.7.0
|
image_gallery_saver: ^1.7.0
|
||||||
image_picker: ^0.8.2
|
image_picker: ^0.8.2
|
||||||
jiffy: ^4.1.0
|
jiffy: ^5.0.0
|
||||||
lottie: ^1.0.1
|
lottie: ^1.0.1
|
||||||
meta: ^1.3.0
|
meta: ^1.3.0
|
||||||
path_provider: ^2.0.1
|
path_provider: ^2.0.1
|
||||||
@@ -36,7 +36,7 @@ dependencies:
|
|||||||
rxdart: ^0.27.0
|
rxdart: ^0.27.0
|
||||||
share_plus: ^3.0.4
|
share_plus: ^3.0.4
|
||||||
shimmer: ^2.0.0
|
shimmer: ^2.0.0
|
||||||
stream_chat_flutter_core: ^3.3.1
|
stream_chat_flutter_core: ^3.5.0
|
||||||
substring_highlight: ^1.0.26
|
substring_highlight: ^1.0.26
|
||||||
synchronized: ^3.0.0
|
synchronized: ^3.0.0
|
||||||
url_launcher: ^6.0.3
|
url_launcher: ^6.0.3
|
||||||
@@ -57,6 +57,6 @@ dev_dependencies:
|
|||||||
dart_code_metrics: ^4.4.0
|
dart_code_metrics: ^4.4.0
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
golden_toolkit: ^0.11.0
|
golden_toolkit: ^0.13.0
|
||||||
mocktail: ^0.2.0
|
mocktail: ^0.2.0
|
||||||
path: ^1.8.0
|
path: ^1.8.0
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
## Upcoming
|
## 3.5.0
|
||||||
|
|
||||||
|
- Updated `stream_chat` dependency to [`3.5.0`](https://pub.dev/packages/stream_chat/changelog).
|
||||||
|
|
||||||
|
## 3.4.0
|
||||||
|
- Updated `stream_chat` dependency to [`3.4.0`](https://pub.dev/packages/stream_chat/changelog).
|
||||||
|
|
||||||
✅ Added
|
✅ Added
|
||||||
|
|
||||||
|
|||||||
@@ -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: 3.3.1
|
version: 3.5.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: ^1.0.0
|
freezed_annotation: ^1.0.0
|
||||||
meta: ^1.3.0
|
meta: ^1.3.0
|
||||||
rxdart: ^0.27.0
|
rxdart: ^0.27.0
|
||||||
stream_chat: ^3.3.1
|
stream_chat: ^3.5.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
build_runner: ^2.0.1
|
build_runner: ^2.0.1
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
## 2.1.0
|
## 2.1.0
|
||||||
|
|
||||||
|
✅ Added
|
||||||
|
|
||||||
|
* Added support for [Portuguese](https://github.com/GetStream/stream-chat-flutter/blob/master/packages/stream_chat_localizations/lib/src/stream_chat_localizations_pt.dart) locale.
|
||||||
|
|
||||||
🔄 Changed
|
🔄 Changed
|
||||||
|
|
||||||
* Some of the `Japanese` translations have been updated/changed for better understanding.
|
* Some of the `Japanese` translations have been updated/changed for better understanding.
|
||||||
|
|
||||||
## 2.0.0
|
## 2.0.0
|
||||||
|
|
||||||
- Updated `stream_chat_flutter` dependency to [`3.0.0`](https://pub.dev/packages/stream_chat_flutter/changelog).
|
* Updated `stream_chat_flutter` dependency to [`3.0.0`](https://pub.dev/packages/stream_chat_flutter/changelog).
|
||||||
|
|
||||||
🐞 Fixed
|
🐞 Fixed
|
||||||
|
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ At the moment we support the following languages:
|
|||||||
- [Spanish](https://github.com/GetStream/stream-chat-flutter/blob/master/packages/stream_chat_localizations/lib/src/stream_chat_localizations_es.dart)
|
- [Spanish](https://github.com/GetStream/stream-chat-flutter/blob/master/packages/stream_chat_localizations/lib/src/stream_chat_localizations_es.dart)
|
||||||
- [Japanese](https://github.com/GetStream/stream-chat-flutter/blob/master/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ja.dart)
|
- [Japanese](https://github.com/GetStream/stream-chat-flutter/blob/master/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ja.dart)
|
||||||
- [Korean](https://github.com/GetStream/stream-chat-flutter/blob/master/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ko.dart)
|
- [Korean](https://github.com/GetStream/stream-chat-flutter/blob/master/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ko.dart)
|
||||||
|
- [Portuguese](https://github.com/GetStream/stream-chat-flutter/blob/master/packages/stream_chat_localizations/lib/src/stream_chat_localizations_pt.dart)
|
||||||
|
|
||||||
More languages will be added in the future. Feel free to [contribute](https://github.com/GetStream/stream-chat-flutter/blob/master/CONTRIBUTING.md) to add more languages.
|
More languages will be added in the future. Feel free to [contribute](https://github.com/GetStream/stream-chat-flutter/blob/master/CONTRIBUTING.md) to add more languages.
|
||||||
|
|
||||||
@@ -74,6 +75,7 @@ class MyApp extends StatelessWidget {
|
|||||||
Locale('es'),
|
Locale('es'),
|
||||||
Locale('ja'),
|
Locale('ja'),
|
||||||
Locale('ko'),
|
Locale('ko'),
|
||||||
|
Locale('pt'),
|
||||||
],
|
],
|
||||||
// Add GlobalStreamChatLocalizations.delegates
|
// Add GlobalStreamChatLocalizations.delegates
|
||||||
localizationsDelegates: GlobalStreamChatLocalizations.delegates,
|
localizationsDelegates: GlobalStreamChatLocalizations.delegates,
|
||||||
@@ -112,13 +114,14 @@ Example:
|
|||||||
```xml
|
```xml
|
||||||
<key>CFBundleLocalizations</key>
|
<key>CFBundleLocalizations</key>
|
||||||
<array>
|
<array>
|
||||||
<string>en</string>
|
<string>en</string>
|
||||||
<string>nb</string>
|
<string>hi</string>
|
||||||
<string>fr</string>
|
<string>fr</string>
|
||||||
<string>it</string>
|
<string>it</string>
|
||||||
<string>es</string>
|
<string>es</string>
|
||||||
<string>ja</string>
|
<string>ja</string>
|
||||||
<string>ko</string>
|
<string>ko</string>
|
||||||
|
<string>pt</string>
|
||||||
</array>
|
</array>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -4,10 +4,14 @@
|
|||||||
<dict>
|
<dict>
|
||||||
<key>CFBundleLocalizations</key>
|
<key>CFBundleLocalizations</key>
|
||||||
<array>
|
<array>
|
||||||
<string>en</string>
|
<string>en</string>
|
||||||
<string>it</string>
|
<string>hi</string>
|
||||||
<string>fr</string>
|
<string>fr</string>
|
||||||
<string>hi</string>
|
<string>it</string>
|
||||||
|
<string>es</string>
|
||||||
|
<string>ja</string>
|
||||||
|
<string>ko</string>
|
||||||
|
<string>pt</string>
|
||||||
</array>
|
</array>
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ class MyApp extends StatelessWidget {
|
|||||||
Locale('es'),
|
Locale('es'),
|
||||||
Locale('ja'),
|
Locale('ja'),
|
||||||
Locale('ko'),
|
Locale('ko'),
|
||||||
|
Locale('pt'),
|
||||||
],
|
],
|
||||||
// Add GlobalStreamChatLocalizations.delegates
|
// Add GlobalStreamChatLocalizations.delegates
|
||||||
localizationsDelegates: GlobalStreamChatLocalizations.delegates,
|
localizationsDelegates: GlobalStreamChatLocalizations.delegates,
|
||||||
|
|||||||
@@ -100,6 +100,7 @@ class MyApp extends StatelessWidget {
|
|||||||
Locale('es'),
|
Locale('es'),
|
||||||
Locale('ja'),
|
Locale('ja'),
|
||||||
Locale('ko'),
|
Locale('ko'),
|
||||||
|
Locale('pt'),
|
||||||
],
|
],
|
||||||
// Add overridden "CustomStreamChatLocalizationsEn.delegate" along with
|
// Add overridden "CustomStreamChatLocalizationsEn.delegate" along with
|
||||||
// "GlobalStreamChatLocalizations.delegates"
|
// "GlobalStreamChatLocalizations.delegates"
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ part 'stream_chat_localizations_ko.dart';
|
|||||||
|
|
||||||
part 'stream_chat_localizations_hi.dart';
|
part 'stream_chat_localizations_hi.dart';
|
||||||
|
|
||||||
|
part 'stream_chat_localizations_pt.dart';
|
||||||
|
|
||||||
/// The set of supported languages, as language code strings.
|
/// The set of supported languages, as language code strings.
|
||||||
///
|
///
|
||||||
/// The [GlobalStreamChatLocalizations.delegate] can generate localizations for
|
/// The [GlobalStreamChatLocalizations.delegate] can generate localizations for
|
||||||
@@ -33,6 +35,7 @@ const kStreamChatSupportedLanguages = {
|
|||||||
'es',
|
'es',
|
||||||
'ja',
|
'ja',
|
||||||
'ko',
|
'ko',
|
||||||
|
'pt',
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Creates a [GlobalStreamChatLocalizations] instance for the given `locale`.
|
/// Creates a [GlobalStreamChatLocalizations] instance for the given `locale`.
|
||||||
@@ -69,6 +72,8 @@ GlobalStreamChatLocalizations? getStreamChatTranslation(Locale locale) {
|
|||||||
return const StreamChatLocalizationsJa();
|
return const StreamChatLocalizationsJa();
|
||||||
case 'ko':
|
case 'ko':
|
||||||
return const StreamChatLocalizationsKo();
|
return const StreamChatLocalizationsKo();
|
||||||
|
case 'pt':
|
||||||
|
return const StreamChatLocalizationsPt();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,374 @@
|
|||||||
|
part of 'stream_chat_localizations.dart';
|
||||||
|
|
||||||
|
/// The translations for Portuguese (`pt`).
|
||||||
|
class StreamChatLocalizationsPt extends GlobalStreamChatLocalizations {
|
||||||
|
/// Create an instance of the translation bundle for Portuguese.
|
||||||
|
const StreamChatLocalizationsPt({String localeName = 'pt'})
|
||||||
|
: super(localeName: localeName);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get launchUrlError => 'O URL não pôde ser aberto';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get loadingUsersError => 'Erro de carregamento do usuário';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get noUsersLabel => 'Nenhum usuário atualmente';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get retryLabel => 'Tente novamente';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get userLastOnlineText => 'Última vez on-line';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get userOnlineText => 'Online';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String userTypingText(Iterable<User> users) {
|
||||||
|
if (users.isEmpty) return '';
|
||||||
|
final first = users.first;
|
||||||
|
if (users.length == 1) {
|
||||||
|
return '${first.name} está digitando';
|
||||||
|
}
|
||||||
|
return '${first.name} e ${users.length - 1} estão digitando';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get threadReplyLabel => 'Responder na conversa';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get onlyVisibleToYouText => 'Visível apenas para você';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String threadReplyCountText(int count) => '$count respostas na conversa';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String attachmentsUploadProgressText({
|
||||||
|
required int remaining,
|
||||||
|
required int total,
|
||||||
|
}) =>
|
||||||
|
'Tranferência em andamento $remaining/$total ...';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String pinnedByUserText({
|
||||||
|
required User pinnedBy,
|
||||||
|
required User currentUser,
|
||||||
|
}) {
|
||||||
|
final pinnedByCurrentUser = currentUser.id == pinnedBy.id;
|
||||||
|
if (pinnedByCurrentUser) return 'Definido por você';
|
||||||
|
return 'Definido por ${pinnedBy.name}';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get emptyMessagesText => 'Não há mensagens';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get genericErrorText => 'Ocorreu um problema';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get loadingMessagesError => 'Ocorreu um problema ao carregar mensagem';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String resultCountText(int count) => '$count resultados';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get messageDeletedText => 'Esta mensagem foi excluída.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get messageDeletedLabel => 'Mensagem excluída';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get messageReactionsLabel => 'Reações às mensagens';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get emptyChatMessagesText => 'Ainda não há mensagens aqui...';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String threadSeparatorText(int replyCount) {
|
||||||
|
if (replyCount == 1) return '1 resposta';
|
||||||
|
return '$replyCount respostas';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get connectedLabel => 'Conectado';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get disconnectedLabel => 'Desconectado';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get reconnectingLabel => 'Reconectando...';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get alsoSendAsDirectMessageLabel =>
|
||||||
|
'Enviar também como mensagem direta';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get addACommentOrSendLabel => 'Adicionar um comnetário ou enviar';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get searchGifLabel => 'Pesquisar GIFs';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get writeAMessageLabel => 'Escrever uma mensagem';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get instantCommandsLabel => 'Comandos instantâneos';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String fileTooLargeAfterCompressionError(double limitInMB) =>
|
||||||
|
'O arquivo é muito grande para carregamento. '
|
||||||
|
'O tamanho máximo do arquivo é de $limitInMB MB. '
|
||||||
|
'Tentamos comprimi-lo, mas não foi suficiente.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String fileTooLargeError(double limitInMB) =>
|
||||||
|
'O arquivo é muito grande para carregamento. '
|
||||||
|
'O tamanho máximo dos arquivos é de $limitInMB MB.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String emojiMatchingQueryText(String query) =>
|
||||||
|
'Emoji correspondente a "$query"';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get addAFileLabel => 'Adicionar um arquivo';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get photoFromCameraLabel => 'Foto da câmera';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get uploadAFileLabel => 'Transferir um arquivo';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get uploadAPhotoLabel => 'Carregar uma foto';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get uploadAVideoLabel => 'Carregar um vídeo';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get videoFromCameraLabel => 'Vídeo da câmera';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get okLabel => 'OK';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get somethingWentWrongError => 'Algo deu errado';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get addMoreFilesLabel => 'Adicionar mais arquivos';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get enablePhotoAndVideoAccessMessage =>
|
||||||
|
'Por favor, permita o acesso a suas fotos'
|
||||||
|
'\ne vídeos para que possa compartilhar com sua rede.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get allowGalleryAccessMessage => 'Permitir acesso à sua galeria';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get flagMessageLabel => 'Denunciar mensagem';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get flagMessageQuestion => 'Gostaria de enviar esta mensagem ao'
|
||||||
|
'\nmoderador para maior investigação?';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get flagLabel => 'DENUNCIAR';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get cancelLabel => 'CANCELAR';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get flagMessageSuccessfulLabel => 'Mensagem denunciada';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get flagMessageSuccessfulText =>
|
||||||
|
'Esta mensagem foi enviada a um moderador.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get deleteLabel => 'APAGAR';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get deleteMessageLabel => 'Apagar mensagem';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get deleteMessageQuestion =>
|
||||||
|
'Você tem certeza que deseja apagar essa\nmensagem permanentemente?';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get operationCouldNotBeCompletedText =>
|
||||||
|
'A operação não pode ser completada.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get replyLabel => 'Resposta';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String togglePinUnpinText({required bool pinned}) {
|
||||||
|
if (pinned) return 'Desafixar na conversa';
|
||||||
|
return 'Fixar na conversa';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toggleDeleteRetryDeleteMessageText({required bool isDeleteFailed}) {
|
||||||
|
if (isDeleteFailed) return 'Repetir apagar mensagem';
|
||||||
|
return 'Apagar mensagem';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get copyMessageLabel => 'Copiar mensagem';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get editMessageLabel => 'Editar mensagem';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toggleResendOrResendEditedMessage({required bool isUpdateFailed}) {
|
||||||
|
if (isUpdateFailed) return 'Reenviar mensagem alterada';
|
||||||
|
return 'Reenviar';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get photosLabel => 'Fotos';
|
||||||
|
|
||||||
|
String _getDay(DateTime dateTime) {
|
||||||
|
final now = DateTime.now();
|
||||||
|
final today = DateTime(now.year, now.month, now.day);
|
||||||
|
final yesterday = DateTime(now.year, now.month, now.day - 1);
|
||||||
|
|
||||||
|
final date = DateTime(dateTime.year, dateTime.month, dateTime.day);
|
||||||
|
|
||||||
|
if (date == today) {
|
||||||
|
return 'Hoje';
|
||||||
|
} else if (date == yesterday) {
|
||||||
|
return 'Ontem';
|
||||||
|
} else {
|
||||||
|
return 'o ${Jiffy(date).MMMd}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String sentAtText({required DateTime date, required DateTime time}) =>
|
||||||
|
'''Enviado ${_getDay(date)} às ${Jiffy(time.toLocal()).format('HH:mm')}''';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get todayLabel => 'Hoje';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get yesterdayLabel => 'Ontem';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get channelIsMutedText => 'O canal está silenciado';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get noTitleText => 'Sem título';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get letsStartChattingLabel => 'Vamos começar a conversar!';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sendingFirstMessageLabel =>
|
||||||
|
'Que tal enviar sua primeira mensagem a um amigo?';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get startAChatLabel => 'Iniciar uma conversa';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get loadingChannelsError => 'Erro ao carregar os canais';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get deleteConversationLabel => 'Apagar a conversa';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get deleteConversationQuestion =>
|
||||||
|
'Tem certeza que deseja apagar essa conversa?';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get streamChatLabel => 'Stream Chat';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get searchingForNetworkText => 'Pesquisando rede';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get offlineLabel => 'Sem conexão...';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get tryAgainLabel => 'Tente novamente';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String membersCountText(int count) {
|
||||||
|
if (count == 1) return '1 membro';
|
||||||
|
return '$count membros';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String watchersCountText(int count) {
|
||||||
|
if (count == 1) return '1 online';
|
||||||
|
return '$count online';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get viewInfoLabel => 'Ver informação';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get leaveGroupLabel => 'Sair do grupo';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get leaveLabel => 'SAIR';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get leaveConversationLabel => 'Sair da conversa';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get leaveConversationQuestion =>
|
||||||
|
'Tem certeza que deseja sair dessa conversa?';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get showInChatLabel => 'Mostrar no chat';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get saveImageLabel => 'Salvar imagem';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get saveVideoLabel => 'Salvar vídeo';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get uploadErrorLabel => 'ERRO DE TRANSFERÊNCIA';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get giphyLabel => 'Giphy';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get shuffleLabel => 'Misturar';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sendLabel => 'Enviar';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get withText => 'com';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get inText => 'em';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get youText => 'Você';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String galleryPaginationText({
|
||||||
|
required int currentPage,
|
||||||
|
required int totalPages,
|
||||||
|
}) =>
|
||||||
|
'${currentPage + 1} de $totalPages';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get fileText => 'Arquivo';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get replyToMessageLabel => 'Responder à mensagem';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String attachmentLimitExceedError(int limit) => '''
|
||||||
|
Não é possível adicionar mais de $limit arquivos de uma vez
|
||||||
|
''';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get slowModeOnLabel => 'Modo lento ativado';
|
||||||
|
}
|
||||||
@@ -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: 2.0.0
|
version: 2.1.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: ^3.0.0
|
stream_chat_flutter: ^3.4.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
dart_code_metrics: ^4.4.0
|
dart_code_metrics: ^4.4.0
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
## Upcoming
|
## 3.1.0
|
||||||
|
|
||||||
- Bump `drift` to `1.3.0`.
|
- Bump `drift` to `1.3.0`.
|
||||||
|
|
||||||
|
|||||||
@@ -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: 3.0.0
|
version: 3.1.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
|
||||||
|
|
||||||
@@ -10,7 +10,7 @@ environment:
|
|||||||
flutter: ">=1.17.0"
|
flutter: ">=1.17.0"
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
drift: 1.3.0
|
drift: ">=1.3.0 < 1.4.0"
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
logging: ^1.0.1
|
logging: ^1.0.1
|
||||||
@@ -19,12 +19,12 @@ dependencies:
|
|||||||
path: ^1.8.0
|
path: ^1.8.0
|
||||||
path_provider: ^2.0.1
|
path_provider: ^2.0.1
|
||||||
sqlite3_flutter_libs: ^0.5.0
|
sqlite3_flutter_libs: ^0.5.0
|
||||||
stream_chat: ^3.0.0
|
stream_chat: ^3.4.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
build_runner: ^2.0.1
|
build_runner: ^2.0.1
|
||||||
dart_code_metrics: ^4.4.0
|
dart_code_metrics: ^4.4.0
|
||||||
drift_dev: 1.3.0
|
drift_dev: ">=1.3.0 < 1.4.0"
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
mocktail: ^0.2.0
|
mocktail: ^0.2.0
|
||||||
|
|||||||
Reference in New Issue
Block a user