Merge branch 'develop' into feat/positioned-list-experiment

This commit is contained in:
Gordon Hayes
2021-10-06 15:11:11 +02:00
175 changed files with 3780 additions and 2112 deletions
@@ -2,7 +2,7 @@ name: stream_flutter_workflow
env:
ACTIONS_ALLOW_UNSECURE_COMMANDS: 'true'
flutter_version: "2.2.2"
flutter_version: "2.5.1"
on:
pull_request:
+8
View File
@@ -177,6 +177,14 @@ Develop is merged into master after the team performs various automated and QA t
---
# Versioning Policy
All of the Stream Chat packages follow [semantic versioning (semver)](https://semver.org/).
See our [versioning policy documentation](https://getstream.io/chat/docs/sdk/flutter/basics/versioning_policy/) for more information.
---
# Styleguides 💅
![image](https://user-images.githubusercontent.com/20601437/124241186-d17a8680-db1b-11eb-9a21-3df305674ca9.png)
+5
View File
@@ -62,6 +62,11 @@ Every package folder includes a fully functional example with setup instructions
We also provide a set of sample apps created using the Stream Flutter SDK at [this location](https://github.com/GetStream/flutter-samples).
## Versioning Policy
All of the Stream Chat packages follow [semantic versioning (semver)](https://semver.org/).
See our [versioning policy documentation](https://getstream.io/chat/docs/sdk/flutter/basics/versioning_policy/) for more information.
## We are hiring
@@ -0,0 +1,20 @@
---
id: versioning_policy
sidebar_position: 3
title: Versioning Policy
---
All of the Stream Chat packages follow [semantic versioning (semver)](https://semver.org/).
That means that with a version number x.y.z (major.minor.patch):
- When releasing bug fixes (backwards compatible), we make a patch release by changing the z number (ex: 3.6.2 to 3.6.3). A bug fix is defined as an internal change that fixes incorrect behavior.
- When releasing new features or non-critical fixes, we make a minor release by changing the y number (ex: 3.6.2 to 3.7.0).
- When releasing breaking changes (backward incompatible), we make a major release by changing the x number (ex: 3.6.2 to 4.0.0).
See the [semantic versioning](https://dart.dev/tools/pub/versioning#semantic-versions) section from the Dart docs for more information.
This versioning policy does not apply to prerelease packages (below major version of 1). See this
[StackOverflow thread](https://stackoverflow.com/questions/66201337/how-do-dart-package-versions-work-how-should-i-version-my-flutter-plugins)
for more information on Dart package versioning.
Whenever possible, we will add deprecation warnings in preparation for future breaking changes.
@@ -104,7 +104,16 @@ Filter.autoComplete('name', 'demo')
The 'exists' filter matches values that exist, or don't exist, based on the specified boolean value.
```dart
Filter.exists('name', true)
Filter.exists('name')
```
#### Filter.notExists
The 'notExists' filter checks if the specified key doesn't exist. This is a simplified call to `Filter.exists`
with the value set to false.
```dart
Filter.notExists('name')
```
#### Filter.contains
@@ -29,7 +29,7 @@ class MessageSearchPage extends StatelessWidget {
child: MessageSearchListView(
filters: Filter.in_('members', [StreamChat.of(context).user!.id],),
messageQuery: 'your query here',
paginationParams: PaginationParams(limit: 20),
limit: 20,
),
),
);
@@ -28,9 +28,9 @@ class MessageSearchPage extends StatelessWidget {
Widget build(BuildContext context) {
return Scaffold(
body: MessageSearchListCore(
messageQuery: _messageFilter,
filters: _channelsFilter,
paginationParams: PaginationParams(limit: 20),
messageQuery: _messageFilter,
filters: _channelsFilter,
limit: 20,
),
);
}
+25 -1
View File
@@ -1,4 +1,15 @@
## Upcoming
## 3.1.1
✅ Added
- Added `Filter.notExists`.
🐞 Fixed
- [[#710]](https://github.com/GetStream/stream-chat-flutter/issues/710) Fixed JWT requiring using `String` as id.
- Fixed expired CDN attachment links not updating correctly.
## 3.0.0
🛑️ Breaking Changes from `2.2.1`
@@ -13,6 +24,19 @@
✅ Added
- Added `Filter.contains` and `Filter.empty`
- Added support for `next`, `previous` value pagination in `client.search`
, [read more.](https://getstream.io/chat/docs/other-rest/search/#pagination)
- `Attachment` class now has a `fileSize` and `mimeType` property. Setting a `file` will also set the `file_size`
, `mime_type` key on `extraData`, so `attachment.fileSize`, `attachment.mimetype`
and `attachment.extraData['file_size']`
, `attachment.extraData['mime_type]` is same respectively.
🐞 Fixed
- [[#659]](https://github.com/GetStream/stream-chat-flutter/issues/659) Fixed unread count not updating correctly.
- Fix `Filter.empty()` json encoding.
- [[#700]](https://github.com/GetStream/stream-chat-flutter/issues/700) Connecting user without providing `name`
uses `id` instead for setting `user.name`.
## 2.2.1
@@ -351,7 +351,7 @@ class Channel {
Future<bool> get initialized => _initializedCompleter.future;
final _cancelableAttachmentUploadRequest = <String, CancelToken>{};
final _messageAttachmentsUploadCompleter = <String, Completer>{};
final _messageAttachmentsUploadCompleter = <String, Completer<Message>>{};
/// Cancels [attachmentId] upload request. Throws exception if the request
/// hasn't even started yet, Already completed or Already cancelled.
@@ -382,12 +382,10 @@ class Channel {
String messageId,
Iterable<String> attachmentIds,
) {
final message = [
var message = [
...state!.messages,
...state!.threads.values.expand((messages) => messages),
].firstWhereOrNull(
(it) => it.id == messageId,
);
].firstWhereOrNull((it) => it.id == messageId);
if (message == null) {
throw const StreamChatError('Error, Message not found');
@@ -409,11 +407,15 @@ class Channel {
client.logger.info('Found ${attachments.length} attachments');
void updateAttachment(Attachment attachment) {
final index =
message.attachments.indexWhere((it) => it.id == attachment.id);
final index = message!.attachments.indexWhere(
(it) => it.id == attachment.id,
);
if (index != -1) {
message.attachments[index] = attachment;
state?.addMessage(message);
final newAttachments = [...message!.attachments]..[index] = attachment;
final updatedMessage = message!.copyWith(attachments: newAttachments);
state?.addMessage(updatedMessage);
// updating original message for next iteration
message = message!.merge(updatedMessage);
}
}
@@ -476,7 +478,7 @@ class Channel {
_cancelableAttachmentUploadRequest.remove(it.id);
});
})).whenComplete(() {
if (message.attachments.every((it) => it.uploadState.isSuccess)) {
if (message!.attachments.every((it) => it.uploadState.isSuccess)) {
_messageAttachmentsUploadCompleter.remove(messageId)?.complete(message);
}
});
@@ -1076,33 +1078,7 @@ class Channel {
} else {
// remove the passed message if response does
// not contain message
final oldIndex = state!.messages.indexWhere((m) => m.id == messageId);
// remove regular message if present
if (oldIndex != -1) {
final oldMessage = state!.messages[oldIndex];
state!.updateChannelState(state!._channelState.copyWith(
messages: state?.messages?..remove(oldMessage),
channel: state?._channelState.channel,
));
} else {
// remove thread message if present
// also reduces total reply count
final oldMessage = state!.threads.values
.expand((messages) => messages)
.firstWhereOrNull((m) => m.id == messageId);
if (oldMessage?.parentId != null) {
final parentMessage = state!.messages.firstWhereOrNull(
(element) => element.id == oldMessage!.parentId,
);
if (parentMessage != null) {
state!.addMessage(parentMessage.copyWith(
replyCount: parentMessage.replyCount! - 1));
}
state!.updateThreadInfo(oldMessage!.parentId!,
state!.threads[oldMessage.parentId!]!..remove(oldMessage));
}
}
state!.removeMessage(message);
await _client.chatPersistenceClient?.deleteMessageById(messageId);
}
return res;
@@ -1521,7 +1497,7 @@ class ChannelClientState {
}
}
void _checkExpiredAttachmentMessages(ChannelState channelState) {
void _checkExpiredAttachmentMessages(ChannelState channelState) async {
final expiredAttachmentMessagesId = channelState.messages
.where((m) =>
!_updatedMessagesIds.contains(m.id) &&
@@ -1532,20 +1508,24 @@ class ChannelClientState {
return false;
}
final uri = Uri.parse(url);
if (uri.host != 'stream-io-cdn.com' ||
if (!uri.host.endsWith('stream-io-cdn.com') ||
uri.queryParameters['Expires'] == null) {
return false;
}
final expiration =
DateTime.parse(uri.queryParameters['Expires']!);
final secondsFromEpoch =
int.parse(uri.queryParameters['Expires']!);
final expiration = DateTime.fromMillisecondsSinceEpoch(
secondsFromEpoch * 1000);
return expiration.isBefore(DateTime.now());
}) ==
true)
.map((e) => e.id)
.toList();
if (expiredAttachmentMessagesId.isNotEmpty == true) {
_channel.getMessagesById(expiredAttachmentMessagesId);
await _channel._initializedCompleter.future;
_updatedMessagesIds.addAll(expiredAttachmentMessagesId);
_channel.getMessagesById(expiredAttachmentMessagesId);
}
}
@@ -1704,7 +1684,7 @@ class ChannelClientState {
}));
}
/// Add a message to this channel.
/// Add a [message] to this [channelState].
void addMessage(Message message) {
if (message.parentId == null || message.showInChannel == true) {
final newMessages = List<Message>.from(_channelState.messages);
@@ -1735,6 +1715,35 @@ class ChannelClientState {
}
}
/// Remove a [message] from this [channelState].
void removeMessage(Message message) {
final parentId = message.parentId;
// i.e. it's a thread message
// 1. Remove the thread message
// 2. Reduce total reply count of parent message
if (parentId != null) {
final allMessages = [...messages];
final parentMessage = allMessages.firstWhereOrNull(
(it) => it.id == parentId,
);
// return if message not available in the memory
if (parentMessage == null) return;
final replyCount = parentMessage.replyCount;
// return if reply count is null or zero
if (replyCount == null || replyCount == 0) return;
addMessage(parentMessage.copyWith(replyCount: replyCount - 1));
updateThreadInfo(parentId, threads[parentId]!..remove(message));
} else {
// Remove regular message
final allMessages = [...messages];
if (allMessages.remove(message)) {
_channelState = _channelState.copyWith(messages: allMessages);
}
}
}
void _listenReadEvents() {
if (_channelState.channel?.config.readEvents == false) {
return;
@@ -1862,15 +1871,11 @@ class ChannelClientState {
if (newThreads.containsKey(parentId)) {
newThreads[parentId] = [
...newThreads[parentId]
?.where(
(newMessage) => !messages.any((m) => m.id == newMessage.id))
.toList() ??
[],
...messages,
];
newThreads[parentId]!.sort(_sortByCreatedAt);
...newThreads[parentId]!.where(
(newMessage) => !messages.any((m) => m.id == newMessage.id),
),
]..sort(_sortByCreatedAt);
} else {
newThreads[parentId] = messages;
}
+21 -25
View File
@@ -143,9 +143,6 @@ class StreamChatClient {
late final RetryPolicy _retryPolicy;
/// sync state of the channels present inside state, defaults to false
bool _synced = false;
/// the last dateTime at the which all the channels were synced
DateTime? _lastSyncedAt;
@@ -406,10 +403,6 @@ class StreamChatClient {
if (event.type == EventType.healthCheck) {
return _handleHealthCheckEvent(event);
}
if (!event.isLocal && _synced) {
_lastSyncedAt = event.createdAt;
_chatPersistenceClient?.updateLastSyncAt(event.createdAt);
}
state.updateUser(event.user);
return _eventController.add(event);
}
@@ -438,8 +431,6 @@ class StreamChatClient {
type: EventType.connectionRecovered,
online: true,
));
} else {
_synced = false;
}
}
@@ -464,13 +455,11 @@ class StreamChatClient {
Future<void> sync({List<String>? cids, DateTime? lastSyncAt}) async {
cids ??= await _chatPersistenceClient?.getChannelCids();
if (cids == null || cids.isEmpty) {
_synced = true;
return;
}
lastSyncAt ??= await _chatPersistenceClient?.getLastSyncAt();
if (lastSyncAt == null) {
_synced = true;
return;
}
@@ -488,12 +477,10 @@ class StreamChatClient {
handleEvent(event);
}
_synced = true;
final now = DateTime.now();
_lastSyncedAt = now;
_chatPersistenceClient?.updateLastSyncAt(now);
} catch (e, stk) {
_synced = false;
logger.severe('Error during sync', e, stk);
}
}
@@ -1325,6 +1312,7 @@ class StreamChatClient {
// resetting state
state.dispose();
state = ClientState(this);
_lastSyncedAt = null;
// resetting credentials
_tokenManager.reset();
@@ -1366,26 +1354,21 @@ class ClientState {
.where((event) =>
event.me != null && event.type != EventType.healthCheck)
.map((e) => e.me!)
.listen((user) {
currentUser = currentUser?.merge(user) ?? user;
final totalUnreadCount = user.totalUnreadCount;
_totalUnreadCountController.add(totalUnreadCount);
final unreadChannels = user.unreadChannels;
if (unreadChannels != null) {
_unreadChannelsController.add(unreadChannels);
}
}),
.listen((user) => currentUser = currentUser?.merge(user) ?? user),
_client
.on()
.map((event) => event.unreadChannels)
.whereType<int>()
.listen(_unreadChannelsController.add),
.listen((count) {
currentUser = currentUser?.copyWith(unreadChannels: count);
}),
_client
.on()
.map((event) => event.totalUnreadCount)
.whereType<int>()
.listen(_totalUnreadCountController.add),
.listen((count) {
currentUser = currentUser?.copyWith(totalUnreadCount: count);
}),
]);
_listenChannelDeleted();
@@ -1441,6 +1424,7 @@ class ClientState {
/// Sets the user currently interacting with the client
/// note: this fully overrides the [currentUser]
set currentUser(OwnUser? user) {
_computeUnreadCounts(user);
_currentUserController.add(user);
}
@@ -1506,6 +1490,18 @@ class ClientState {
_channelsController.add(newChannels);
}
void _computeUnreadCounts(OwnUser? user) {
final totalUnreadCount = user?.totalUnreadCount;
if (totalUnreadCount != null) {
_totalUnreadCountController.add(totalUnreadCount);
}
final unreadChannels = user?.unreadChannels;
if (unreadChannels != null) {
_unreadChannelsController.add(unreadChannels);
}
}
final _channelsController = BehaviorSubject<Map<String, Channel>>.seeded({});
final _currentUserController = BehaviorSubject<OwnUser?>();
final _usersController = BehaviorSubject<Map<String, User>>.seeded({});
@@ -6,8 +6,8 @@ import 'package:rxdart/rxdart.dart';
import 'package:stream_chat/src/client/channel.dart';
import 'package:stream_chat/src/client/retry_policy.dart';
import 'package:stream_chat/src/core/error/error.dart';
import 'package:stream_chat/src/event_type.dart';
import 'package:stream_chat/src/core/models/message.dart';
import 'package:stream_chat/src/event_type.dart';
import 'package:stream_chat/stream_chat.dart';
/// The retry queue associated to a channel
@@ -36,6 +36,10 @@ class GeneralApi {
PaginationParams? pagination,
Filter? messageFilters,
}) async {
assert(
pagination?.offset == null || pagination?.offset == 0 || sort == null,
'Cannot specify `offset` with `sort` parameter',
);
assert(() {
if (query == null && messageFilters == null) {
throw ArgumentError('Provide at least `query` or `messageFilters`');
@@ -15,7 +15,7 @@ class SortOption<T> {
/// ```
const SortOption(
this.field, {
this.direction = DESC,
this.direction = SortOption.DESC,
this.comparator,
});
@@ -60,12 +60,16 @@ class PaginationParams extends Equatable {
/// ```
const PaginationParams({
this.limit = 10,
this.offset = 0,
this.offset,
this.next,
this.greaterThan,
this.greaterThanOrEqual,
this.lessThan,
this.lessThanOrEqual,
});
}) : assert(
offset == null || offset == 0 || next == null,
'Cannot specify non-zero `offset` with `next` parameter',
);
/// Create a new instance from a json
factory PaginationParams.fromJson(Map<String, dynamic> json) =>
@@ -75,7 +79,10 @@ class PaginationParams extends Equatable {
final int limit;
/// The offset of requesting items.
final int offset;
final int? offset;
/// A key used to paginate.
final String? next;
/// Filter on ids greater than the given value.
@JsonKey(name: 'id_gt')
@@ -100,6 +107,7 @@ class PaginationParams extends Equatable {
PaginationParams copyWith({
int? limit,
int? offset,
String? next,
String? greaterThan,
String? greaterThanOrEqual,
String? lessThan,
@@ -108,6 +116,7 @@ class PaginationParams extends Equatable {
PaginationParams(
limit: limit ?? this.limit,
offset: offset ?? this.offset,
next: next ?? this.next,
greaterThan: greaterThan ?? this.greaterThan,
greaterThanOrEqual: greaterThanOrEqual ?? this.greaterThanOrEqual,
lessThan: lessThan ?? this.lessThan,
@@ -118,6 +127,7 @@ class PaginationParams extends Equatable {
List<Object?> get props => [
limit,
offset,
next,
greaterThan,
greaterThanOrEqual,
lessThan,
@@ -6,12 +6,11 @@ part of 'requests.dart';
// JsonSerializableGenerator
// **************************************************************************
SortOption<T> _$SortOptionFromJson<T>(Map<String, dynamic> json) {
return SortOption<T>(
json['field'] as String,
direction: json['direction'] as int,
);
}
SortOption<T> _$SortOptionFromJson<T>(Map<String, dynamic> json) =>
SortOption<T>(
json['field'] as String,
direction: json['direction'] as int? ?? SortOption.DESC,
);
Map<String, dynamic> _$SortOptionToJson<T>(SortOption<T> instance) =>
<String, dynamic>{
@@ -19,21 +18,20 @@ Map<String, dynamic> _$SortOptionToJson<T>(SortOption<T> instance) =>
'direction': instance.direction,
};
PaginationParams _$PaginationParamsFromJson(Map<String, dynamic> json) {
return PaginationParams(
limit: json['limit'] as int,
offset: json['offset'] as int,
greaterThan: json['id_gt'] as String?,
greaterThanOrEqual: json['id_gte'] as String?,
lessThan: json['id_lt'] as String?,
lessThanOrEqual: json['id_lte'] as String?,
);
}
PaginationParams _$PaginationParamsFromJson(Map<String, dynamic> json) =>
PaginationParams(
limit: json['limit'] as int? ?? 10,
offset: json['offset'] as int?,
next: json['next'] as String?,
greaterThan: json['id_gt'] as String?,
greaterThanOrEqual: json['id_gte'] as String?,
lessThan: json['id_lt'] as String?,
lessThanOrEqual: json['id_lte'] as String?,
);
Map<String, dynamic> _$PaginationParamsToJson(PaginationParams instance) {
final val = <String, dynamic>{
'limit': instance.limit,
'offset': instance.offset,
};
void writeNotNull(String key, dynamic value) {
@@ -42,6 +40,8 @@ Map<String, dynamic> _$PaginationParamsToJson(PaginationParams instance) {
}
}
writeNotNull('offset', instance.offset);
writeNotNull('next', instance.next);
writeNotNull('id_gt', instance.greaterThan);
writeNotNull('id_gte', instance.greaterThanOrEqual);
writeNotNull('id_lt', instance.lessThan);
@@ -253,6 +253,12 @@ class SearchMessagesResponse extends _BaseResponse {
@JsonKey(defaultValue: [])
late List<GetMessageResponse> results;
/// Message id of where to start searching from for next [results]
late String? next;
/// Message id of where to start searching from for previous [results]
late String? previous;
/// Create a new instance from a json
static SearchMessagesResponse fromJson(Map<String, dynamic> json) =>
_$SearchMessagesResponseFromJson(json);
@@ -6,14 +6,13 @@ part of 'responses.dart';
// JsonSerializableGenerator
// **************************************************************************
ErrorResponse _$ErrorResponseFromJson(Map<String, dynamic> json) {
return ErrorResponse()
..duration = json['duration'] as String?
..code = json['code'] as int?
..message = json['message'] as String?
..statusCode = json['StatusCode'] as int?
..moreInfo = json['more_info'] as String?;
}
ErrorResponse _$ErrorResponseFromJson(Map<String, dynamic> json) =>
ErrorResponse()
..duration = json['duration'] as String?
..code = json['code'] as int?
..message = json['message'] as String?
..statusCode = json['StatusCode'] as int?
..moreInfo = json['more_info'] as String?;
Map<String, dynamic> _$ErrorResponseToJson(ErrorResponse instance) =>
<String, dynamic>{
@@ -24,273 +23,253 @@ Map<String, dynamic> _$ErrorResponseToJson(ErrorResponse instance) =>
'more_info': instance.moreInfo,
};
SyncResponse _$SyncResponseFromJson(Map<String, dynamic> json) {
return SyncResponse()
..duration = json['duration'] as String?
..events = (json['events'] as List<dynamic>?)
?.map((e) => Event.fromJson(e as Map<String, dynamic>))
.toList() ??
[];
}
SyncResponse _$SyncResponseFromJson(Map<String, dynamic> json) => SyncResponse()
..duration = json['duration'] as String?
..events = (json['events'] as List<dynamic>?)
?.map((e) => Event.fromJson(e as Map<String, dynamic>))
.toList() ??
[];
QueryChannelsResponse _$QueryChannelsResponseFromJson(
Map<String, dynamic> json) {
return QueryChannelsResponse()
..duration = json['duration'] as String?
..channels = (json['channels'] as List<dynamic>?)
?.map((e) => ChannelState.fromJson(e as Map<String, dynamic>))
.toList() ??
[];
}
Map<String, dynamic> json) =>
QueryChannelsResponse()
..duration = json['duration'] as String?
..channels = (json['channels'] as List<dynamic>?)
?.map((e) => ChannelState.fromJson(e as Map<String, dynamic>))
.toList() ??
[];
TranslateMessageResponse _$TranslateMessageResponseFromJson(
Map<String, dynamic> json) {
return TranslateMessageResponse()
..duration = json['duration'] as String?
..message = Message.fromJson(json['message'] as Map<String, dynamic>);
}
Map<String, dynamic> json) =>
TranslateMessageResponse()
..duration = json['duration'] as String?
..message = Message.fromJson(json['message'] as Map<String, dynamic>);
QueryMembersResponse _$QueryMembersResponseFromJson(Map<String, dynamic> json) {
return QueryMembersResponse()
..duration = json['duration'] as String?
..members = (json['members'] as List<dynamic>?)
?.map((e) => Member.fromJson(e as Map<String, dynamic>))
.toList() ??
[];
}
QueryMembersResponse _$QueryMembersResponseFromJson(
Map<String, dynamic> json) =>
QueryMembersResponse()
..duration = json['duration'] as String?
..members = (json['members'] as List<dynamic>?)
?.map((e) => Member.fromJson(e as Map<String, dynamic>))
.toList() ??
[];
QueryUsersResponse _$QueryUsersResponseFromJson(Map<String, dynamic> json) {
return QueryUsersResponse()
..duration = json['duration'] as String?
..users = (json['users'] as List<dynamic>?)
?.map((e) => User.fromJson(e as Map<String, dynamic>))
.toList() ??
[];
}
QueryUsersResponse _$QueryUsersResponseFromJson(Map<String, dynamic> json) =>
QueryUsersResponse()
..duration = json['duration'] as String?
..users = (json['users'] as List<dynamic>?)
?.map((e) => User.fromJson(e as Map<String, dynamic>))
.toList() ??
[];
QueryReactionsResponse _$QueryReactionsResponseFromJson(
Map<String, dynamic> json) {
return QueryReactionsResponse()
..duration = json['duration'] as String?
..reactions = (json['reactions'] as List<dynamic>?)
?.map((e) => Reaction.fromJson(e as Map<String, dynamic>))
.toList() ??
[];
}
Map<String, dynamic> json) =>
QueryReactionsResponse()
..duration = json['duration'] as String?
..reactions = (json['reactions'] as List<dynamic>?)
?.map((e) => Reaction.fromJson(e as Map<String, dynamic>))
.toList() ??
[];
QueryRepliesResponse _$QueryRepliesResponseFromJson(Map<String, dynamic> json) {
return QueryRepliesResponse()
..duration = json['duration'] as String?
..messages = (json['messages'] as List<dynamic>?)
?.map((e) => Message.fromJson(e as Map<String, dynamic>))
.toList() ??
[];
}
QueryRepliesResponse _$QueryRepliesResponseFromJson(
Map<String, dynamic> json) =>
QueryRepliesResponse()
..duration = json['duration'] as String?
..messages = (json['messages'] as List<dynamic>?)
?.map((e) => Message.fromJson(e as Map<String, dynamic>))
.toList() ??
[];
ListDevicesResponse _$ListDevicesResponseFromJson(Map<String, dynamic> json) {
return ListDevicesResponse()
..duration = json['duration'] as String?
..devices = (json['devices'] as List<dynamic>?)
?.map((e) => Device.fromJson(e as Map<String, dynamic>))
.toList() ??
[];
}
ListDevicesResponse _$ListDevicesResponseFromJson(Map<String, dynamic> json) =>
ListDevicesResponse()
..duration = json['duration'] as String?
..devices = (json['devices'] as List<dynamic>?)
?.map((e) => Device.fromJson(e as Map<String, dynamic>))
.toList() ??
[];
SendFileResponse _$SendFileResponseFromJson(Map<String, dynamic> json) {
return SendFileResponse()
..duration = json['duration'] as String?
..file = json['file'] as String;
}
SendFileResponse _$SendFileResponseFromJson(Map<String, dynamic> json) =>
SendFileResponse()
..duration = json['duration'] as String?
..file = json['file'] as String;
SendImageResponse _$SendImageResponseFromJson(Map<String, dynamic> json) {
return SendImageResponse()
..duration = json['duration'] as String?
..file = json['file'] as String;
}
SendImageResponse _$SendImageResponseFromJson(Map<String, dynamic> json) =>
SendImageResponse()
..duration = json['duration'] as String?
..file = json['file'] as String;
SendReactionResponse _$SendReactionResponseFromJson(Map<String, dynamic> json) {
return SendReactionResponse()
..duration = json['duration'] as String?
..message = Message.fromJson(json['message'] as Map<String, dynamic>)
..reaction = Reaction.fromJson(json['reaction'] as Map<String, dynamic>);
}
SendReactionResponse _$SendReactionResponseFromJson(
Map<String, dynamic> json) =>
SendReactionResponse()
..duration = json['duration'] as String?
..message = Message.fromJson(json['message'] as Map<String, dynamic>)
..reaction = Reaction.fromJson(json['reaction'] as Map<String, dynamic>);
ConnectGuestUserResponse _$ConnectGuestUserResponseFromJson(
Map<String, dynamic> json) {
return ConnectGuestUserResponse()
..duration = json['duration'] as String?
..accessToken = json['access_token'] as String
..user = User.fromJson(json['user'] as Map<String, dynamic>);
}
Map<String, dynamic> json) =>
ConnectGuestUserResponse()
..duration = json['duration'] as String?
..accessToken = json['access_token'] as String
..user = User.fromJson(json['user'] as Map<String, dynamic>);
UpdateUsersResponse _$UpdateUsersResponseFromJson(Map<String, dynamic> json) {
return UpdateUsersResponse()
..duration = json['duration'] as String?
..users = (json['users'] as Map<String, dynamic>?)?.map(
(k, e) => MapEntry(k, User.fromJson(e as Map<String, dynamic>)),
) ??
{};
}
UpdateUsersResponse _$UpdateUsersResponseFromJson(Map<String, dynamic> json) =>
UpdateUsersResponse()
..duration = json['duration'] as String?
..users = (json['users'] as Map<String, dynamic>?)?.map(
(k, e) => MapEntry(k, User.fromJson(e as Map<String, dynamic>)),
) ??
{};
UpdateMessageResponse _$UpdateMessageResponseFromJson(
Map<String, dynamic> json) {
return UpdateMessageResponse()
..duration = json['duration'] as String?
..message = Message.fromJson(json['message'] as Map<String, dynamic>);
}
Map<String, dynamic> json) =>
UpdateMessageResponse()
..duration = json['duration'] as String?
..message = Message.fromJson(json['message'] as Map<String, dynamic>);
SendMessageResponse _$SendMessageResponseFromJson(Map<String, dynamic> json) {
return SendMessageResponse()
..duration = json['duration'] as String?
..message = Message.fromJson(json['message'] as Map<String, dynamic>);
}
SendMessageResponse _$SendMessageResponseFromJson(Map<String, dynamic> json) =>
SendMessageResponse()
..duration = json['duration'] as String?
..message = Message.fromJson(json['message'] as Map<String, dynamic>);
GetMessageResponse _$GetMessageResponseFromJson(Map<String, dynamic> json) {
return GetMessageResponse()
..duration = json['duration'] as String?
..message = Message.fromJson(json['message'] as Map<String, dynamic>)
..channel = json['channel'] == null
? null
: ChannelModel.fromJson(json['channel'] as Map<String, dynamic>);
}
GetMessageResponse _$GetMessageResponseFromJson(Map<String, dynamic> json) =>
GetMessageResponse()
..duration = json['duration'] as String?
..message = Message.fromJson(json['message'] as Map<String, dynamic>)
..channel = json['channel'] == null
? null
: ChannelModel.fromJson(json['channel'] as Map<String, dynamic>);
SearchMessagesResponse _$SearchMessagesResponseFromJson(
Map<String, dynamic> json) {
return SearchMessagesResponse()
..duration = json['duration'] as String?
..results = (json['results'] as List<dynamic>?)
?.map((e) => GetMessageResponse.fromJson(e as Map<String, dynamic>))
.toList() ??
[];
}
Map<String, dynamic> json) =>
SearchMessagesResponse()
..duration = json['duration'] as String?
..results = (json['results'] as List<dynamic>?)
?.map(
(e) => GetMessageResponse.fromJson(e as Map<String, dynamic>))
.toList() ??
[]
..next = json['next'] as String?
..previous = json['previous'] as String?;
GetMessagesByIdResponse _$GetMessagesByIdResponseFromJson(
Map<String, dynamic> json) {
return GetMessagesByIdResponse()
..duration = json['duration'] as String?
..messages = (json['messages'] as List<dynamic>?)
?.map((e) => Message.fromJson(e as Map<String, dynamic>))
.toList() ??
[];
}
Map<String, dynamic> json) =>
GetMessagesByIdResponse()
..duration = json['duration'] as String?
..messages = (json['messages'] as List<dynamic>?)
?.map((e) => Message.fromJson(e as Map<String, dynamic>))
.toList() ??
[];
UpdateChannelResponse _$UpdateChannelResponseFromJson(
Map<String, dynamic> json) {
return UpdateChannelResponse()
..duration = json['duration'] as String?
..channel = ChannelModel.fromJson(json['channel'] as Map<String, dynamic>)
..members = (json['members'] as List<dynamic>?)
?.map((e) => Member.fromJson(e as Map<String, dynamic>))
.toList()
..message = json['message'] == null
? null
: Message.fromJson(json['message'] as Map<String, dynamic>);
}
Map<String, dynamic> json) =>
UpdateChannelResponse()
..duration = json['duration'] as String?
..channel = ChannelModel.fromJson(json['channel'] as Map<String, dynamic>)
..members = (json['members'] as List<dynamic>?)
?.map((e) => Member.fromJson(e as Map<String, dynamic>))
.toList()
..message = json['message'] == null
? null
: Message.fromJson(json['message'] as Map<String, dynamic>);
PartialUpdateChannelResponse _$PartialUpdateChannelResponseFromJson(
Map<String, dynamic> json) {
return PartialUpdateChannelResponse()
..duration = json['duration'] as String?
..channel = ChannelModel.fromJson(json['channel'] as Map<String, dynamic>)
..members = (json['members'] as List<dynamic>?)
?.map((e) => Member.fromJson(e as Map<String, dynamic>))
.toList();
}
Map<String, dynamic> json) =>
PartialUpdateChannelResponse()
..duration = json['duration'] as String?
..channel = ChannelModel.fromJson(json['channel'] as Map<String, dynamic>)
..members = (json['members'] as List<dynamic>?)
?.map((e) => Member.fromJson(e as Map<String, dynamic>))
.toList();
InviteMembersResponse _$InviteMembersResponseFromJson(
Map<String, dynamic> json) {
return InviteMembersResponse()
..duration = json['duration'] as String?
..channel = ChannelModel.fromJson(json['channel'] as Map<String, dynamic>)
..members = (json['members'] as List<dynamic>?)
?.map((e) => Member.fromJson(e as Map<String, dynamic>))
.toList() ??
[]
..message = json['message'] == null
? null
: Message.fromJson(json['message'] as Map<String, dynamic>);
}
Map<String, dynamic> json) =>
InviteMembersResponse()
..duration = json['duration'] as String?
..channel = ChannelModel.fromJson(json['channel'] as Map<String, dynamic>)
..members = (json['members'] as List<dynamic>?)
?.map((e) => Member.fromJson(e as Map<String, dynamic>))
.toList() ??
[]
..message = json['message'] == null
? null
: Message.fromJson(json['message'] as Map<String, dynamic>);
RemoveMembersResponse _$RemoveMembersResponseFromJson(
Map<String, dynamic> json) {
return RemoveMembersResponse()
..duration = json['duration'] as String?
..channel = ChannelModel.fromJson(json['channel'] as Map<String, dynamic>)
..members = (json['members'] as List<dynamic>?)
?.map((e) => Member.fromJson(e as Map<String, dynamic>))
.toList() ??
[]
..message = json['message'] == null
? null
: Message.fromJson(json['message'] as Map<String, dynamic>);
}
Map<String, dynamic> json) =>
RemoveMembersResponse()
..duration = json['duration'] as String?
..channel = ChannelModel.fromJson(json['channel'] as Map<String, dynamic>)
..members = (json['members'] as List<dynamic>?)
?.map((e) => Member.fromJson(e as Map<String, dynamic>))
.toList() ??
[]
..message = json['message'] == null
? null
: Message.fromJson(json['message'] as Map<String, dynamic>);
SendActionResponse _$SendActionResponseFromJson(Map<String, dynamic> json) {
return SendActionResponse()
..duration = json['duration'] as String?
..message = json['message'] == null
? null
: Message.fromJson(json['message'] as Map<String, dynamic>);
}
SendActionResponse _$SendActionResponseFromJson(Map<String, dynamic> json) =>
SendActionResponse()
..duration = json['duration'] as String?
..message = json['message'] == null
? null
: Message.fromJson(json['message'] as Map<String, dynamic>);
AddMembersResponse _$AddMembersResponseFromJson(Map<String, dynamic> json) {
return AddMembersResponse()
..duration = json['duration'] as String?
..channel = ChannelModel.fromJson(json['channel'] as Map<String, dynamic>)
..members = (json['members'] as List<dynamic>?)
?.map((e) => Member.fromJson(e as Map<String, dynamic>))
.toList() ??
[]
..message = json['message'] == null
? null
: Message.fromJson(json['message'] as Map<String, dynamic>);
}
AddMembersResponse _$AddMembersResponseFromJson(Map<String, dynamic> json) =>
AddMembersResponse()
..duration = json['duration'] as String?
..channel = ChannelModel.fromJson(json['channel'] as Map<String, dynamic>)
..members = (json['members'] as List<dynamic>?)
?.map((e) => Member.fromJson(e as Map<String, dynamic>))
.toList() ??
[]
..message = json['message'] == null
? null
: Message.fromJson(json['message'] as Map<String, dynamic>);
AcceptInviteResponse _$AcceptInviteResponseFromJson(Map<String, dynamic> json) {
return AcceptInviteResponse()
..duration = json['duration'] as String?
..channel = ChannelModel.fromJson(json['channel'] as Map<String, dynamic>)
..members = (json['members'] as List<dynamic>?)
?.map((e) => Member.fromJson(e as Map<String, dynamic>))
.toList() ??
[]
..message = json['message'] == null
? null
: Message.fromJson(json['message'] as Map<String, dynamic>);
}
AcceptInviteResponse _$AcceptInviteResponseFromJson(
Map<String, dynamic> json) =>
AcceptInviteResponse()
..duration = json['duration'] as String?
..channel = ChannelModel.fromJson(json['channel'] as Map<String, dynamic>)
..members = (json['members'] as List<dynamic>?)
?.map((e) => Member.fromJson(e as Map<String, dynamic>))
.toList() ??
[]
..message = json['message'] == null
? null
: Message.fromJson(json['message'] as Map<String, dynamic>);
RejectInviteResponse _$RejectInviteResponseFromJson(Map<String, dynamic> json) {
return RejectInviteResponse()
..duration = json['duration'] as String?
..channel = ChannelModel.fromJson(json['channel'] as Map<String, dynamic>)
..members = (json['members'] as List<dynamic>?)
?.map((e) => Member.fromJson(e as Map<String, dynamic>))
.toList() ??
[]
..message = json['message'] == null
? null
: Message.fromJson(json['message'] as Map<String, dynamic>);
}
RejectInviteResponse _$RejectInviteResponseFromJson(
Map<String, dynamic> json) =>
RejectInviteResponse()
..duration = json['duration'] as String?
..channel = ChannelModel.fromJson(json['channel'] as Map<String, dynamic>)
..members = (json['members'] as List<dynamic>?)
?.map((e) => Member.fromJson(e as Map<String, dynamic>))
.toList() ??
[]
..message = json['message'] == null
? null
: Message.fromJson(json['message'] as Map<String, dynamic>);
EmptyResponse _$EmptyResponseFromJson(Map<String, dynamic> json) {
return EmptyResponse()..duration = json['duration'] as String?;
}
EmptyResponse _$EmptyResponseFromJson(Map<String, dynamic> json) =>
EmptyResponse()..duration = json['duration'] as String?;
ChannelStateResponse _$ChannelStateResponseFromJson(Map<String, dynamic> json) {
return ChannelStateResponse()
..duration = json['duration'] as String?
..channel = ChannelModel.fromJson(json['channel'] as Map<String, dynamic>)
..messages = (json['messages'] as List<dynamic>?)
?.map((e) => Message.fromJson(e as Map<String, dynamic>))
.toList() ??
[]
..members = (json['members'] as List<dynamic>?)
?.map((e) => Member.fromJson(e as Map<String, dynamic>))
.toList() ??
[]
..watcherCount = json['watcher_count'] as int? ?? 0
..read = (json['read'] as List<dynamic>?)
?.map((e) => Read.fromJson(e as Map<String, dynamic>))
.toList() ??
[];
}
ChannelStateResponse _$ChannelStateResponseFromJson(
Map<String, dynamic> json) =>
ChannelStateResponse()
..duration = json['duration'] as String?
..channel = ChannelModel.fromJson(json['channel'] as Map<String, dynamic>)
..messages = (json['messages'] as List<dynamic>?)
?.map((e) => Message.fromJson(e as Map<String, dynamic>))
.toList() ??
[]
..members = (json['members'] as List<dynamic>?)
?.map((e) => Member.fromJson(e as Map<String, dynamic>))
.toList() ??
[]
..watcherCount = json['watcher_count'] as int? ?? 0
..read = (json['read'] as List<dynamic>?)
?.map((e) => Read.fromJson(e as Map<String, dynamic>))
.toList() ??
[];
@@ -38,7 +38,9 @@ class AuthInterceptor extends Interceptor {
'Authorization': token.rawValue,
'stream-auth-type': token.authType.raw,
};
options..queryParameters.addAll(params)..headers.addAll(headers);
options
..queryParameters.addAll(params)
..headers.addAll(headers);
return handler.next(options);
}
@@ -46,12 +46,16 @@ class Token extends Equatable {
/// Creates a [Token] instance from the provided [rawValue] if it's valid.
factory Token.fromRawValue(String rawValue) {
final jwtBody = JsonWebToken.unverified(rawValue);
final userId = jwtBody.claims.getTyped<String>('user_id');
final userId = jwtBody.claims.getTyped('user_id');
assert(
userId != null,
'Invalid `token`, It should contain `user_id`',
);
return Token._(rawValue: rawValue, userId: userId!, authType: AuthType.jwt);
return Token._(
rawValue: rawValue,
userId: userId!.toString(),
authType: AuthType.jwt,
);
}
/// The token which can be used during the development.
@@ -21,7 +21,6 @@ class Action {
final String name;
/// The style of the action
@JsonKey(defaultValue: 'default')
final String style;
/// The test of the action
@@ -6,15 +6,13 @@ part of 'action.dart';
// JsonSerializableGenerator
// **************************************************************************
Action _$ActionFromJson(Map<String, dynamic> json) {
return Action(
name: json['name'] as String,
style: json['style'] as String? ?? 'default',
text: json['text'] as String,
type: json['type'] as String,
value: json['value'] as String?,
);
}
Action _$ActionFromJson(Map<String, dynamic> json) => Action(
name: json['name'] as String,
style: json['style'] as String? ?? 'default',
text: json['text'] as String,
type: json['type'] as String,
value: json['value'] as String?,
);
Map<String, dynamic> _$ActionToJson(Action instance) => <String, dynamic>{
'name': instance.name,
@@ -33,13 +33,20 @@ class Attachment extends Equatable {
this.authorIcon,
this.assetUrl,
List<Action>? actions,
this.extraData = const {},
Map<String, Object?> extraData = const {},
this.file,
UploadState? uploadState,
}) : id = id ?? const Uuid().v4(),
title = title ?? file?.name,
localUri = file?.path != null ? Uri.parse(file!.path!) : null,
actions = actions ?? [] {
actions = actions ?? [],
// For backwards compatibility,
// set 'file_size', 'mime_type' in [extraData].
extraData = {
...extraData,
if (file?.size != null) 'file_size': file?.size,
if (file?.mimeType != null) 'mime_type': file?.mimeType?.mimeType,
} {
this.uploadState = uploadState ??
((assetUrl != null || imageUrl != null)
? const UploadState.success()
@@ -110,10 +117,7 @@ class Attachment extends Equatable {
late final UploadState uploadState;
/// Map of custom channel extraData
@JsonKey(
includeIfNull: false,
defaultValue: {},
)
@JsonKey(includeIfNull: false)
final Map<String, Object?> extraData;
/// The attachment ID.
@@ -121,6 +125,18 @@ class Attachment extends Equatable {
/// This is created locally for uniquely identifying a attachment.
final String id;
/// Shortcut for file size.
///
/// {@macro fileSize}
@JsonKey(ignore: true)
int? get fileSize => extraData['file_size'] as int?;
/// Shortcut for file mimeType.
///
/// {@macro mimeType}
@JsonKey(ignore: true)
String? get mimeType => extraData['mime_type'] as String?;
/// Known top level fields.
/// Useful for [Serializer] methods.
static const topLevelFields = [
@@ -6,39 +6,37 @@ part of 'attachment.dart';
// JsonSerializableGenerator
// **************************************************************************
Attachment _$AttachmentFromJson(Map<String, dynamic> json) {
return Attachment(
id: json['id'] as String?,
type: json['type'] as String?,
titleLink: json['title_link'] as String?,
title: json['title'] as String?,
thumbUrl: json['thumb_url'] as String?,
text: json['text'] as String?,
pretext: json['pretext'] as String?,
ogScrapeUrl: json['og_scrape_url'] as String?,
imageUrl: json['image_url'] as String?,
footerIcon: json['footer_icon'] as String?,
footer: json['footer'] as String?,
fields: json['fields'],
fallback: json['fallback'] as String?,
color: json['color'] as String?,
authorName: json['author_name'] as String?,
authorLink: json['author_link'] as String?,
authorIcon: json['author_icon'] as String?,
assetUrl: json['asset_url'] as String?,
actions: (json['actions'] as List<dynamic>?)
?.map((e) => Action.fromJson(e as Map<String, dynamic>))
.toList() ??
[],
extraData: json['extra_data'] as Map<String, dynamic>? ?? {},
file: json['file'] == null
? null
: AttachmentFile.fromJson(json['file'] as Map<String, dynamic>),
uploadState: json['upload_state'] == null
? null
: UploadState.fromJson(json['upload_state'] as Map<String, dynamic>),
);
}
Attachment _$AttachmentFromJson(Map<String, dynamic> json) => Attachment(
id: json['id'] as String?,
type: json['type'] as String?,
titleLink: json['title_link'] as String?,
title: json['title'] as String?,
thumbUrl: json['thumb_url'] as String?,
text: json['text'] as String?,
pretext: json['pretext'] as String?,
ogScrapeUrl: json['og_scrape_url'] as String?,
imageUrl: json['image_url'] as String?,
footerIcon: json['footer_icon'] as String?,
footer: json['footer'] as String?,
fields: json['fields'],
fallback: json['fallback'] as String?,
color: json['color'] as String?,
authorName: json['author_name'] as String?,
authorLink: json['author_link'] as String?,
authorIcon: json['author_icon'] as String?,
assetUrl: json['asset_url'] as String?,
actions: (json['actions'] as List<dynamic>?)
?.map((e) => Action.fromJson(e as Map<String, dynamic>))
.toList() ??
[],
extraData: json['extra_data'] as Map<String, dynamic>? ?? const {},
file: json['file'] == null
? null
: AttachmentFile.fromJson(json['file'] as Map<String, dynamic>),
uploadState: json['upload_state'] == null
? null
: UploadState.fromJson(json['upload_state'] as Map<String, dynamic>),
);
Map<String, dynamic> _$AttachmentToJson(Attachment instance) {
final val = <String, dynamic>{};
@@ -2,6 +2,7 @@ import 'dart:typed_data';
import 'package:dio/dio.dart' show MultipartFile;
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:http_parser/http_parser.dart';
import 'package:meta/meta.dart';
import 'package:stream_chat/src/core/platform_detector/platform_detector.dart';
import 'package:stream_chat/src/core/util/extension.dart';
@@ -65,7 +66,7 @@ class AttachmentFile {
AttachmentFile({
required this.size,
this.path,
this.name,
String? name,
this.bytes,
}) : assert(
path != null || bytes != null,
@@ -74,7 +75,12 @@ class AttachmentFile {
assert(
!CurrentPlatform.isWeb || bytes != null,
'File by path is not supported in web, Please provide bytes',
);
),
assert(
name?.contains('.') ?? true,
'Invalid file name, should also contain file extension',
),
_name = name;
/// Create a new instance from a json
factory AttachmentFile.fromJson(Map<String, dynamic> json) =>
@@ -87,8 +93,10 @@ class AttachmentFile {
/// ```
final String? path;
final String? _name;
/// File name including its extension.
final String? name;
String? get name => _name ?? path?.split('/').last;
/// Byte data for this file. Particularly useful if you want to manipulate
/// its data or easily upload to somewhere else.
@@ -101,26 +109,26 @@ class AttachmentFile {
/// File extension for this file.
String? get extension => name?.split('.').last;
/// The mime type of this file.
MediaType? get mimeType => name?.mimeType;
/// Serialize to json
Map<String, dynamic> toJson() => _$AttachmentFileToJson(this);
/// Converts this into a [MultipartFile]
Future<MultipartFile> toMultipartFile() async {
final filename = path?.split('/').last ?? name;
final mimeType = filename?.mimeType;
late MultipartFile multiPartFile;
MultipartFile multiPartFile;
if (CurrentPlatform.isWeb) {
multiPartFile = MultipartFile.fromBytes(
bytes!,
filename: filename,
filename: name,
contentType: mimeType,
);
} else {
multiPartFile = await MultipartFile.fromFile(
path!,
filename: filename,
filename: name,
contentType: mimeType,
);
}
@@ -1,5 +1,6 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target
part of 'attachment_file.dart';
@@ -13,7 +14,7 @@ final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more informations: https://github.com/rrousselGit/freezed#custom-getters-and-methods');
UploadState _$UploadStateFromJson(Map<String, dynamic> json) {
switch (json['runtimeType'] as String) {
switch (json['runtimeType'] as String?) {
case 'preparing':
return Preparing.fromJson(json);
case 'inProgress':
@@ -24,7 +25,8 @@ UploadState _$UploadStateFromJson(Map<String, dynamic> json) {
return Failed.fromJson(json);
default:
throw FallThroughError();
throw CheckedFromJsonException(json, 'runtimeType', 'UploadState',
'Invalid union type "${json['runtimeType']}"!');
}
}
@@ -72,6 +74,14 @@ mixin _$UploadState {
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult Function()? preparing,
TResult Function(int uploaded, int total)? inProgress,
TResult Function()? success,
TResult Function(String error)? failed,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? preparing,
TResult Function(int uploaded, int total)? inProgress,
@@ -89,6 +99,14 @@ mixin _$UploadState {
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult Function(Preparing value)? preparing,
TResult Function(InProgress value)? inProgress,
TResult Function(Success value)? success,
TResult Function(Failed value)? failed,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(Preparing value)? preparing,
TResult Function(InProgress value)? inProgress,
@@ -138,7 +156,7 @@ class _$Preparing implements Preparing {
const _$Preparing();
factory _$Preparing.fromJson(Map<String, dynamic> json) =>
_$_$PreparingFromJson(json);
_$$PreparingFromJson(json);
@override
String toString() {
@@ -164,6 +182,17 @@ class _$Preparing implements Preparing {
return preparing();
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult Function()? preparing,
TResult Function(int uploaded, int total)? inProgress,
TResult Function()? success,
TResult Function(String error)? failed,
}) {
return preparing?.call();
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
@@ -190,6 +219,17 @@ class _$Preparing implements Preparing {
return preparing(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult Function(Preparing value)? preparing,
TResult Function(InProgress value)? inProgress,
TResult Function(Success value)? success,
TResult Function(Failed value)? failed,
}) {
return preparing?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
@@ -207,7 +247,7 @@ class _$Preparing implements Preparing {
@override
Map<String, dynamic> toJson() {
return _$_$PreparingToJson(this)..['runtimeType'] = 'preparing';
return _$$PreparingToJson(this)..['runtimeType'] = 'preparing';
}
}
@@ -258,7 +298,7 @@ class _$InProgress implements InProgress {
const _$InProgress({required this.uploaded, required this.total});
factory _$InProgress.fromJson(Map<String, dynamic> json) =>
_$_$InProgressFromJson(json);
_$$InProgressFromJson(json);
@override
final int uploaded;
@@ -303,6 +343,17 @@ class _$InProgress implements InProgress {
return inProgress(uploaded, total);
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult Function()? preparing,
TResult Function(int uploaded, int total)? inProgress,
TResult Function()? success,
TResult Function(String error)? failed,
}) {
return inProgress?.call(uploaded, total);
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
@@ -329,6 +380,17 @@ class _$InProgress implements InProgress {
return inProgress(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult Function(Preparing value)? preparing,
TResult Function(InProgress value)? inProgress,
TResult Function(Success value)? success,
TResult Function(Failed value)? failed,
}) {
return inProgress?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
@@ -346,7 +408,7 @@ class _$InProgress implements InProgress {
@override
Map<String, dynamic> toJson() {
return _$_$InProgressToJson(this)..['runtimeType'] = 'inProgress';
return _$$InProgressToJson(this)..['runtimeType'] = 'inProgress';
}
}
@@ -386,7 +448,7 @@ class _$Success implements Success {
const _$Success();
factory _$Success.fromJson(Map<String, dynamic> json) =>
_$_$SuccessFromJson(json);
_$$SuccessFromJson(json);
@override
String toString() {
@@ -412,6 +474,17 @@ class _$Success implements Success {
return success();
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult Function()? preparing,
TResult Function(int uploaded, int total)? inProgress,
TResult Function()? success,
TResult Function(String error)? failed,
}) {
return success?.call();
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
@@ -438,6 +511,17 @@ class _$Success implements Success {
return success(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult Function(Preparing value)? preparing,
TResult Function(InProgress value)? inProgress,
TResult Function(Success value)? success,
TResult Function(Failed value)? failed,
}) {
return success?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
@@ -455,7 +539,7 @@ class _$Success implements Success {
@override
Map<String, dynamic> toJson() {
return _$_$SuccessToJson(this)..['runtimeType'] = 'success';
return _$$SuccessToJson(this)..['runtimeType'] = 'success';
}
}
@@ -500,7 +584,7 @@ class _$Failed implements Failed {
const _$Failed({required this.error});
factory _$Failed.fromJson(Map<String, dynamic> json) =>
_$_$FailedFromJson(json);
_$$FailedFromJson(json);
@override
final String error;
@@ -538,6 +622,17 @@ class _$Failed implements Failed {
return failed(error);
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult Function()? preparing,
TResult Function(int uploaded, int total)? inProgress,
TResult Function()? success,
TResult Function(String error)? failed,
}) {
return failed?.call(error);
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
@@ -564,6 +659,17 @@ class _$Failed implements Failed {
return failed(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult Function(Preparing value)? preparing,
TResult Function(InProgress value)? inProgress,
TResult Function(Success value)? success,
TResult Function(Failed value)? failed,
}) {
return failed?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
@@ -581,7 +687,7 @@ class _$Failed implements Failed {
@override
Map<String, dynamic> toJson() {
return _$_$FailedToJson(this)..['runtimeType'] = 'failed';
return _$$FailedToJson(this)..['runtimeType'] = 'failed';
}
}
@@ -6,14 +6,13 @@ part of 'attachment_file.dart';
// JsonSerializableGenerator
// **************************************************************************
AttachmentFile _$AttachmentFileFromJson(Map<String, dynamic> json) {
return AttachmentFile(
size: json['size'] as int?,
path: json['path'] as String?,
name: json['name'] as String?,
bytes: _fromString(json['bytes'] as String?),
);
}
AttachmentFile _$AttachmentFileFromJson(Map<String, dynamic> json) =>
AttachmentFile(
size: json['size'] as int?,
path: json['path'] as String?,
name: json['name'] as String?,
bytes: _fromString(json['bytes'] as String?),
);
Map<String, dynamic> _$AttachmentFileToJson(AttachmentFile instance) =>
<String, dynamic>{
@@ -23,39 +22,31 @@ Map<String, dynamic> _$AttachmentFileToJson(AttachmentFile instance) =>
'size': instance.size,
};
_$Preparing _$_$PreparingFromJson(Map<String, dynamic> json) {
return _$Preparing();
}
_$Preparing _$$PreparingFromJson(Map<String, dynamic> json) => _$Preparing();
Map<String, dynamic> _$_$PreparingToJson(_$Preparing instance) =>
Map<String, dynamic> _$$PreparingToJson(_$Preparing instance) =>
<String, dynamic>{};
_$InProgress _$_$InProgressFromJson(Map<String, dynamic> json) {
return _$InProgress(
uploaded: json['uploaded'] as int,
total: json['total'] as int,
);
}
_$InProgress _$$InProgressFromJson(Map<String, dynamic> json) => _$InProgress(
uploaded: json['uploaded'] as int,
total: json['total'] as int,
);
Map<String, dynamic> _$_$InProgressToJson(_$InProgress instance) =>
Map<String, dynamic> _$$InProgressToJson(_$InProgress instance) =>
<String, dynamic>{
'uploaded': instance.uploaded,
'total': instance.total,
};
_$Success _$_$SuccessFromJson(Map<String, dynamic> json) {
return _$Success();
}
_$Success _$$SuccessFromJson(Map<String, dynamic> json) => _$Success();
Map<String, dynamic> _$_$SuccessToJson(_$Success instance) =>
Map<String, dynamic> _$$SuccessToJson(_$Success instance) =>
<String, dynamic>{};
_$Failed _$_$FailedFromJson(Map<String, dynamic> json) {
return _$Failed(
error: json['error'] as String,
);
}
_$Failed _$$FailedFromJson(Map<String, dynamic> json) => _$Failed(
error: json['error'] as String,
);
Map<String, dynamic> _$_$FailedToJson(_$Failed instance) => <String, dynamic>{
Map<String, dynamic> _$$FailedToJson(_$Failed instance) => <String, dynamic>{
'error': instance.error,
};
@@ -31,15 +31,12 @@ class ChannelConfig {
_$ChannelConfigFromJson(json);
/// Moderation configuration
@JsonKey(defaultValue: 'flag')
final String automod;
/// List of available commands
@JsonKey(defaultValue: [])
final List<Command> commands;
/// True if the channel should send connect events
@JsonKey(defaultValue: false)
final bool connectEvents;
/// Date of channel creation
@@ -49,43 +46,33 @@ class ChannelConfig {
final DateTime updatedAt;
/// Max channel message length
@JsonKey(defaultValue: 0)
final int maxMessageLength;
/// Duration of message retention
@JsonKey(defaultValue: '')
final String messageRetention;
/// True if users can be muted
@JsonKey(defaultValue: false)
final bool mutes;
/// True if reaction are active for this channel
@JsonKey(defaultValue: false)
final bool reactions;
/// True if readEvents are active for this channel
@JsonKey(defaultValue: false)
final bool readEvents;
/// True if reply message are active for this channel
@JsonKey(defaultValue: false)
final bool replies;
/// True if it's possible to perform a search in this channel
@JsonKey(defaultValue: false)
final bool search;
/// True if typing events should be sent for this channel
@JsonKey(defaultValue: false)
final bool typingEvents;
/// True if it's possible to upload files to this channel
@JsonKey(defaultValue: false)
final bool uploads;
/// True if urls appears as attachments
@JsonKey(defaultValue: false)
final bool urlEnrichment;
/// Serialize to json
@@ -6,32 +6,31 @@ part of 'channel_config.dart';
// JsonSerializableGenerator
// **************************************************************************
ChannelConfig _$ChannelConfigFromJson(Map<String, dynamic> json) {
return ChannelConfig(
automod: json['automod'] as String? ?? 'flag',
commands: (json['commands'] as List<dynamic>?)
?.map((e) => Command.fromJson(e as Map<String, dynamic>))
.toList() ??
[],
connectEvents: json['connect_events'] as bool? ?? false,
createdAt: json['created_at'] == null
? null
: DateTime.parse(json['created_at'] as String),
updatedAt: json['updated_at'] == null
? null
: DateTime.parse(json['updated_at'] as String),
maxMessageLength: json['max_message_length'] as int? ?? 0,
messageRetention: json['message_retention'] as String? ?? '',
mutes: json['mutes'] as bool? ?? false,
reactions: json['reactions'] as bool? ?? false,
readEvents: json['read_events'] as bool? ?? false,
replies: json['replies'] as bool? ?? false,
search: json['search'] as bool? ?? false,
typingEvents: json['typing_events'] as bool? ?? false,
uploads: json['uploads'] as bool? ?? false,
urlEnrichment: json['url_enrichment'] as bool? ?? false,
);
}
ChannelConfig _$ChannelConfigFromJson(Map<String, dynamic> json) =>
ChannelConfig(
automod: json['automod'] as String? ?? 'flag',
commands: (json['commands'] as List<dynamic>?)
?.map((e) => Command.fromJson(e as Map<String, dynamic>))
.toList() ??
const [],
connectEvents: json['connect_events'] as bool? ?? false,
createdAt: json['created_at'] == null
? null
: DateTime.parse(json['created_at'] as String),
updatedAt: json['updated_at'] == null
? null
: DateTime.parse(json['updated_at'] as String),
maxMessageLength: json['max_message_length'] as int? ?? 0,
messageRetention: json['message_retention'] as String? ?? '',
mutes: json['mutes'] as bool? ?? false,
reactions: json['reactions'] as bool? ?? false,
readEvents: json['read_events'] as bool? ?? false,
replies: json['replies'] as bool? ?? false,
search: json['search'] as bool? ?? false,
typingEvents: json['typing_events'] as bool? ?? false,
uploads: json['uploads'] as bool? ?? false,
urlEnrichment: json['url_enrichment'] as bool? ?? false,
);
Map<String, dynamic> _$ChannelConfigToJson(ChannelConfig instance) =>
<String, dynamic>{
@@ -1,7 +1,7 @@
import 'package:json_annotation/json_annotation.dart';
import 'package:stream_chat/src/core/models/channel_config.dart';
import 'package:stream_chat/src/core/util/serializer.dart';
import 'package:stream_chat/src/core/models/user.dart';
import 'package:stream_chat/src/core/util/serializer.dart';
part 'channel_model.g.dart';
@@ -59,7 +59,7 @@ class ChannelModel {
final User? createdBy;
/// True if this channel is frozen
@JsonKey(includeIfNull: false, defaultValue: false)
@JsonKey(includeIfNull: false)
final bool frozen;
/// The date of the last message
@@ -79,18 +79,15 @@ class ChannelModel {
final DateTime? deletedAt;
/// The count of this channel members
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly, defaultValue: 0)
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
final int memberCount;
/// The number of seconds in a cooldown
@JsonKey(includeIfNull: false, defaultValue: 0)
@JsonKey(includeIfNull: false)
final int cooldown;
/// Map of custom channel extraData
@JsonKey(
includeIfNull: false,
defaultValue: {},
)
@JsonKey(includeIfNull: false)
final Map<String, Object?> extraData;
/// The team the channel belongs to
@@ -6,36 +6,34 @@ part of 'channel_model.dart';
// JsonSerializableGenerator
// **************************************************************************
ChannelModel _$ChannelModelFromJson(Map<String, dynamic> json) {
return ChannelModel(
id: json['id'] as String?,
type: json['type'] as String?,
cid: json['cid'] as String?,
config: json['config'] == null
? null
: ChannelConfig.fromJson(json['config'] as Map<String, dynamic>),
createdBy: json['created_by'] == null
? null
: User.fromJson(json['created_by'] as Map<String, dynamic>),
frozen: json['frozen'] as bool? ?? false,
lastMessageAt: json['last_message_at'] == null
? null
: DateTime.parse(json['last_message_at'] as String),
createdAt: json['created_at'] == null
? null
: DateTime.parse(json['created_at'] as String),
updatedAt: json['updated_at'] == null
? null
: DateTime.parse(json['updated_at'] as String),
deletedAt: json['deleted_at'] == null
? null
: DateTime.parse(json['deleted_at'] as String),
memberCount: json['member_count'] as int? ?? 0,
extraData: json['extra_data'] as Map<String, dynamic>? ?? {},
team: json['team'] as String?,
cooldown: json['cooldown'] as int? ?? 0,
);
}
ChannelModel _$ChannelModelFromJson(Map<String, dynamic> json) => ChannelModel(
id: json['id'] as String?,
type: json['type'] as String?,
cid: json['cid'] as String?,
config: json['config'] == null
? null
: ChannelConfig.fromJson(json['config'] as Map<String, dynamic>),
createdBy: json['created_by'] == null
? null
: User.fromJson(json['created_by'] as Map<String, dynamic>),
frozen: json['frozen'] as bool? ?? false,
lastMessageAt: json['last_message_at'] == null
? null
: DateTime.parse(json['last_message_at'] as String),
createdAt: json['created_at'] == null
? null
: DateTime.parse(json['created_at'] as String),
updatedAt: json['updated_at'] == null
? null
: DateTime.parse(json['updated_at'] as String),
deletedAt: json['deleted_at'] == null
? null
: DateTime.parse(json['deleted_at'] as String),
memberCount: json['member_count'] as int? ?? 0,
extraData: json['extra_data'] as Map<String, dynamic>? ?? const {},
team: json['team'] as String?,
cooldown: json['cooldown'] as int? ?? 0,
);
Map<String, dynamic> _$ChannelModelToJson(ChannelModel instance) {
final val = <String, dynamic>{
@@ -25,26 +25,21 @@ class ChannelState {
final ChannelModel? channel;
/// A paginated list of channel messages
@JsonKey(defaultValue: <Message>[])
final List<Message> messages;
/// A paginated list of channel members
@JsonKey(defaultValue: <Member>[])
final List<Member> members;
/// A paginated list of pinned messages
@JsonKey(defaultValue: <Message>[])
final List<Message> pinnedMessages;
/// The count of users watching the channel
final int? watcherCount;
/// A paginated list of users watching the channel
@JsonKey(defaultValue: <User>[])
final List<User> watchers;
/// The list of channel reads
@JsonKey(defaultValue: <Read>[])
final List<Read> read;
/// Create a new instance from a json
@@ -6,34 +6,32 @@ part of 'channel_state.dart';
// JsonSerializableGenerator
// **************************************************************************
ChannelState _$ChannelStateFromJson(Map<String, dynamic> json) {
return ChannelState(
channel: json['channel'] == null
? null
: ChannelModel.fromJson(json['channel'] as Map<String, dynamic>),
messages: (json['messages'] as List<dynamic>?)
?.map((e) => Message.fromJson(e as Map<String, dynamic>))
.toList() ??
[],
members: (json['members'] as List<dynamic>?)
?.map((e) => Member.fromJson(e as Map<String, dynamic>))
.toList() ??
[],
pinnedMessages: (json['pinned_messages'] as List<dynamic>?)
?.map((e) => Message.fromJson(e as Map<String, dynamic>))
.toList() ??
[],
watcherCount: json['watcher_count'] as int?,
watchers: (json['watchers'] as List<dynamic>?)
?.map((e) => User.fromJson(e as Map<String, dynamic>))
.toList() ??
[],
read: (json['read'] as List<dynamic>?)
?.map((e) => Read.fromJson(e as Map<String, dynamic>))
.toList() ??
[],
);
}
ChannelState _$ChannelStateFromJson(Map<String, dynamic> json) => ChannelState(
channel: json['channel'] == null
? null
: ChannelModel.fromJson(json['channel'] as Map<String, dynamic>),
messages: (json['messages'] as List<dynamic>?)
?.map((e) => Message.fromJson(e as Map<String, dynamic>))
.toList() ??
const [],
members: (json['members'] as List<dynamic>?)
?.map((e) => Member.fromJson(e as Map<String, dynamic>))
.toList() ??
const [],
pinnedMessages: (json['pinned_messages'] as List<dynamic>?)
?.map((e) => Message.fromJson(e as Map<String, dynamic>))
.toList() ??
const [],
watcherCount: json['watcher_count'] as int?,
watchers: (json['watchers'] as List<dynamic>?)
?.map((e) => User.fromJson(e as Map<String, dynamic>))
.toList() ??
const [],
read: (json['read'] as List<dynamic>?)
?.map((e) => Read.fromJson(e as Map<String, dynamic>))
.toList() ??
const [],
);
Map<String, dynamic> _$ChannelStateToJson(ChannelState instance) =>
<String, dynamic>{
@@ -6,13 +6,11 @@ part of 'command.dart';
// JsonSerializableGenerator
// **************************************************************************
Command _$CommandFromJson(Map<String, dynamic> json) {
return Command(
name: json['name'] as String,
description: json['description'] as String,
args: json['args'] as String,
);
}
Command _$CommandFromJson(Map<String, dynamic> json) => Command(
name: json['name'] as String,
description: json['description'] as String,
args: json['args'] as String,
);
Map<String, dynamic> _$CommandToJson(Command instance) => <String, dynamic>{
'name': instance.name,
@@ -6,12 +6,10 @@ part of 'device.dart';
// JsonSerializableGenerator
// **************************************************************************
Device _$DeviceFromJson(Map<String, dynamic> json) {
return Device(
id: json['id'] as String,
pushProvider: json['push_provider'] as String,
);
}
Device _$DeviceFromJson(Map<String, dynamic> json) => Device(
id: json['id'] as String,
pushProvider: json['push_provider'] as String,
);
Map<String, dynamic> _$DeviceToJson(Device instance) => <String, dynamic>{
'id': instance.id,
@@ -92,7 +92,6 @@ class Event {
final bool isLocal;
/// Map of custom channel extraData
@JsonKey(defaultValue: {})
final Map<String, Object?> extraData;
/// Known top level fields.
@@ -184,7 +183,7 @@ class EventChannel extends ChannelModel {
DateTime? deletedAt,
required int memberCount,
Map<String, Object?>? extraData,
required int cooldown,
int cooldown = 0,
String? team,
}) : super(
id: id,
@@ -6,42 +6,40 @@ part of 'event.dart';
// JsonSerializableGenerator
// **************************************************************************
Event _$EventFromJson(Map<String, dynamic> json) {
return Event(
type: json['type'] as String,
cid: json['cid'] as String?,
connectionId: json['connection_id'] as String?,
createdAt: json['created_at'] == null
? null
: DateTime.parse(json['created_at'] as String),
me: json['me'] == null
? null
: OwnUser.fromJson(json['me'] as Map<String, dynamic>),
user: json['user'] == null
? null
: User.fromJson(json['user'] as Map<String, dynamic>),
message: json['message'] == null
? null
: Message.fromJson(json['message'] as Map<String, dynamic>),
totalUnreadCount: json['total_unread_count'] as int?,
unreadChannels: json['unread_channels'] as int?,
reaction: json['reaction'] == null
? null
: Reaction.fromJson(json['reaction'] as Map<String, dynamic>),
online: json['online'] as bool?,
channel: json['channel'] == null
? null
: EventChannel.fromJson(json['channel'] as Map<String, dynamic>),
member: json['member'] == null
? null
: Member.fromJson(json['member'] as Map<String, dynamic>),
channelId: json['channel_id'] as String?,
channelType: json['channel_type'] as String?,
parentId: json['parent_id'] as String?,
extraData: json['extra_data'] as Map<String, dynamic>? ?? {},
isLocal: json['is_local'] as bool? ?? false,
);
}
Event _$EventFromJson(Map<String, dynamic> json) => Event(
type: json['type'] as String? ?? 'local.event',
cid: json['cid'] as String?,
connectionId: json['connection_id'] as String?,
createdAt: json['created_at'] == null
? null
: DateTime.parse(json['created_at'] as String),
me: json['me'] == null
? null
: OwnUser.fromJson(json['me'] as Map<String, dynamic>),
user: json['user'] == null
? null
: User.fromJson(json['user'] as Map<String, dynamic>),
message: json['message'] == null
? null
: Message.fromJson(json['message'] as Map<String, dynamic>),
totalUnreadCount: json['total_unread_count'] as int?,
unreadChannels: json['unread_channels'] as int?,
reaction: json['reaction'] == null
? null
: Reaction.fromJson(json['reaction'] as Map<String, dynamic>),
online: json['online'] as bool?,
channel: json['channel'] == null
? null
: EventChannel.fromJson(json['channel'] as Map<String, dynamic>),
member: json['member'] == null
? null
: Member.fromJson(json['member'] as Map<String, dynamic>),
channelId: json['channel_id'] as String?,
channelType: json['channel_type'] as String?,
parentId: json['parent_id'] as String?,
extraData: json['extra_data'] as Map<String, dynamic>? ?? const {},
isLocal: json['is_local'] as bool? ?? false,
);
Map<String, dynamic> _$EventToJson(Event instance) => <String, dynamic>{
'type': instance.type,
@@ -64,30 +62,28 @@ Map<String, dynamic> _$EventToJson(Event instance) => <String, dynamic>{
'extra_data': instance.extraData,
};
EventChannel _$EventChannelFromJson(Map<String, dynamic> json) {
return EventChannel(
members: (json['members'] as List<dynamic>?)
?.map((e) => Member.fromJson(e as Map<String, dynamic>))
.toList(),
id: json['id'] as String?,
type: json['type'] as String?,
cid: json['cid'] as String,
config: ChannelConfig.fromJson(json['config'] as Map<String, dynamic>),
createdBy: json['created_by'] == null
? null
: User.fromJson(json['created_by'] as Map<String, dynamic>),
frozen: json['frozen'] as bool? ?? false,
lastMessageAt: json['last_message_at'] == null
? null
: DateTime.parse(json['last_message_at'] as String),
createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String),
deletedAt: json['deleted_at'] == null
? null
: DateTime.parse(json['deleted_at'] as String),
memberCount: json['member_count'] as int? ?? 0,
extraData: json['extra_data'] as Map<String, dynamic>? ?? {},
cooldown: json['cooldown'] as int? ?? 0,
team: json['team'] as String?,
);
}
EventChannel _$EventChannelFromJson(Map<String, dynamic> json) => EventChannel(
members: (json['members'] as List<dynamic>?)
?.map((e) => Member.fromJson(e as Map<String, dynamic>))
.toList(),
id: json['id'] as String?,
type: json['type'] as String?,
cid: json['cid'] as String,
config: ChannelConfig.fromJson(json['config'] as Map<String, dynamic>),
createdBy: json['created_by'] == null
? null
: User.fromJson(json['created_by'] as Map<String, dynamic>),
frozen: json['frozen'] as bool? ?? false,
lastMessageAt: json['last_message_at'] == null
? null
: DateTime.parse(json['last_message_at'] as String),
createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String),
deletedAt: json['deleted_at'] == null
? null
: DateTime.parse(json['deleted_at'] as String),
memberCount: json['member_count'] as int,
extraData: json['extra_data'] as Map<String, dynamic>?,
cooldown: json['cooldown'] as int? ?? 0,
team: json['team'] as String?,
);
@@ -102,6 +102,12 @@ class Filter extends Equatable {
this.key,
}) : operator = operator.rawValue;
/// An empty filter
const Filter.empty()
: value = const <String, Object?>{},
operator = null,
key = null;
/// Combines the provided filters and matches the values
/// matched by all filters.
factory Filter.and(List<Filter> filters) =>
@@ -161,6 +167,9 @@ class Filter extends Equatable {
factory Filter.exists(String key, {bool exists = true}) =>
Filter._(operator: FilterOperator.exists, key: key, value: exists);
/// Matches values that don't exist.
factory Filter.notExists(String key) => Filter.exists(key, exists: false);
/// Matches any list that contains the specified values
factory Filter.contains(String key, Object value) =>
Filter._(operator: FilterOperator.contains, key: key, value: value);
@@ -172,9 +181,6 @@ class Filter extends Equatable {
String? key,
}) = Filter.__;
/// An empty filter
factory Filter.empty() => const Filter.raw(value: {});
/// Creates a custom [Filter] from a raw map value
///
/// ```dart
@@ -42,7 +42,6 @@ class Member extends Equatable {
final DateTime? inviteRejectedAt;
/// True if the user has been invited to the channel
@JsonKey(defaultValue: false)
final bool invited;
/// The role of the user in the channel
@@ -52,15 +51,12 @@ class Member extends Equatable {
final String? userId;
/// True if the user is a moderator of the channel
@JsonKey(defaultValue: false)
final bool isModerator;
/// True if the member is banned from the channel
@JsonKey(defaultValue: false)
final bool banned;
/// True if the member is shadow banned from the channel
@JsonKey(defaultValue: false)
final bool shadowBanned;
/// The date of creation
@@ -6,31 +6,29 @@ part of 'member.dart';
// JsonSerializableGenerator
// **************************************************************************
Member _$MemberFromJson(Map<String, dynamic> json) {
return Member(
user: json['user'] == null
? null
: User.fromJson(json['user'] as Map<String, dynamic>),
inviteAcceptedAt: json['invite_accepted_at'] == null
? null
: DateTime.parse(json['invite_accepted_at'] as String),
inviteRejectedAt: json['invite_rejected_at'] == null
? null
: DateTime.parse(json['invite_rejected_at'] as String),
invited: json['invited'] as bool? ?? false,
role: json['role'] as String?,
userId: json['user_id'] as String?,
isModerator: json['is_moderator'] as bool? ?? false,
createdAt: json['created_at'] == null
? null
: DateTime.parse(json['created_at'] as String),
updatedAt: json['updated_at'] == null
? null
: DateTime.parse(json['updated_at'] as String),
banned: json['banned'] as bool? ?? false,
shadowBanned: json['shadow_banned'] as bool? ?? false,
);
}
Member _$MemberFromJson(Map<String, dynamic> json) => Member(
user: json['user'] == null
? null
: User.fromJson(json['user'] as Map<String, dynamic>),
inviteAcceptedAt: json['invite_accepted_at'] == null
? null
: DateTime.parse(json['invite_accepted_at'] as String),
inviteRejectedAt: json['invite_rejected_at'] == null
? null
: DateTime.parse(json['invite_rejected_at'] as String),
invited: json['invited'] as bool? ?? false,
role: json['role'] as String?,
userId: json['user_id'] as String?,
isModerator: json['is_moderator'] as bool? ?? false,
createdAt: json['created_at'] == null
? null
: DateTime.parse(json['created_at'] as String),
updatedAt: json['updated_at'] == null
? null
: DateTime.parse(json['updated_at'] as String),
banned: json['banned'] as bool? ?? false,
shadowBanned: json['shadow_banned'] as bool? ?? false,
);
Map<String, dynamic> _$MemberToJson(Member instance) => <String, dynamic>{
'user': instance.user?.toJson(),
@@ -98,23 +98,16 @@ class Message extends Equatable {
@JsonKey(
includeIfNull: false,
toJson: Serializer.readOnly,
defaultValue: 'regular',
)
final String type;
/// The list of attachments, either provided by the user or generated from a
/// command or as a result of URL scraping.
@JsonKey(
includeIfNull: false,
defaultValue: [],
)
@JsonKey(includeIfNull: false)
final List<Attachment> attachments;
/// The list of user mentioned in the message
@JsonKey(
toJson: User.toIds,
defaultValue: [],
)
@JsonKey(toJson: User.toIds)
final List<User> mentionedUsers;
/// A map describing the count of number of every reaction
@@ -155,14 +148,12 @@ class Message extends Equatable {
final bool? showInChannel;
/// If true the message is silent
@JsonKey(defaultValue: false)
final bool silent;
/// If true the message is shadowed
@JsonKey(
includeIfNull: false,
toJson: Serializer.readOnly,
defaultValue: false,
)
final bool shadowed;
@@ -183,7 +174,6 @@ class Message extends Equatable {
final User? user;
/// If true the message is pinned
@JsonKey(defaultValue: false)
final bool pinned;
/// Reserved field indicating when the message was pinned
@@ -200,10 +190,7 @@ class Message extends Equatable {
final User? pinnedBy;
/// Message custom extraData
@JsonKey(
includeIfNull: false,
defaultValue: {},
)
@JsonKey(includeIfNull: false)
final Map<String, Object?> extraData;
/// True if the message is a system info
@@ -6,72 +6,70 @@ part of 'message.dart';
// JsonSerializableGenerator
// **************************************************************************
Message _$MessageFromJson(Map<String, dynamic> json) {
return Message(
id: json['id'] as String?,
text: json['text'] as String?,
type: json['type'] as String? ?? 'regular',
attachments: (json['attachments'] as List<dynamic>?)
?.map((e) => Attachment.fromJson(e as Map<String, dynamic>))
.toList() ??
[],
mentionedUsers: (json['mentioned_users'] as List<dynamic>?)
?.map((e) => User.fromJson(e as Map<String, dynamic>))
.toList() ??
[],
silent: json['silent'] as bool? ?? false,
shadowed: json['shadowed'] as bool? ?? false,
reactionCounts: (json['reaction_counts'] as Map<String, dynamic>?)?.map(
(k, e) => MapEntry(k, e as int),
),
reactionScores: (json['reaction_scores'] as Map<String, dynamic>?)?.map(
(k, e) => MapEntry(k, e as int),
),
latestReactions: (json['latest_reactions'] as List<dynamic>?)
?.map((e) => Reaction.fromJson(e as Map<String, dynamic>))
.toList(),
ownReactions: (json['own_reactions'] as List<dynamic>?)
?.map((e) => Reaction.fromJson(e as Map<String, dynamic>))
.toList(),
parentId: json['parent_id'] as String?,
quotedMessage: json['quoted_message'] == null
? null
: Message.fromJson(json['quoted_message'] as Map<String, dynamic>),
quotedMessageId: json['quoted_message_id'] as String?,
replyCount: json['reply_count'] as int?,
threadParticipants: (json['thread_participants'] as List<dynamic>?)
?.map((e) => User.fromJson(e as Map<String, dynamic>))
.toList(),
showInChannel: json['show_in_channel'] as bool?,
command: json['command'] as String?,
createdAt: json['created_at'] == null
? null
: DateTime.parse(json['created_at'] as String),
updatedAt: json['updated_at'] == null
? null
: DateTime.parse(json['updated_at'] as String),
user: json['user'] == null
? null
: User.fromJson(json['user'] as Map<String, dynamic>),
pinned: json['pinned'] as bool? ?? false,
pinnedAt: json['pinned_at'] == null
? null
: DateTime.parse(json['pinned_at'] as String),
pinExpires: json['pin_expires'] == null
? null
: DateTime.parse(json['pin_expires'] as String),
pinnedBy: json['pinned_by'] == null
? null
: User.fromJson(json['pinned_by'] as Map<String, dynamic>),
extraData: json['extra_data'] as Map<String, dynamic>? ?? {},
deletedAt: json['deleted_at'] == null
? null
: DateTime.parse(json['deleted_at'] as String),
i18n: (json['i18n'] as Map<String, dynamic>?)?.map(
(k, e) => MapEntry(k, e as String),
),
);
}
Message _$MessageFromJson(Map<String, dynamic> json) => Message(
id: json['id'] as String?,
text: json['text'] as String?,
type: json['type'] as String? ?? 'regular',
attachments: (json['attachments'] as List<dynamic>?)
?.map((e) => Attachment.fromJson(e as Map<String, dynamic>))
.toList() ??
const [],
mentionedUsers: (json['mentioned_users'] as List<dynamic>?)
?.map((e) => User.fromJson(e as Map<String, dynamic>))
.toList() ??
const [],
silent: json['silent'] as bool? ?? false,
shadowed: json['shadowed'] as bool? ?? false,
reactionCounts: (json['reaction_counts'] as Map<String, dynamic>?)?.map(
(k, e) => MapEntry(k, e as int),
),
reactionScores: (json['reaction_scores'] as Map<String, dynamic>?)?.map(
(k, e) => MapEntry(k, e as int),
),
latestReactions: (json['latest_reactions'] as List<dynamic>?)
?.map((e) => Reaction.fromJson(e as Map<String, dynamic>))
.toList(),
ownReactions: (json['own_reactions'] as List<dynamic>?)
?.map((e) => Reaction.fromJson(e as Map<String, dynamic>))
.toList(),
parentId: json['parent_id'] as String?,
quotedMessage: json['quoted_message'] == null
? null
: Message.fromJson(json['quoted_message'] as Map<String, dynamic>),
quotedMessageId: json['quoted_message_id'] as String?,
replyCount: json['reply_count'] as int? ?? 0,
threadParticipants: (json['thread_participants'] as List<dynamic>?)
?.map((e) => User.fromJson(e as Map<String, dynamic>))
.toList(),
showInChannel: json['show_in_channel'] as bool?,
command: json['command'] as String?,
createdAt: json['created_at'] == null
? null
: DateTime.parse(json['created_at'] as String),
updatedAt: json['updated_at'] == null
? null
: DateTime.parse(json['updated_at'] as String),
user: json['user'] == null
? null
: User.fromJson(json['user'] as Map<String, dynamic>),
pinned: json['pinned'] as bool? ?? false,
pinnedAt: json['pinned_at'] == null
? null
: DateTime.parse(json['pinned_at'] as String),
pinExpires: json['pin_expires'] == null
? null
: DateTime.parse(json['pin_expires'] as String),
pinnedBy: json['pinned_by'] == null
? null
: User.fromJson(json['pinned_by'] as Map<String, dynamic>),
extraData: json['extra_data'] as Map<String, dynamic>? ?? const {},
deletedAt: json['deleted_at'] == null
? null
: DateTime.parse(json['deleted_at'] as String),
i18n: (json['i18n'] as Map<String, dynamic>?)?.map(
(k, e) => MapEntry(k, e as String),
),
);
Map<String, dynamic> _$MessageToJson(Message instance) {
final val = <String, dynamic>{
@@ -6,11 +6,9 @@ part of 'mute.dart';
// JsonSerializableGenerator
// **************************************************************************
Mute _$MuteFromJson(Map<String, dynamic> json) {
return Mute(
user: User.fromJson(json['user'] as Map<String, dynamic>),
channel: ChannelModel.fromJson(json['channel'] as Map<String, dynamic>),
createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String),
);
}
Mute _$MuteFromJson(Map<String, dynamic> json) => Mute(
user: User.fromJson(json['user'] as Map<String, dynamic>),
channel: ChannelModel.fromJson(json['channel'] as Map<String, dynamic>),
createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String),
);
@@ -17,7 +17,7 @@ class OwnUser extends User {
this.devices = const [],
this.mutes = const [],
this.totalUnreadCount = 0,
this.unreadChannels,
this.unreadChannels = 0,
this.channelMutes = const [],
required String id,
String? role,
@@ -54,8 +54,6 @@ class OwnUser extends User {
factory OwnUser.fromUser(User user) => OwnUser(
id: user.id,
role: user.role,
name: user.name,
image: user.image,
createdAt: user.createdAt,
updatedAt: user.updatedAt,
lastActive: user.lastActive,
@@ -116,8 +114,6 @@ class OwnUser extends User {
return copyWith(
id: other.id,
role: other.role,
name: other.name,
image: other.image,
banned: other.banned,
channelMutes: other.channelMutes,
createdAt: other.createdAt,
@@ -135,24 +131,24 @@ class OwnUser extends User {
}
/// List of user devices.
@JsonKey(includeIfNull: false, defaultValue: <Device>[])
@JsonKey(includeIfNull: false)
final List<Device> devices;
/// List of users muted by the user.
@JsonKey(includeIfNull: false, defaultValue: <Mute>[])
@JsonKey(includeIfNull: false)
final List<Mute> mutes;
/// List of users muted by the user.
@JsonKey(includeIfNull: false, defaultValue: <Mute>[])
@JsonKey(includeIfNull: false)
final List<Mute> channelMutes;
/// Total unread messages by the user.
@JsonKey(includeIfNull: false, defaultValue: 0)
@JsonKey(includeIfNull: false)
final int totalUnreadCount;
/// Total unread channels by the user.
@JsonKey(includeIfNull: false)
final int? unreadChannels;
final int unreadChannels;
/// Known top level fields.
///
@@ -6,39 +6,37 @@ part of 'own_user.dart';
// JsonSerializableGenerator
// **************************************************************************
OwnUser _$OwnUserFromJson(Map<String, dynamic> json) {
return OwnUser(
devices: (json['devices'] as List<dynamic>?)
?.map((e) => Device.fromJson(e as Map<String, dynamic>))
.toList() ??
[],
mutes: (json['mutes'] as List<dynamic>?)
?.map((e) => Mute.fromJson(e as Map<String, dynamic>))
.toList() ??
[],
totalUnreadCount: json['total_unread_count'] as int? ?? 0,
unreadChannels: json['unread_channels'] as int?,
channelMutes: (json['channel_mutes'] as List<dynamic>?)
?.map((e) => Mute.fromJson(e as Map<String, dynamic>))
.toList() ??
[],
id: json['id'] as String,
role: json['role'] as String?,
createdAt: json['created_at'] == null
? null
: DateTime.parse(json['created_at'] as String),
updatedAt: json['updated_at'] == null
? null
: DateTime.parse(json['updated_at'] as String),
lastActive: json['last_active'] == null
? null
: DateTime.parse(json['last_active'] as String),
online: json['online'] as bool? ?? false,
extraData: json['extra_data'] as Map<String, dynamic>? ?? {},
banned: json['banned'] as bool? ?? false,
teams:
(json['teams'] as List<dynamic>?)?.map((e) => e as String).toList() ??
[],
language: json['language'] as String?,
);
}
OwnUser _$OwnUserFromJson(Map<String, dynamic> json) => OwnUser(
devices: (json['devices'] as List<dynamic>?)
?.map((e) => Device.fromJson(e as Map<String, dynamic>))
.toList() ??
const [],
mutes: (json['mutes'] as List<dynamic>?)
?.map((e) => Mute.fromJson(e as Map<String, dynamic>))
.toList() ??
const [],
totalUnreadCount: json['total_unread_count'] as int? ?? 0,
unreadChannels: json['unread_channels'] as int? ?? 0,
channelMutes: (json['channel_mutes'] as List<dynamic>?)
?.map((e) => Mute.fromJson(e as Map<String, dynamic>))
.toList() ??
const [],
id: json['id'] as String,
role: json['role'] as String?,
createdAt: json['created_at'] == null
? null
: DateTime.parse(json['created_at'] as String),
updatedAt: json['updated_at'] == null
? null
: DateTime.parse(json['updated_at'] as String),
lastActive: json['last_active'] == null
? null
: DateTime.parse(json['last_active'] as String),
online: json['online'] as bool? ?? false,
extraData: json['extra_data'] as Map<String, dynamic>? ?? const {},
banned: json['banned'] as bool? ?? false,
teams:
(json['teams'] as List<dynamic>?)?.map((e) => e as String).toList() ??
const [],
language: json['language'] as String?,
);
@@ -1,6 +1,6 @@
import 'package:json_annotation/json_annotation.dart';
import 'package:stream_chat/src/core/util/serializer.dart';
import 'package:stream_chat/src/core/models/user.dart';
import 'package:stream_chat/src/core/util/serializer.dart';
part 'reaction.g.dart';
@@ -41,7 +41,6 @@ class Reaction {
final User? user;
/// The score of the reaction (ie. number of reactions sent)
@JsonKey(defaultValue: 0)
final int score;
/// The userId that sent the reaction
@@ -49,10 +48,7 @@ class Reaction {
final String? userId;
/// Reaction custom extraData
@JsonKey(
includeIfNull: false,
defaultValue: {},
)
@JsonKey(includeIfNull: false)
final Map<String, Object?> extraData;
/// Map of custom user extraData
@@ -6,21 +6,19 @@ part of 'reaction.dart';
// JsonSerializableGenerator
// **************************************************************************
Reaction _$ReactionFromJson(Map<String, dynamic> json) {
return Reaction(
messageId: json['message_id'] as String?,
createdAt: json['created_at'] == null
? null
: DateTime.parse(json['created_at'] as String),
type: json['type'] as String,
user: json['user'] == null
? null
: User.fromJson(json['user'] as Map<String, dynamic>),
userId: json['user_id'] as String?,
score: json['score'] as int? ?? 0,
extraData: json['extra_data'] as Map<String, dynamic>? ?? {},
);
}
Reaction _$ReactionFromJson(Map<String, dynamic> json) => Reaction(
messageId: json['message_id'] as String?,
createdAt: json['created_at'] == null
? null
: DateTime.parse(json['created_at'] as String),
type: json['type'] as String,
user: json['user'] == null
? null
: User.fromJson(json['user'] as Map<String, dynamic>),
userId: json['user_id'] as String?,
score: json['score'] as int? ?? 0,
extraData: json['extra_data'] as Map<String, dynamic>? ?? const {},
);
Map<String, dynamic> _$ReactionToJson(Reaction instance) {
final val = <String, dynamic>{
@@ -23,7 +23,6 @@ class Read {
final User user;
/// Number of unread messages
@JsonKey(defaultValue: 0)
final int unreadMessages;
/// Serialize to json
@@ -6,13 +6,11 @@ part of 'read.dart';
// JsonSerializableGenerator
// **************************************************************************
Read _$ReadFromJson(Map<String, dynamic> json) {
return Read(
lastRead: DateTime.parse(json['last_read'] as String),
user: User.fromJson(json['user'] as Map<String, dynamic>),
unreadMessages: json['unread_messages'] as int? ?? 0,
);
}
Read _$ReadFromJson(Map<String, dynamic> json) => Read(
lastRead: DateTime.parse(json['last_read'] as String),
user: User.fromJson(json['user'] as Map<String, dynamic>),
unreadMessages: json['unread_messages'] as int? ?? 0,
);
Map<String, dynamic> _$ReadToJson(Read instance) => <String, dynamic>{
'last_read': instance.lastRead.toIso8601String(),
@@ -100,7 +100,6 @@ class User extends Equatable {
@JsonKey(
includeIfNull: false,
toJson: Serializer.readOnly,
defaultValue: <String>[],
)
final List<String> teams;
@@ -118,19 +117,20 @@ class User extends Equatable {
/// True if user is online.
@JsonKey(
includeIfNull: false, toJson: Serializer.readOnly, defaultValue: false)
includeIfNull: false,
toJson: Serializer.readOnly,
)
final bool online;
/// True if user is banned from the chat.
@JsonKey(
includeIfNull: false, toJson: Serializer.readOnly, defaultValue: false)
includeIfNull: false,
toJson: Serializer.readOnly,
)
final bool banned;
/// Map of custom user extraData.
@JsonKey(
includeIfNull: false,
defaultValue: {},
)
@JsonKey(includeIfNull: false)
final Map<String, Object?> extraData;
/// The language this user prefers.
@@ -6,28 +6,26 @@ part of 'user.dart';
// JsonSerializableGenerator
// **************************************************************************
User _$UserFromJson(Map<String, dynamic> json) {
return User(
id: json['id'] as String,
role: json['role'] as String?,
createdAt: json['created_at'] == null
? null
: DateTime.parse(json['created_at'] as String),
updatedAt: json['updated_at'] == null
? null
: DateTime.parse(json['updated_at'] as String),
lastActive: json['last_active'] == null
? null
: DateTime.parse(json['last_active'] as String),
extraData: json['extra_data'] as Map<String, dynamic>? ?? {},
online: json['online'] as bool? ?? false,
banned: json['banned'] as bool? ?? false,
teams:
(json['teams'] as List<dynamic>?)?.map((e) => e as String).toList() ??
[],
language: json['language'] as String?,
);
}
User _$UserFromJson(Map<String, dynamic> json) => User(
id: json['id'] as String,
role: json['role'] as String?,
createdAt: json['created_at'] == null
? null
: DateTime.parse(json['created_at'] as String),
updatedAt: json['updated_at'] == null
? null
: DateTime.parse(json['updated_at'] as String),
lastActive: json['last_active'] == null
? null
: DateTime.parse(json['last_active'] as String),
extraData: json['extra_data'] as Map<String, dynamic>? ?? const {},
online: json['online'] as bool? ?? false,
banned: json['banned'] as bool? ?? false,
teams:
(json['teams'] as List<dynamic>?)?.map((e) => e as String).toList() ??
const [],
language: json['language'] as String?,
);
Map<String, dynamic> _$UserToJson(User instance) {
final val = <String, dynamic>{
@@ -6,15 +6,15 @@ import 'package:logging/logging.dart';
import 'package:meta/meta.dart';
import 'package:rxdart/rxdart.dart';
import 'package:stream_chat/src/core/error/error.dart';
import 'package:stream_chat/src/ws/connection_status.dart';
import 'package:stream_chat/src/core/http/token.dart';
import 'package:stream_chat/src/core/http/token_manager.dart';
import 'package:stream_chat/src/core/models/event.dart';
import 'package:stream_chat/src/core/models/user.dart';
import 'package:stream_chat/src/event_type.dart';
import 'package:stream_chat/src/ws/connection_status.dart';
import 'package:stream_chat/src/ws/timer_helper.dart';
import 'package:web_socket_channel/web_socket_channel.dart';
import 'package:web_socket_channel/status.dart' as status;
import 'package:web_socket_channel/web_socket_channel.dart';
/// Typedef which exposes an [Event] as the only parameter.
typedef EventHandler = void Function(Event);
+1 -1
View File
@@ -3,4 +3,4 @@ import 'package:stream_chat/src/client/client.dart';
/// Current package version
/// Used in [StreamChatClient] to build the `x-stream-client` header
// ignore: constant_identifier_names
const PACKAGE_VERSION = '2.2.1';
const PACKAGE_VERSION = '3.1.1';
+3 -3
View File
@@ -1,7 +1,7 @@
name: stream_chat
homepage: https://getstream.io/
description: The official Dart client for Stream Chat, a service for building chat applications.
version: 2.2.1
version: 3.1.1
repository: https://github.com/GetStream/stream-chat-flutter
issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues
@@ -28,6 +28,6 @@ dependencies:
dev_dependencies:
build_runner: ^2.0.1
freezed: ^0.14.1+3
json_serializable: ^4.1.0
json_serializable: ^5.0.2
mocktail: ^0.1.1
test: ^1.17.7
test: ^1.18.2
@@ -334,20 +334,87 @@ void main() {
expectLater(
// skipping first seed message list -> [] messages
channel.state?.messagesStream.skip(1),
emitsInOrder([
emitsInOrder(
[
isSameMessageAs(
message.copyWith(status: MessageSendingStatus.sending),
matchSendingStatus: true,
),
// preparing attachments to upload
[
isSameMessageAs(
message.copyWith(
status: MessageSendingStatus.sending,
attachments: [
...attachments.map((it) => it.copyWith(
uploadState: const UploadState.preparing()))
],
),
matchSendingStatus: true,
matchAttachments: true,
matchAttachmentsUploadState: true,
),
],
// 0th attachment is successfully uploaded
[
isSameMessageAs(
message.copyWith(
status: MessageSendingStatus.sending,
attachments: [...attachments]..[0] =
attachments[0].copyWith(
uploadState: const UploadState.success(),
),
),
matchSendingStatus: true,
matchAttachments: true,
matchAttachmentsUploadState: true,
),
],
// 0th and 1st attachment is successfully uploaded
[
isSameMessageAs(
message.copyWith(
status: MessageSendingStatus.sending,
attachments: [...attachments]
..[0] = attachments[0].copyWith(
uploadState: const UploadState.success(),
)
..[1] = attachments[1].copyWith(
uploadState: const UploadState.success(),
),
),
matchSendingStatus: true,
matchAttachments: true,
matchAttachmentsUploadState: true,
),
],
// all the attachments are successfully uploaded
[
isSameMessageAs(
message.copyWith(
status: MessageSendingStatus.sending,
attachments: [
...attachments.map((it) =>
it.copyWith(uploadState: const UploadState.success()))
],
),
matchSendingStatus: true,
matchAttachments: true,
matchAttachmentsUploadState: true,
),
],
[
isSameMessageAs(
message.copyWith(
status: MessageSendingStatus.sent,
attachments: [
...attachments.map((it) =>
it.copyWith(uploadState: const UploadState.success()))
],
),
matchSendingStatus: true,
matchAttachments: true,
matchAttachmentsUploadState: true,
),
],
],
[
isSameMessageAs(
message.copyWith(status: MessageSendingStatus.sent),
matchSendingStatus: true,
),
],
]),
),
);
final res = await channel.sendMessage(message);
@@ -472,20 +539,87 @@ void main() {
expectLater(
// skipping first seed message list -> [] messages
channel.state?.messagesStream.skip(1),
emitsInOrder([
emitsInOrder(
[
isSameMessageAs(
message.copyWith(status: MessageSendingStatus.updating),
matchSendingStatus: true,
),
// preparing attachments to upload
[
isSameMessageAs(
message.copyWith(
status: MessageSendingStatus.updating,
attachments: [
...attachments.map((it) => it.copyWith(
uploadState: const UploadState.preparing()))
],
),
matchSendingStatus: true,
matchAttachments: true,
matchAttachmentsUploadState: true,
),
],
// 0th attachment is successfully uploaded
[
isSameMessageAs(
message.copyWith(
status: MessageSendingStatus.updating,
attachments: [...attachments]..[0] =
attachments[0].copyWith(
uploadState: const UploadState.success(),
),
),
matchSendingStatus: true,
matchAttachments: true,
matchAttachmentsUploadState: true,
),
],
// 0th and 1st attachment is successfully uploaded
[
isSameMessageAs(
message.copyWith(
status: MessageSendingStatus.updating,
attachments: [...attachments]
..[0] = attachments[0].copyWith(
uploadState: const UploadState.success(),
)
..[1] = attachments[1].copyWith(
uploadState: const UploadState.success(),
),
),
matchSendingStatus: true,
matchAttachments: true,
matchAttachmentsUploadState: true,
),
],
// all the attachments are successfully uploaded
[
isSameMessageAs(
message.copyWith(
status: MessageSendingStatus.updating,
attachments: [
...attachments.map((it) =>
it.copyWith(uploadState: const UploadState.success()))
],
),
matchSendingStatus: true,
matchAttachments: true,
matchAttachmentsUploadState: true,
),
],
[
isSameMessageAs(
message.copyWith(
status: MessageSendingStatus.sent,
attachments: [
...attachments.map((it) =>
it.copyWith(uploadState: const UploadState.success()))
],
),
matchSendingStatus: true,
matchAttachments: true,
matchAttachmentsUploadState: true,
),
],
],
[
isSameMessageAs(
message.copyWith(status: MessageSendingStatus.sent),
matchSendingStatus: true,
),
],
]),
),
);
final res = await channel.updateMessage(message);
@@ -2313,5 +2313,27 @@ void main() {
)).called(1);
verifyNoMoreInteractions(api.message);
});
test(
'''setting the `currentUser` should also compute and update the unreadCounts''',
() {
final state = client.state;
final initialUser = OwnUser.fromUser(user);
expect(state.currentUser, initialUser);
expect(state.totalUnreadCount, 0);
expect(state.unreadChannels, 0);
final updateUser = initialUser.copyWith(
totalUnreadCount: 33,
unreadChannels: 33,
);
state.currentUser = updateUser;
expect(state.currentUser, updateUser);
expect(state.totalUnreadCount, 33);
expect(state.unreadChannels, 33);
},
);
});
}
@@ -86,6 +86,24 @@ void main() {
},
);
test(
'should throw if `pagination.offset` and `sort` both are provided',
() async {
final filter = Filter.in_('cid', const ['test-cid-1', 'test-cid-2']);
const sort = [SortOption<ChannelModel>('test-field')];
const pagination = PaginationParams(offset: 10);
try {
await generalApi.searchMessages(
filter,
sort: sort,
pagination: pagination,
);
} catch (e) {
expect(e, isA<AssertionError>());
}
},
);
test('should run successfully with `query`', () async {
final filter = Filter.in_('cid', const ['test-cid-1', 'test-cid-2']);
const query = 'test-query';
@@ -9,11 +9,23 @@ void main() {
expect(j, {'field': 'name', 'direction': -1});
});
test('PaginationParams', () {
const option = PaginationParams();
final j = option.toJson();
expect(j, containsPair('limit', 10));
expect(j, containsPair('offset', 0));
group('PaginationParams', () {
test('default', () {
const option = PaginationParams();
final j = option.toJson();
expect(j, containsPair('limit', 10));
});
test(
'should throw if non-zero `offset` and `next` both are provided',
() {
try {
PaginationParams(offset: 10, next: 'next-message-id');
} catch (e) {
expect(e, isA<AssertionError>());
}
},
);
});
});
}
@@ -1,6 +1,5 @@
import 'dart:convert';
import 'package:test/test.dart';
import 'package:stream_chat/src/core/api/responses.dart';
import 'package:stream_chat/src/core/models/device.dart';
import 'package:stream_chat/src/core/models/member.dart';
@@ -8,6 +7,7 @@ import 'package:stream_chat/src/core/models/message.dart';
import 'package:stream_chat/src/core/models/reaction.dart';
import 'package:stream_chat/src/core/models/read.dart';
import 'package:stream_chat/stream_chat.dart';
import 'package:test/test.dart';
void main() {
group('src/api/responses', () {
@@ -1,5 +1,6 @@
import 'package:stream_chat/src/core/models/action.dart';
import 'package:stream_chat/src/core/models/attachment.dart';
import 'package:stream_chat/src/core/models/attachment_file.dart';
import 'package:test/test.dart';
import '../../utils.dart';
@@ -41,5 +42,59 @@ void main() {
},
);
});
test('fileName, mimeType property and extraData manipulation', () {
final file = AttachmentFile(size: 3, path: 'myfolder/myfile.txt');
final attachment = Attachment(file: file);
expect(attachment.fileSize, 3);
expect(attachment.mimeType, 'text/plain');
expect(attachment.toJson(), {
'title': 'myfile.txt',
'actions': [],
'file_size': 3,
'mime_type': 'text/plain'
});
expect(Attachment.fromJson(attachment.toJson()).toJson(), {
'title': 'myfile.txt',
'actions': [],
'file_size': 3,
'mime_type': 'text/plain'
});
// Setting the size and mimeType using extraData should work fine
var newAttachment = Attachment(
extraData: const {
'file_size': 6,
'mime_type': 'application/pdf',
},
);
expect(newAttachment.extraData['file_size'], 6);
expect(newAttachment.extraData['mime_type'], 'application/pdf');
expect(newAttachment.fileSize, 6);
expect(newAttachment.mimeType, 'application/pdf');
// switching a new file should update size and mimeType
final fileTwo = AttachmentFile(size: 12, path: 'myfolder/fileTwo.pdf');
newAttachment = attachment.copyWith(file: fileTwo);
expect(newAttachment.extraData['file_size'], 12);
expect(newAttachment.extraData['mime_type'], 'application/pdf');
expect(newAttachment.fileSize, 12);
expect(newAttachment.mimeType, 'application/pdf');
// if file is available, should override size and mimeType.
final fileThree = AttachmentFile(size: 9, path: 'myfolder/fileThree.png');
newAttachment = attachment.copyWith(file: fileThree, extraData: {
'file_size': 88,
'mime_type': 'application/pdf',
});
expect(newAttachment.extraData['file_size'], 9);
expect(newAttachment.extraData['mime_type'], 'image/png');
expect(newAttachment.fileSize, 9);
expect(newAttachment.mimeType, 'image/png');
});
});
}
@@ -1,7 +1,7 @@
import 'dart:convert';
import 'package:test/test.dart';
import 'package:stream_chat/src/core/models/filter.dart';
import 'package:test/test.dart';
void main() {
group('operators', () {
@@ -114,7 +114,7 @@ void main() {
test('notExists', () {
const key = 'testKey';
final filter = Filter.exists(key, exists: false);
final filter = Filter.notExists(key);
expect(filter.key, key);
expect(filter.value, isFalse);
expect(filter.operator, FilterOperator.exists.rawValue);
@@ -139,7 +139,7 @@ void main() {
});
test('empty', () {
final filter = Filter.empty();
const filter = Filter.empty();
expect(filter.value, {});
});
@@ -226,6 +226,12 @@ void main() {
json.encode(value),
);
});
test('empty', () {
const filter = Filter.empty();
final encoded = json.encode(filter);
expect(encoded, '{}');
});
});
test('groupedFilter', () {
@@ -175,5 +175,26 @@ void main() {
expect(newUser.teams, ['team1', 'team2']);
expect(newUser.language, 'fr');
});
test(
'fromUser should not override name with id if not available in extraData',
() {
final user = User(id: 'test-id');
expect(user.id, 'test-id');
expect(user.name, 'test-id');
final encodedUser = user.toJson();
expect(encodedUser['id'], 'test-id');
expect(encodedUser['name'], null);
final ownUser = OwnUser.fromUser(user);
expect(user.id, 'test-id');
expect(user.name, 'test-id');
final encodedOwnUser = ownUser.toJson();
expect(encodedOwnUser['id'], 'test-id');
expect(encodedOwnUser['name'], null);
},
);
});
}
@@ -1,5 +1,5 @@
import 'package:test/test.dart';
import 'package:stream_chat/src/core/util/serializer.dart';
import 'package:test/test.dart';
void main() {
group('src/models/serialization', () {
@@ -1,5 +1,5 @@
import 'package:test/test.dart';
import 'package:stream_chat/src/core/util/extension.dart';
import 'package:test/test.dart';
void main() {
test('`.withNullifyer` converts the type into non-nullable', () {
+1 -1
View File
@@ -6,11 +6,11 @@ import 'package:rxdart/rxdart.dart';
import 'package:stream_chat/src/core/api/channel_api.dart';
import 'package:stream_chat/src/core/api/device_api.dart';
import 'package:stream_chat/src/core/api/general_api.dart';
import 'package:stream_chat/src/core/api/guest_api.dart';
import 'package:stream_chat/src/core/api/message_api.dart';
import 'package:stream_chat/src/core/api/moderation_api.dart';
import 'package:stream_chat/src/core/api/stream_chat_api.dart';
import 'package:stream_chat/src/core/api/user_api.dart';
import 'package:stream_chat/src/core/api/guest_api.dart';
import 'package:stream_chat/src/core/http/token.dart';
import 'package:stream_chat/src/core/http/token_manager.dart';
import 'package:stream_chat/src/ws/websocket.dart';
@@ -1,6 +1,7 @@
import 'package:collection/collection.dart';
import 'package:dio/dio.dart' show MultipartFile;
import 'package:stream_chat/src/client/channel.dart';
import 'package:stream_chat/src/core/models/attachment.dart';
import 'package:stream_chat/src/core/models/channel_state.dart';
import 'package:stream_chat/src/core/models/event.dart';
import 'package:stream_chat/src/core/models/message.dart';
@@ -46,12 +47,16 @@ Matcher isSameMessageAs(
bool matchText = false,
bool matchReactions = false,
bool matchSendingStatus = false,
bool matchAttachments = false,
bool matchAttachmentsUploadState = false,
}) =>
_IsSameMessageAs(
targetMessage: targetMessage,
matchText: matchText,
matchReactions: matchReactions,
matchSendingStatus: matchSendingStatus,
matchAttachments: matchAttachments,
matchAttachmentsUploadState: matchAttachmentsUploadState,
);
class _IsSameMessageAs extends Matcher {
@@ -60,12 +65,16 @@ class _IsSameMessageAs extends Matcher {
this.matchText = false,
this.matchReactions = false,
this.matchSendingStatus = false,
this.matchAttachments = false,
this.matchAttachmentsUploadState = false,
});
final Message targetMessage;
final bool matchText;
final bool matchReactions;
final bool matchSendingStatus;
final bool matchAttachments;
final bool matchAttachmentsUploadState;
@override
Description describe(Description description) =>
@@ -96,6 +105,56 @@ class _IsSameMessageAs extends Matcher {
?.map((it) => '${it.type}-${it.messageId}')
.toList());
}
if (matchAttachments) {
bool matchAttachments() {
final attachments = message.attachments;
final targetAttachments = targetMessage.attachments;
if (identical(attachments, targetAttachments)) return true;
final length = attachments.length;
if (length != targetAttachments.length) return false;
for (var i = 0; i < length; i++) {
if (!isSameAttachmentAs(
attachments[i],
matchUploadState: matchAttachmentsUploadState,
).matches(targetAttachments[i], matchState)) return false;
}
return true;
}
matches &= matchAttachments();
}
return matches;
}
}
Matcher isSameAttachmentAs(
Attachment targetAttachment, {
bool matchUploadState = false,
}) =>
_IsSameAttachmentAs(
targetAttachment: targetAttachment,
matchUploadState: matchUploadState,
);
class _IsSameAttachmentAs extends Matcher {
const _IsSameAttachmentAs({
required this.targetAttachment,
this.matchUploadState = false,
});
final Attachment targetAttachment;
final bool matchUploadState;
@override
Description describe(Description description) =>
description.add('is same attachment as $targetAttachment');
@override
bool matches(covariant Attachment attachment, Map matchState) {
var matches = attachment.id == targetAttachment.id;
if (matchUploadState) {
matches &= attachment.uploadState == targetAttachment.uploadState;
}
return matches;
}
}
@@ -1,11 +1,11 @@
import 'dart:async';
import 'dart:convert';
import 'package:mocktail/mocktail.dart';
import 'package:stream_chat/src/core/http/token_manager.dart';
import 'package:stream_chat/src/ws/websocket.dart';
import 'package:stream_chat/stream_chat.dart';
import 'package:test/test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:stream_chat/src/ws/websocket.dart';
import 'package:web_socket_channel/web_socket_channel.dart';
import '../fakes.dart';
+130 -48
View File
@@ -1,3 +1,98 @@
## Upcoming
- Updated Dart SDK constraints to `>=2.14.0 <3.0.0`
## 3.1.1
- Updated `stream_chat_flutter_core` dependency to [`3.1.1`](https://pub.dev/packages/stream_chat_flutter_core/changelog).
- Updated `file_picker`, `image_gallery_saver`, and `video_thumbnail` to the latest versions.
🐞 Fixed
- [[#687]](https://github.com/GetStream/stream-chat-flutter/issues/687): Fix Users losing their place in the conversation after replying in threads.
- Fixed floating date stream subscription causing "Bad state: stream has already been listened.” error.
- Fixed `String` capitalize extension not working on empty strings.
✅ Added
- Added `MessageInput.customOverlays` property to add custom overlays to the message input.
- Added `MessageInput.mentionAllAppUsers` property to mention all app users in the message input.
- The `MessageInput` now supports local search for channels with less than 100 members.
- Added `MessageListView.paginationLoadingIndicatorBuilder` to override the default loading indicator shown while paginating the message list.
- Added new `linkBackgroundColor` in `MessageTheme` for setting background colors of link attachments.
⚠️ Deprecated
- `MessageInput.mentionsTileBuilder` is now deprecated in favor of `MessageInput.userMentionsTileBuilder`.
- `MentionTile` is now deprecated in favor of `UserMentionsTile`.
## 3.0.0
- Updated `stream_chat_flutter_core` dependency to [`3.0.0`](https://pub.dev/packages/stream_chat_flutter_core/changelog).
🛑️ Breaking Changes from `2.2.1`
- `UserListView` `filter` property now is non-nullable.
🐞 Fixed
- [[#668]](https://github.com/GetStream/stream-chat-flutter/issues/668): Fix `MessageInput` rendering errors in case
there are no actions available to show.
- [[#349]](https://github.com/GetStream/stream-chat-flutter/issues/349): Fix `MessageInput` attachment render overflow error.
- `MessageInput` overlays now follow the `MessageInput` focus.
- [[#674]](https://github.com/GetStream/stream-chat-flutter/issues/674): Check scrollController is attached before calling jump in MessageListView.
- Fixed `MessageListView` header and footer when `reverse: false`.
🔄 Changed
- Animation curves changed from default `Curves.linear` to `Curves.easeOut` and `Curves.easeIn` for attachment controls.
- Removed default padding in `DateDivider` in `MessageListView`
✅ Added
- Added `MessageInput.customPortalOptions` property to add custom overlays to the `MessageInput`.
## 2.2.1
⚠️ Deprecated
- `MessageSearchListView` `paginationParams` property is now deprecated in favor of `limit`.
```dart
// previous
paginationParams = const PaginationParams(limit: 30)
// new
limit = 30
```
- `UserListView` `pagination` property is now deprecated in favor of `limit`.
```dart
// previous
pagination = const PaginationParams(limit: 30)
// new
limit = 30
```
- `ChannelListView` `pagination` property is now deprecated in favor of `limit`.
```dart
// previous
pagination = const PaginationParams(limit: 30)
// new
limit = 30
```
🔄 Changed
- `UserListViewCore` filter property now has a default value.
```dart
filter = const Filter.empty()
```
🐞 Fixed
- Fixed `MessageSearchListView` pagination.
- Fixed `MessageWidget` attachment tap callbacks.
## 2.2.1
- Updated `stream_chat_flutter_core` dependency to 2.2.1
@@ -7,14 +102,13 @@
✅ Added
- [#516](https://github.com/GetStream/stream-chat-flutter/issues/516):
Added `StreamChatThemeData.placeholderUserImage` for building a widget when the `UserAvatar` image
is loading
Added `StreamChatThemeData.placeholderUserImage` for building a widget when the `UserAvatar` image is loading
- Added a `backgroundColor` property to the following widgets:
- `ChannelHeader`
- `ChannelListHeader`
- `GalleryHeader`
- `GalleryFooter`
- `ThreadHeader`
- `ChannelHeader`
- `ChannelListHeader`
- `GalleryHeader`
- `GalleryFooter`
- `ThreadHeader`
- Added `MessageInput.attachmentLimit` in order to limit the no. of attachments that can be sent with a single message.
- Added `MessageInput.onAttachmentLimitExceed` callback which will be called when the `attachmentLimit` is exceeded.
This will override the default error alert behaviour.
@@ -34,9 +128,8 @@ You can call `.copyWith` to customize just a subset of properties.
🔄 Changed
Theming has been upgraded! Most theme classes now have `InheritedTheme` classes associated with
them, and have been upgraded with some goodies like `lerp` functions. Here's the full naming
breakdown:
Theming has been upgraded! Most theme classes now have `InheritedTheme` classes associated with them, and have been
upgraded with some goodies like `lerp` functions. Here's the full naming breakdown:
* `AvatarTheme` is now `AvatarThemeData`
* `ChannelHeaderTheme` is now `ChannelHeaderThemeData`
@@ -53,18 +146,18 @@ breakdown:
🐞 Fixed
- Fixed `MessageInput` textField case where `input` is not enabled if the file picked from the
camera is null.
- Fixed `MessageInput` textField case where `input` is not enabled if the file picked from the camera is null.
- Fixed date dividers position/alignment in non reversed `MessageListView`.
- Fixed `MessageListView` not opening to the right initialMessage if `StreamChannel.initialMessageId` is set.
- Fixed null check errors when accessing `message.text` in `MessageWidget` and `MessageListView`; this occurred when sending a message with no text.
- Fixed null check errors when accessing `message.text` in `MessageWidget` and `MessageListView`; this occurred when
sending a message with no text.
## 2.1.2
🐞 Fixed
- [#590](https://github.com/GetStream/stream-chat-flutter/issues/590): livestream use case, no
members when sending message
- [#590](https://github.com/GetStream/stream-chat-flutter/issues/590): livestream use case, no members when sending
message
## 2.1.1
@@ -82,8 +175,7 @@ breakdown:
🔄 Changed
- `StreamChat.of(context).user` is now deprecated in favor of `StreamChat.of(context).currentUser`.
- `StreamChat.of(context).userStream` is now deprecated in favor
of `StreamChat.of(context).currentUserStream`.
- `StreamChat.of(context).userStream` is now deprecated in favor of `StreamChat.of(context).currentUserStream`.
🐞 Fixed
@@ -136,8 +228,7 @@ You can call `.copyWith` to customize just a subset of properties
- Added video compress options (frame and quality) to `MessageInput`
- TypingIndicator now has a property called `parentId` to show typing indicator specific to threads
- [#493](https://github.com/GetStream/stream-chat-flutter/pull/493): add support for messageListView
header/footer
- [#493](https://github.com/GetStream/stream-chat-flutter/pull/493): add support for messageListView header/footer
- `MessageWidget` accepts a `userAvatarBuilder`
- Added pinMessage ui support
- Added `MessageListView.threadSeparatorBuilder` property
@@ -146,12 +237,10 @@ You can call `.copyWith` to customize just a subset of properties
🐞 Fixed
- [#483](https://github.com/GetStream/stream-chat-flutter/issues/483): Keyboard covers input text
box when editing message
- Modals are shown using the nearest `Navigator` to make using the SDK easier in a nested navigator
use case
- [#484](https://github.com/GetStream/stream-chat-flutter/issues/484): messages don't update without
a reload
- [#483](https://github.com/GetStream/stream-chat-flutter/issues/483): Keyboard covers input text box when editing
message
- Modals are shown using the nearest `Navigator` to make using the SDK easier in a nested navigator use case
- [#484](https://github.com/GetStream/stream-chat-flutter/issues/484): messages don't update without a reload
- `MessageListView` not rendering if the user is not a member of the channel
- Fix `MessageInput` overflow when there are no actions
- Minor fixes and improvements
@@ -204,18 +293,15 @@ You can call `.copyWith` to customize just a subset of properties.
✅ Added
- TypingIndicator now has a property called `parentId` to show typing indicator specific to threads
- [#493](https://github.com/GetStream/stream-chat-flutter/pull/493): add support for messageListView
header/footer
- [#493](https://github.com/GetStream/stream-chat-flutter/pull/493): add support for messageListView header/footer
- `MessageWidget` accepts a `userAvatarBuilder`
🐞 Fixed
- [#483](https://github.com/GetStream/stream-chat-flutter/issues/483): Keyboard covers input text
box when editing message
- Modals are shown using the nearest `Navigator` to make using the SDK easier in a nested navigator
use case
- [#484](https://github.com/GetStream/stream-chat-flutter/issues/484): messages don't update without
a reload
- [#483](https://github.com/GetStream/stream-chat-flutter/issues/483): Keyboard covers input text box when editing
message
- Modals are shown using the nearest `Navigator` to make using the SDK easier in a nested navigator use case
- [#484](https://github.com/GetStream/stream-chat-flutter/issues/484): messages don't update without a reload
- `MessageListView` not rendering if the user is not a member of the channel
## 2.0.0-nullsafety.7
@@ -285,8 +371,7 @@ You can call `.copyWith` to customize just a subset of properties.
- Show error messages as system and keep them in the message input
- Remove notification badge logic
- Use shimmer while loading images
- Polished `StreamChatTheme` adding more options and a new `MessageInputTheme` dedicated
to `MessageInput`
- Polished `StreamChatTheme` adding more options and a new `MessageInputTheme` dedicated to `MessageInput`
- Add possibility to specify custom message actions using `MessageWidget.customActions`
- Added `MessageListView.onAttachmentTap` callback
- Fixed message newline issue
@@ -343,8 +428,7 @@ You can call `.copyWith` to customize just a subset of properties.
- Improved api documentation
- Updated `stream_chat` dependency to `^1.0.0-beta`
- Extracted sample app into dedicated [repo](https://github.com/GetStream/flutter-samples)
- Reimplemented existing widgets
using [stream_chat_flutter_core](https://pub.dev/packages/stream_chat_flutter_core)
- Reimplemented existing widgets using [stream_chat_flutter_core](https://pub.dev/packages/stream_chat_flutter_core)
## 0.2.21
@@ -361,8 +445,8 @@ You can call `.copyWith` to customize just a subset of properties.
## 0.2.20+2
- Added `shouldAddChannel` to ChannelsBloc in order to check if a channel has to be added to the
list when a new message arrives
- Added `shouldAddChannel` to ChannelsBloc in order to check if a channel has to be added to the list when a new message
arrives
## 0.2.20+1
@@ -396,8 +480,7 @@ You can call `.copyWith` to customize just a subset of properties.
## 0.2.16
- Do not wrap channel preview builder. Users will have to implement they're custom onTap/onLongPress
implementation
- Do not wrap channel preview builder. Users will have to implement they're custom onTap/onLongPress implementation
- Make public autofocus field of the TextField of message_input
## 0.2.15
@@ -582,11 +665,10 @@ You can call `.copyWith` to customize just a subset of properties.
## 0.2.1-alpha+1
- Removed the additional `Navigator` in `StreamChat` widget. It was added to make the app have
the `StreamChat` widget as ancestor in every route. Now the recommended way to add `StreamChat` to
your app is using the `builder` property of your `MaterialApp` widget. Otherwise you can use it in
the usual way, but you need to add a `StreamChat` widget to every route of your app.
Read [this issue](https://github.com/GetStream/stream-chat-flutter/issues/47) for more
- Removed the additional `Navigator` in `StreamChat` widget. It was added to make the app have the `StreamChat` widget
as ancestor in every route. Now the recommended way to add `StreamChat` to your app is using the `builder` property of
your `MaterialApp` widget. Otherwise you can use it in the usual way, but you need to add a `StreamChat` widget to
every route of your app. Read [this issue](https://github.com/GetStream/stream-chat-flutter/issues/47) for more
information.
```dart
@@ -688,8 +770,8 @@ Widget build(BuildContext context) {
- Add gesture (vertical drag down) to close the keyboard
- Add keyboard type parameters (set it to TextInputType.text to show the submit button that will
even close the keyboard)
- Add keyboard type parameters (set it to TextInputType.text to show the submit button that will even close the
keyboard)
The property showVideoFullScreen was added mainly because of this issue brianegan/chewie#261
@@ -21,6 +21,6 @@
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>8.0</string>
<string>9.0</string>
</dict>
</plist>
@@ -106,9 +106,7 @@ class ChannelListPage extends StatelessWidget {
[StreamChat.of(context).currentUser!.id],
),
sort: const [SortOption('last_message_at')],
pagination: const PaginationParams(
limit: 20,
),
limit: 20,
),
),
);
@@ -83,9 +83,7 @@ class ChannelListPage extends StatelessWidget {
[StreamChat.of(context).currentUser!.id],
),
sort: const [SortOption('last_message_at')],
pagination: const PaginationParams(
limit: 20,
),
limit: 20,
channelWidget: const ChannelPage(),
),
),
@@ -85,9 +85,7 @@ class ChannelListPage extends StatelessWidget {
),
channelPreviewBuilder: _channelPreviewBuilder,
// sort: [SortOption('last_message_at')],
pagination: const PaginationParams(
limit: 20,
),
limit: 20,
channelWidget: const ChannelPage(),
),
),
@@ -69,9 +69,7 @@ class ChannelListPage extends StatelessWidget {
[StreamChat.of(context).currentUser!.id],
),
sort: const [SortOption('last_message_at')],
pagination: const PaginationParams(
limit: 20,
),
limit: 20,
channelWidget: const ChannelPage(),
),
),
@@ -75,9 +75,7 @@ class ChannelListPage extends StatelessWidget {
[StreamChat.of(context).currentUser!.id],
),
sort: const [SortOption('last_message_at')],
pagination: const PaginationParams(
limit: 20,
),
limit: 20,
channelWidget: const ChannelPage(),
),
),
@@ -34,7 +34,7 @@ Future<void> main() async {
await client.connectUser(
User(id: 'super-band-9'),
'''eyJ0eXAiO«iJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A''',
'''eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A''',
);
runApp(
@@ -102,9 +102,7 @@ class ChannelListPage extends StatelessWidget {
[StreamChat.of(context).currentUser!.id],
),
sort: const [SortOption('last_message_at')],
pagination: const PaginationParams(
limit: 20,
),
limit: 20,
channelWidget: const ChannelPage(),
),
),
@@ -19,42 +19,36 @@ class AttachmentTitle extends StatelessWidget {
final Attachment attachment;
@override
Widget build(BuildContext context) => GestureDetector(
onTap: () {
if (attachment.titleLink != null) {
launchURL(context, attachment.titleLink);
}
},
child: Padding(
padding: const EdgeInsets.all(8),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
if (attachment.title != null)
Text(
attachment.title!,
overflow: TextOverflow.ellipsis,
style: messageTheme.messageTextStyle?.copyWith(
color: StreamChatTheme.of(context).colorTheme.accentPrimary,
fontWeight: FontWeight.bold,
),
Widget build(BuildContext context) {
final normalizedTitleLink = attachment.titleLink?.replaceFirst(
RegExp(r'https?://(www\.)?'),
'',
);
return GestureDetector(
onTap: () {
final titleLink = attachment.titleLink;
if (titleLink != null) launchURL(context, titleLink);
},
child: Padding(
padding: const EdgeInsets.all(8),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
if (attachment.title != null)
Text(
attachment.title!,
overflow: TextOverflow.ellipsis,
style: messageTheme.messageTextStyle?.copyWith(
color: StreamChatTheme.of(context).colorTheme.accentPrimary,
fontWeight: FontWeight.bold,
),
if (attachment.titleLink != null ||
attachment.ogScrapeUrl != null)
Text(
Uri.parse(attachment.titleLink ?? attachment.ogScrapeUrl!)
.authority
.split('.')
.reversed
.take(2)
.toList()
.reversed
.join('.'),
style: messageTheme.messageTextStyle,
),
],
),
),
if (normalizedTitleLink != null)
Text(normalizedTitleLink, style: messageTheme.messageTextStyle),
],
),
);
),
);
}
}
@@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/src/extension.dart';
import 'package:stream_chat_flutter/src/upload_progress_indicator.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter/src/extension.dart';
/// Widget to build in progress
typedef InProgressBuilder = Widget Function(BuildContext, int, int);
@@ -1,15 +1,14 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:shimmer/shimmer.dart';
import 'package:stream_chat_flutter/src/attachment/attachment_widget.dart';
import 'package:stream_chat_flutter/src/extension.dart';
import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
import 'package:stream_chat_flutter/src/upload_progress_indicator.dart';
import 'package:stream_chat_flutter/src/utils.dart';
import 'package:stream_chat_flutter/src/video_thumbnail_image.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:stream_chat_flutter/src/extension.dart';
import 'package:stream_chat_flutter/src/attachment/attachment_widget.dart';
/// Widget for displaying file attachments
class FileAttachment extends AttachmentWidget {
@@ -258,7 +257,8 @@ class FileAttachment extends AttachmentWidget {
visualDensity: VisualDensity.compact,
splashRadius: 16,
onPressed: () {
launchURL(context, attachment.assetUrl);
final assetUrl = attachment.assetUrl;
if (assetUrl != null) launchURL(context, assetUrl);
},
);
}
@@ -2,10 +2,10 @@ import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:shimmer/shimmer.dart';
import 'package:stream_chat_flutter/src/attachment/attachment_widget.dart';
import 'package:stream_chat_flutter/src/extension.dart';
import 'package:stream_chat_flutter/src/visible_footnote.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:stream_chat_flutter/src/extension.dart';
/// Widget for showing a GIF attachment
class GiphyAttachment extends AttachmentWidget {
@@ -98,7 +98,13 @@ class GiphyAttachment extends AttachmentWidget {
Padding(
padding: const EdgeInsets.all(2),
child: GestureDetector(
onTap: () => onAttachmentTap ?? _onImageTap(context),
onTap: () {
if (onAttachmentTap != null) {
onAttachmentTap?.call();
} else {
_onImageTap(context);
}
},
child: CachedNetworkImage(
height: size?.height,
width: size?.width,
@@ -253,21 +259,12 @@ class GiphyAttachment extends AttachmentWidget {
Widget _buildSentAttachment(BuildContext context, String imageUrl) =>
SizedBox(
child: GestureDetector(
onTap: () async {
final res =
await Navigator.push(context, MaterialPageRoute(builder: (_) {
final channel = StreamChannel.of(context).channel;
return StreamChannel(
channel: channel,
child: FullScreenMedia(
mediaAttachments: [attachment],
userName: message.user?.name,
message: message,
onShowMessage: onShowMessage,
),
);
}));
if (res != null) onReturnAction!(res);
onTap: () {
if (onAttachmentTap != null) {
onAttachmentTap?.call();
} else {
_onImageTap(context);
}
},
child: Stack(
children: [
@@ -10,6 +10,7 @@ class UrlAttachment extends StatelessWidget {
Key? key,
required this.urlAttachment,
required this.hostDisplayName,
required this.messageTheme,
this.textPadding = const EdgeInsets.symmetric(
horizontal: 16,
vertical: 8,
@@ -25,15 +26,16 @@ class UrlAttachment extends StatelessWidget {
/// Padding for text
final EdgeInsets textPadding;
/// [MessageThemeData] for showing image title
final MessageThemeData messageTheme;
@override
Widget build(BuildContext context) {
final chatThemeData = StreamChatTheme.of(context);
return GestureDetector(
onTap: () {
launchURL(
context,
urlAttachment.ogScrapeUrl,
);
final titleLink = urlAttachment.titleLink;
if (titleLink != null) launchURL(context, titleLink);
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
@@ -60,7 +62,7 @@ class UrlAttachment extends StatelessWidget {
borderRadius: const BorderRadius.only(
topRight: Radius.circular(16),
),
color: chatThemeData.colorTheme.linkBg,
color: messageTheme.linkBackgroundColor,
),
child: Padding(
padding: const EdgeInsets.only(
@@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/src/channel_info.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter/src/extension.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
/// Bottom Sheet with options
class ChannelBottomSheet extends StatefulWidget {
@@ -1,12 +1,13 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:stream_chat_flutter/src/back_button.dart';
import 'package:stream_chat_flutter/src/channel_info.dart';
import 'package:stream_chat_flutter/src/channel_name.dart';
import 'package:stream_chat_flutter/src/extension.dart';
import 'package:stream_chat_flutter/src/info_tile.dart';
import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:stream_chat_flutter/src/extension.dart';
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/packages/stream_chat_flutter/screenshots/channel_header.png)
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/packages/stream_chat_flutter/screenshots/channel_header_paint.png)
@@ -137,12 +138,17 @@ class ChannelHeader extends StatelessWidget implements PreferredSizeWidget {
break;
}
final theme = Theme.of(context);
return InfoTile(
showMessage: showConnectionStateTile && showStatus,
message: statusString,
child: AppBar(
textTheme: Theme.of(context).textTheme,
brightness: Theme.of(context).brightness,
toolbarTextStyle: theme.textTheme.bodyText2,
titleTextStyle: theme.textTheme.headline6,
systemOverlayStyle: theme.brightness == Brightness.dark
? SystemUiOverlayStyle.light
: SystemUiOverlayStyle.dark,
elevation: 1,
leading: leadingWidget,
backgroundColor: backgroundColor ?? channelHeaderTheme.color,
@@ -1,8 +1,8 @@
import 'package:collection/collection.dart' show IterableExtension;
import 'package:flutter/material.dart';
import 'package:jiffy/jiffy.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter/src/extension.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
/// Widget which shows channel info
class ChannelInfo extends StatelessWidget {
@@ -1,6 +1,7 @@
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:stream_chat_flutter/src/extension.dart';
import 'package:stream_chat_flutter/src/stream_neumorphic_button.dart';
@@ -121,12 +122,16 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget {
final chatThemeData = StreamChatTheme.of(context);
final channelListHeaderThemeData = ChannelListHeaderTheme.of(context);
final theme = Theme.of(context);
return InfoTile(
showMessage: showConnectionStateTile && showStatus,
message: statusString,
child: AppBar(
textTheme: Theme.of(context).textTheme,
brightness: Theme.of(context).brightness,
toolbarTextStyle: theme.textTheme.bodyText2,
titleTextStyle: theme.textTheme.headline6,
systemOverlayStyle: theme.brightness == Brightness.dark
? SystemUiOverlayStyle.light
: SystemUiOverlayStyle.dark,
elevation: 1,
backgroundColor:
backgroundColor ?? channelListHeaderThemeData.color,
@@ -4,11 +4,11 @@ import 'package:flutter/material.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
import 'package:shimmer/shimmer.dart';
import 'package:stream_chat_flutter/src/channel_bottom_sheet.dart';
import 'package:stream_chat_flutter/src/extension.dart';
import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
import 'package:stream_chat_flutter/src/theme/themes.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:stream_chat_flutter/src/extension.dart';
/// Callback called when tapping on a channel
typedef ChannelTapCallback = void Function(Channel, Widget?);
@@ -59,7 +59,7 @@ typedef ViewInfoCallback = void Function(Channel);
/// Modify it to change the widget appearance.
class ChannelListView extends StatefulWidget {
/// Instantiate a new ChannelListView
const ChannelListView({
ChannelListView({
Key? key,
this.filter,
this.sort,
@@ -68,9 +68,12 @@ class ChannelListView extends StatefulWidget {
this.presence = false,
this.memberLimit,
this.messageLimit,
this.pagination = const PaginationParams(
limit: 25,
),
@Deprecated(
"'pagination' is deprecated and shouldn't be used. "
"This property is no longer used, Please use 'limit' instead",
)
this.pagination,
int? limit,
this.onChannelTap,
this.onChannelLongPress,
this.channelWidget,
@@ -92,7 +95,8 @@ class ChannelListView extends StatefulWidget {
this.onDeletePressed,
this.swipeActions,
this.channelListController,
}) : super(key: key);
}) : limit = limit ?? pagination?.limit ?? 25,
super(key: key);
/// If true a default swipe to action behaviour will be added to this widget
final bool swipeToAction;
@@ -129,7 +133,14 @@ class ChannelListView extends StatefulWidget {
/// limit: the number of channels to return (max is 30)
/// offset: the offset (max is 1000)
/// message_limit: how many messages should be included to each channel
final PaginationParams pagination;
@Deprecated(
"'pagination' is deprecated and shouldn't be used. "
"This property is no longer used, Please use 'limit' instead",
)
final PaginationParams? pagination;
/// The amount of channels requested per API call.
final int limit;
/// Function called when tapping on a channel
/// By default it calls [Navigator.push] building a [MaterialPageRoute]
@@ -218,7 +229,7 @@ class _ChannelListViewState extends State<ChannelListView> {
presence: widget.presence,
memberLimit: widget.memberLimit,
messageLimit: widget.messageLimit,
pagination: widget.pagination,
limit: widget.limit,
channelListController: _channelListController,
listBuilder: widget.listBuilder ?? _buildListView,
emptyBuilder: widget.emptyBuilder ?? _buildEmptyWidget,
@@ -1,8 +1,8 @@
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:stream_chat_flutter/src/extension.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:stream_chat_flutter/src/extension.dart';
/// It shows the current [Channel] name using a [Text] widget.
///
@@ -0,0 +1,210 @@
import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/src/extension.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
/// Overlay for displaying commands that can be used
class CommandsOverlay extends StatelessWidget {
/// Constructor for creating a [CommandsOverlay]
const CommandsOverlay({
required this.text,
required this.onCommandResult,
required this.size,
required this.channel,
Key? key,
}) : super(key: key);
/// The size of the overlay
final Size size;
/// Query for searching commands
final String text;
/// The channel to search for users
final Channel channel;
/// Callback called when a command is selected
final ValueChanged<Command> onCommandResult;
@override
Widget build(BuildContext context) {
final _streamChatTheme = StreamChatTheme.of(context);
final commands = channel.config?.commands
.where((c) => c.name.contains(text.replaceFirst('/', '')))
.toList() ??
[];
if (commands.isEmpty) {
return const SizedBox();
}
return Padding(
padding: const EdgeInsets.all(4),
child: Card(
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
color: _streamChatTheme.colorTheme.barsBg,
clipBehavior: Clip.hardEdge,
child: Container(
constraints: BoxConstraints.loose(size),
decoration: BoxDecoration(
color: _streamChatTheme.colorTheme.barsBg,
borderRadius: BorderRadius.circular(8)),
child: ListView(
padding: const EdgeInsets.all(0),
shrinkWrap: true,
children: [
if (commands.isNotEmpty)
Padding(
padding: const EdgeInsets.only(top: 8),
child: Row(
children: [
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 8,
),
child: StreamSvgIcon.lightning(
color: _streamChatTheme.colorTheme.accentPrimary,
),
),
Text(
context.translations.instantCommandsLabel,
style: TextStyle(
color: _streamChatTheme.colorTheme.textHighEmphasis
.withOpacity(.5),
),
)
],
),
),
const SizedBox(
height: 10,
),
...commands
.map(
(c) => InkWell(
onTap: () {
onCommandResult(c);
},
child: SizedBox(
height: 40,
child: Row(
children: [
const SizedBox(
width: 16,
),
_buildCommandIcon(_streamChatTheme, c.name),
const SizedBox(
width: 8,
),
Text.rich(
TextSpan(
text: c.name.capitalize(),
style: const TextStyle(
fontWeight: FontWeight.bold),
children: [
TextSpan(
text: ' /${c.name} ${c.args}',
style: _streamChatTheme.textTheme.body
.copyWith(
// ignore: lines_longer_than_80_chars
color: _streamChatTheme
// ignore: lines_longer_than_80_chars
.colorTheme
.textLowEmphasis,
),
),
],
),
),
],
),
),
),
)
.toList(),
],
),
),
),
);
}
Widget _buildCommandIcon(
StreamChatThemeData _streamChatTheme, String iconType) {
switch (iconType) {
case 'giphy':
return CircleAvatar(
radius: 12,
child: StreamSvgIcon.giphyIcon(
size: 24,
),
);
case 'ban':
return CircleAvatar(
backgroundColor: _streamChatTheme.colorTheme.accentPrimary,
radius: 12,
child: StreamSvgIcon.iconUserDelete(
size: 16,
color: Colors.white,
),
);
case 'flag':
return CircleAvatar(
backgroundColor: _streamChatTheme.colorTheme.accentPrimary,
radius: 12,
child: StreamSvgIcon.flag(
size: 14,
color: Colors.white,
),
);
case 'imgur':
return CircleAvatar(
backgroundColor: _streamChatTheme.colorTheme.accentPrimary,
radius: 12,
child: ClipOval(
child: StreamSvgIcon.imgur(
size: 24,
),
),
);
case 'mute':
return CircleAvatar(
backgroundColor: _streamChatTheme.colorTheme.accentPrimary,
radius: 12,
child: StreamSvgIcon.mute(
size: 16,
color: Colors.white,
),
);
case 'unban':
return CircleAvatar(
backgroundColor: _streamChatTheme.colorTheme.accentPrimary,
radius: 12,
child: StreamSvgIcon.userAdd(
size: 16,
color: Colors.white,
),
);
case 'unmute':
return CircleAvatar(
backgroundColor: _streamChatTheme.colorTheme.accentPrimary,
radius: 12,
child: StreamSvgIcon.volumeUp(
size: 16,
color: Colors.white,
),
);
default:
return CircleAvatar(
backgroundColor: _streamChatTheme.colorTheme.accentPrimary,
radius: 12,
child: StreamSvgIcon.lightning(
size: 16,
color: Colors.white,
),
);
}
}
}
@@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:jiffy/jiffy.dart';
import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
import 'package:stream_chat_flutter/src/extension.dart';
import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
/// It shows a date divider depending on the date difference
class DateDivider extends StatelessWidget {
@@ -26,7 +26,8 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import 'package:collection/collection.dart' show IterableExtension;
import 'package:collection/collection.dart'
show IterableExtension, ListEquality;
/// All Groups
enum EmojiGroup {
@@ -114240,7 +114241,6 @@ final emojiRegex = RegExp(
class Emoji {
static const variationSelector16 = 65039;
static const ZWJ = 8205;
final String? name;
final String? char;
final String? shortName;
@@ -114252,14 +114252,39 @@ class Emoji {
/// Emoji class.
/// [name] of emoji. [char] and character of emoji. [shortName] and a digest name of emoji, [emojiGroup] is emoji's group and [emojiSubgroup] is emoji's subgroup. [keywords] list of keywords for emoji. [modifiable] `true` if emoji has skin.
Emoji(
{this.name,
this.char,
this.shortName,
this.emojiGroup,
this.emojiSubgroup,
this.keywords = const [],
this.modifiable = false});
Emoji({
this.name,
this.char,
this.shortName,
this.emojiGroup,
this.emojiSubgroup,
this.keywords = const [],
this.modifiable = false,
});
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is Emoji &&
runtimeType == other.runtimeType &&
name == other.name &&
char == other.char &&
shortName == other.shortName &&
emojiGroup == other.emojiGroup &&
emojiSubgroup == other.emojiSubgroup &&
const ListEquality().equals(keywords, other.keywords) &&
modifiable == other.modifiable;
@override
int get hashCode => Object.hash(
name.hashCode,
char.hashCode,
shortName.hashCode,
emojiGroup.hashCode,
emojiSubgroup.hashCode,
keywords.hashCode,
modifiable.hashCode,
);
/// Runes of Emoji Character
List<int> get charRunes {
@@ -114339,9 +114364,11 @@ class Emoji {
return _emojis.firstWhereOrNull((Emoji emoji) => emoji.name == name);
}
/// Returns Emoji by [name] as short name.
static Emoji? byShortName(String name) {
return _emojis.firstWhereOrNull((Emoji emoji) => emoji.char == name);
/// Returns Emoji by [shortName] as short name.
static Emoji? byShortName(String shortName) {
return _emojis.firstWhereOrNull(
(Emoji emoji) => emoji.shortName == shortName,
);
}
/// Returns list of Emojis in a same [group]
@@ -0,0 +1,118 @@
import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/src/emoji/emoji.dart';
import 'package:stream_chat_flutter/src/extension.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:substring_highlight/substring_highlight.dart';
/// Overlay for displaying emoji that can be used
class EmojiOverlay extends StatelessWidget {
/// Constructor for creating a [EmojiOverlay]
const EmojiOverlay({
required this.query,
required this.onEmojiResult,
required this.size,
Key? key,
}) : super(key: key);
/// The size of the overlay
final Size size;
/// Query for searching emoji
final String query;
/// Callback called when an emoji is selected
final ValueChanged<Emoji> onEmojiResult;
@override
Widget build(BuildContext context) {
final _streamChatTheme = StreamChatTheme.of(context);
final _emojiNames =
Emoji.all().where((it) => it.name != null).map((e) => e.name!);
final emojis = _emojiNames
.where((e) => e.contains(query))
.map(Emoji.byName)
.where((e) => e != null);
if (emojis.isEmpty) {
return const SizedBox();
}
return Card(
margin: const EdgeInsets.all(8),
elevation: 2,
color: _streamChatTheme.colorTheme.barsBg,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
clipBehavior: Clip.hardEdge,
child: Container(
constraints: BoxConstraints.loose(size),
decoration: BoxDecoration(
boxShadow: const [
BoxShadow(
spreadRadius: -8,
blurRadius: 5,
offset: Offset(0, -4),
),
],
color: _streamChatTheme.colorTheme.barsBg,
),
child: ListView.builder(
padding: const EdgeInsets.all(0),
shrinkWrap: true,
itemCount: emojis.length + 1,
itemBuilder: (context, i) {
if (i == 0) {
return Padding(
padding: const EdgeInsets.only(left: 8, top: 8),
child: Row(
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: StreamSvgIcon.smile(
color: _streamChatTheme.colorTheme.accentPrimary,
),
),
Flexible(
child: Text(
context.translations.emojiMatchingQueryText(
query,
),
style: TextStyle(
color: _streamChatTheme.colorTheme.textHighEmphasis
.withOpacity(.5),
),
),
)
],
),
);
}
final emoji = emojis.elementAt(i - 1)!;
final themeData = Theme.of(context);
return ListTile(
title: SubstringHighlight(
text:
// ignore: lines_longer_than_80_chars
"${emoji.char} ${emoji.name!.replaceAll('_', ' ')}",
term: query,
textStyleHighlight: themeData.textTheme.headline6!.copyWith(
fontSize: 14.5,
fontWeight: FontWeight.bold,
),
textStyle: themeData.textTheme.headline6!.copyWith(
fontSize: 14.5,
),
),
onTap: () {
onEmojiResult(emoji);
},
);
},
),
),
);
}
}
@@ -1,4 +1,5 @@
import 'package:characters/characters.dart';
import 'package:diacritic/diacritic.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/src/emoji/emoji.dart';
@@ -11,7 +12,7 @@ final _emojiChars = Emoji.chars();
extension StringExtension on String {
/// Returns the capitalized string
String capitalize() =>
'${this[0].toUpperCase()}${substring(1).toLowerCase()}';
isNotEmpty ? '${this[0].toUpperCase()}${substring(1).toLowerCase()}' : '';
/// Returns whether the string contains only emoji's or not.
///
@@ -24,6 +25,12 @@ extension StringExtension on String {
final characters = trim().characters;
return characters.every(_emojiChars.contains);
}
/// Removes accents and diacritics from the given String.
String get diacriticsInsensitive => removeDiacritics(this);
/// Levenshtein distance between this and [t].
int levenshteinDistance(String t) => levenshtein(this, t);
}
/// List extension
@@ -170,3 +177,49 @@ extension IconButtonX on IconButton {
icon: icon ?? this.icon,
);
}
/// Extensions on List<User>
extension UserListX on List<User> {
/// It does an search on a list of [User] and returns users with
/// `id` or `name` containing the [query].
///
/// Results are returned sorted by their edit distance from the
/// searched string, distance is calculated using the [levenshtein] algorithm.
List<User> search(String query) {
String normalize(String input) => input.toLowerCase().diacriticsInsensitive;
final normalizedQuery = normalize(query);
final matchingUsers = <User, int>{}; // User:lDistance
for (final user in this) {
final normalizedId = normalize(user.id);
final normalizedUserName = normalize(user.name);
final lDistance = normalizedUserName.levenshteinDistance(normalizedQuery);
final containsId = normalizedId.contains(normalizedQuery);
final containsName = normalizedUserName.contains(normalizedQuery);
if (lDistance < 3 || containsId || containsName) {
matchingUsers[user] = lDistance;
}
}
final entries = matchingUsers.entries.toList(growable: false)
..sort((prev, curr) {
bool containsQuery(User user) =>
normalize(user.id).contains(normalizedQuery) ||
normalize(user.name).contains(normalizedQuery);
final containsInPrev = containsQuery(prev.key);
final containsInCurr = containsQuery(curr.key);
if (containsInPrev && !containsInCurr) {
return -1;
} else if (!containsInPrev && containsInCurr) {
return 1;
}
return prev.value.compareTo(curr.value);
});
return entries.map((e) => e.key).toList(growable: false);
}
}
@@ -5,11 +5,11 @@ import 'package:cached_network_image/cached_network_image.dart';
import 'package:chewie/chewie.dart';
import 'package:flutter/material.dart';
import 'package:photo_view/photo_view.dart';
import 'package:stream_chat_flutter/src/extension.dart';
import 'package:stream_chat_flutter/src/gallery_footer.dart';
import 'package:stream_chat_flutter/src/gallery_header.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:video_player/video_player.dart';
import 'package:stream_chat_flutter/src/extension.dart';
/// Return action for coming back from pages
enum ReturnActionType {
@@ -112,6 +112,8 @@ class _FullScreenMediaState extends State<FullScreenMedia>
attachment.assetUrl ??
attachment.thumbUrl;
return PhotoView(
loadingBuilder: (context, image) =>
const Offstage(),
imageProvider: (imageUrl == null &&
attachment.localUri != null &&
attachment.file?.bytes != null)
@@ -7,12 +7,12 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';
import 'package:share_plus/share_plus.dart';
import 'package:stream_chat_flutter/src/extension.dart';
import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
import 'package:stream_chat_flutter/src/theme/themes.dart';
import 'package:stream_chat_flutter/src/video_thumbnail_image.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:stream_chat_flutter/src/extension.dart';
/// Footer widget for media display
class GalleryFooter extends StatefulWidget implements PreferredSizeWidget {
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:stream_chat_flutter/src/attachment_actions_modal.dart';
import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
@@ -57,9 +58,13 @@ class GalleryHeader extends StatelessWidget implements PreferredSizeWidget {
@override
Widget build(BuildContext context) {
final galleryHeaderThemeData = GalleryHeaderTheme.of(context);
final theme = Theme.of(context);
return AppBar(
textTheme: Theme.of(context).textTheme,
brightness: Theme.of(context).brightness,
toolbarTextStyle: theme.textTheme.bodyText2,
titleTextStyle: theme.textTheme.headline6,
systemOverlayStyle: theme.brightness == Brightness.dark
? SystemUiOverlayStyle.light
: SystemUiOverlayStyle.dark,
elevation: 1,
leading: showBackButton
? IconButton(
@@ -15,6 +15,7 @@ class ImageGroup extends StatelessWidget {
required this.size,
this.onReturnAction,
this.onShowMessage,
this.onAttachmentTap,
}) : super(key: key);
/// List of attachments to show
@@ -23,6 +24,9 @@ class ImageGroup extends StatelessWidget {
/// Callback when attachment is returned to from other screens
final ValueChanged<ReturnActionType>? onReturnAction;
/// Callback when attachment is tapped
final void Function(Message message, Attachment attachment)? onAttachmentTap;
/// Message which images are attached to
final Message message;
@@ -117,6 +121,10 @@ class ImageGroup extends StatelessWidget {
BuildContext context,
int index,
) async {
if (onAttachmentTap != null) {
return onAttachmentTap!(message, images[index]);
}
final channel = StreamChannel.of(context).channel;
final res = await Navigator.push(
@@ -4,6 +4,7 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart';
/// This widget is used for showing user tiles for mentions
/// Use [title], [subtitle], [leading], [trailing] for
/// substituting widgets in respective positions
@Deprecated('Use `UserMentionTile` instead. Will be removed in future release')
class MentionTile extends StatelessWidget {
/// Constructor for creating a [MentionTile] widget
const MentionTile(
File diff suppressed because it is too large Load Diff
@@ -169,6 +169,7 @@ class MessageListView extends StatefulWidget {
this.messageListController,
this.reverse = true,
this.paginationLimit = 20,
this.paginationLoadingIndicatorBuilder,
}) : super(key: key);
/// Function used to build a custom message widget
@@ -284,6 +285,9 @@ class MessageListView extends StatefulWidget {
/// Use [ChannelListController.paginateData] pagination.
final MessageListController? messageListController;
/// Builder used to build the loading indicator shown while paginating.
final WidgetBuilder? paginationLoadingIndicatorBuilder;
@override
_MessageListViewState createState() => _MessageListViewState();
}
@@ -293,7 +297,6 @@ class _MessageListViewState extends State<MessageListView> {
void Function(Message)? _onThreadTap;
bool _showScrollToBottom = false;
late final ItemPositionsListener _itemPositionListener;
late final Stream<Iterable<ItemPosition>> _itemPositionStream;
int? _messageListLength;
StreamChannelState? streamChannel;
late StreamChatThemeData _streamTheme;
@@ -499,14 +502,18 @@ class _MessageListViewState extends State<MessageListView> {
return _buildThreadSeparator();
}
if (i == itemCount - 3) {
if (widget.headerBuilder == null) {
if (widget.reverse
? widget.headerBuilder == null
: widget.footerBuilder == null) {
if (_isThreadConversation) return const Offstage();
return const SizedBox(height: 52);
}
return const SizedBox(height: 8);
}
if (i == 0) {
if (widget.footerBuilder == null) {
if (widget.reverse
? widget.footerBuilder == null
: widget.headerBuilder == null) {
return const SizedBox(height: 30);
}
return const SizedBox(height: 8);
@@ -530,13 +537,13 @@ class _MessageListViewState extends State<MessageListView> {
? widget.dateDividerBuilder!(
nextMessage.createdAt.toLocal(),
)
: DateDivider(
dateTime: nextMessage.createdAt.toLocal(),
: Padding(
padding: const EdgeInsets.symmetric(vertical: 12),
child: DateDivider(
dateTime: nextMessage.createdAt.toLocal(),
),
);
return Padding(
padding: const EdgeInsets.symmetric(vertical: 12),
child: divider,
);
return divider;
}
final timeDiff =
Jiffy(nextMessage.createdAt.toLocal()).diff(
@@ -565,27 +572,42 @@ class _MessageListViewState extends State<MessageListView> {
}
if (i == itemCount - 2) {
return widget.headerBuilder?.call(context) ??
const Offstage();
if (widget.reverse) {
return widget.headerBuilder?.call(context) ??
const Offstage();
} else {
return widget.footerBuilder?.call(context) ??
const Offstage();
}
}
final indicatorBuilder =
widget.paginationLoadingIndicatorBuilder;
if (i == itemCount - 3) {
return _buildLoadingIndicator(
return _loadingIndicator(
streamChannel!,
QueryDirection.top,
indicatorBuilder: indicatorBuilder,
);
}
if (i == 1) {
return _buildLoadingIndicator(
return _loadingIndicator(
streamChannel!,
QueryDirection.bottom,
indicatorBuilder: indicatorBuilder,
);
}
if (i == 0) {
return widget.footerBuilder?.call(context) ??
const Offstage();
if (widget.reverse) {
return widget.footerBuilder?.call(context) ??
const Offstage();
} else {
return widget.headerBuilder?.call(context) ??
const Offstage();
}
}
const bottomMessageIndex = 2; // 1 -> loader // 0 -> footer
@@ -657,7 +679,8 @@ class _MessageListViewState extends State<MessageListView> {
right: 0,
child: BetterStreamBuilder<Iterable<ItemPosition>>(
initialData: _itemPositionListener.itemPositions.value,
stream: _itemPositionStream,
stream: _valueListenableToStreamAdapter(
_itemPositionListener.itemPositions),
comparator: (a, b) {
if (a == null || b == null) {
return false;
@@ -808,15 +831,17 @@ class _MessageListViewState extends State<MessageListView> {
},
);
Widget _buildLoadingIndicator(
Widget _loadingIndicator(
StreamChannelState streamChannel,
QueryDirection direction,
) =>
QueryDirection direction, {
WidgetBuilder? indicatorBuilder,
}) =>
_LoadingIndicator(
direction: direction,
streamTheme: _streamTheme,
streamChannel: streamChannel,
isThreadConversation: _isThreadConversation,
indicatorBuilder: indicatorBuilder,
);
Widget _buildBottomMessage(
@@ -996,7 +1021,7 @@ class _MessageListViewState extends State<MessageListView> {
final isOnlyEmoji = message.text?.isOnlyEmoji ?? false;
final hasUrlAttachment =
message.attachments.any((it) => it.ogScrapeUrl != null) == true;
message.attachments.any((it) => it.titleLink != null) == true;
final borderSide =
isOnlyEmoji || hasUrlAttachment || (isMyMessage && !hasFileAttachment)
@@ -1185,8 +1210,6 @@ class _MessageListViewState extends State<MessageListView> {
_scrollController = widget.scrollController ?? ItemScrollController();
_itemPositionListener =
widget.itemPositionListener ?? ItemPositionsListener.create();
_itemPositionStream =
_valueListenableToStreamAdapter(_itemPositionListener.itemPositions);
_getOnThreadTap();
super.initState();
@@ -1204,10 +1227,12 @@ class _MessageListViewState extends State<MessageListView> {
initialAlignment = _initialAlignment;
WidgetsBinding.instance!.addPostFrameCallback((timeStamp) {
_scrollController?.jumpTo(
index: initialIndex,
alignment: initialAlignment,
);
if (_scrollController?.isAttached == true) {
_scrollController?.jumpTo(
index: initialIndex,
alignment: initialAlignment,
);
}
});
_messageNewListener =
@@ -1216,8 +1241,9 @@ class _MessageListViewState extends State<MessageListView> {
_bottomPaginationActive = false;
_topPaginationActive = false;
}
if (event.message!.user!.id ==
streamChannel!.channel.client.state.currentUser!.id) {
if (event.message?.parentId == widget.parentMessage?.id &&
event.message!.user!.id ==
streamChannel!.channel.client.state.currentUser!.id) {
WidgetsBinding.instance!.addPostFrameCallback((_) {
_scrollController?.jumpTo(
index: 0,
@@ -1280,12 +1306,14 @@ class _LoadingIndicator extends StatelessWidget {
required this.isThreadConversation,
required this.direction,
required this.streamChannel,
this.indicatorBuilder,
}) : super(key: key);
final StreamChatThemeData streamTheme;
final bool isThreadConversation;
final QueryDirection direction;
final StreamChannelState streamChannel;
final WidgetBuilder? indicatorBuilder;
@override
Widget build(BuildContext context) {
@@ -1304,12 +1332,13 @@ class _LoadingIndicator extends StatelessWidget {
),
builder: (context, data) {
if (!data) return const Offstage();
return const Center(
child: Padding(
padding: EdgeInsets.all(8),
child: CircularProgressIndicator(),
),
);
return indicatorBuilder?.call(context) ??
const Center(
child: Padding(
padding: EdgeInsets.all(8),
child: CircularProgressIndicator(),
),
);
},
);
}
@@ -1,6 +1,7 @@
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/src/extension.dart';
import 'package:stream_chat_flutter/src/reaction_bubble.dart';
import 'package:stream_chat_flutter/src/reaction_picker.dart';
import 'package:stream_chat_flutter/src/stream_chat.dart';
@@ -8,7 +9,6 @@ import 'package:stream_chat_flutter/src/theme/themes.dart';
import 'package:stream_chat_flutter/src/user_avatar.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:stream_chat_flutter/src/extension.dart';
/// Modal widget for displaying message reactions
class MessageReactionsModal extends StatelessWidget {
@@ -1,8 +1,8 @@
import 'package:flutter/material.dart';
import 'package:jiffy/jiffy.dart';
import 'package:stream_chat_flutter/src/extension.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:stream_chat_flutter/src/extension.dart';
/// It shows the current [Message] preview.
///
@@ -1,10 +1,10 @@
import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/src/extension.dart';
import 'package:stream_chat_flutter/src/info_tile.dart';
import 'package:stream_chat_flutter/src/message_search_item.dart';
import 'package:stream_chat_flutter/src/theme/themes.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:stream_chat_flutter/src/extension.dart';
/// Callback called when tapping on a user
typedef MessageSearchItemTapCallback = void Function(GetMessageResponse);
@@ -30,13 +30,13 @@ typedef EmptyMessageSearchBuilder = Widget Function(
/// Widget build(BuildContext context) {
/// return Scaffold(
/// body: MessageSearchListView(
/// messageQuery: _channelQuery,
/// filters: {
/// 'members': {
/// r'$in': [user.id]
/// }
/// },
/// paginationParams: PaginationParams(limit: 20),
/// messageQuery: _channelQuery,
/// filters: {
/// 'members': {
/// r'$in': [user.id]
/// }
/// },
/// limit: 20,
/// ),
/// );
/// }
@@ -58,7 +58,7 @@ class MessageSearchListView extends StatefulWidget {
required this.filters,
this.messageQuery,
this.sortOptions,
this.paginationParams,
this.limit = 30,
this.messageFilters,
this.separatorBuilder,
this.itemBuilder,
@@ -89,11 +89,8 @@ class MessageSearchListView extends StatefulWidget {
/// Direction can be ascending or descending.
final List<SortOption>? sortOptions;
/// Pagination parameters
/// limit: the number of users to return (max is 30)
/// offset: the offset (max is 1000)
/// message_limit: how many messages should be included to each channel
final PaginationParams? paginationParams;
/// The amount of messages requested per API call.
final int limit;
/// The message query filters to use.
/// You can query on any of the custom fields you've defined on the [Channel].
@@ -152,7 +149,7 @@ class _MessageSearchListViewState extends State<MessageSearchListView> {
filters: widget.filters,
sortOptions: widget.sortOptions,
messageQuery: widget.messageQuery,
paginationParams: widget.paginationParams,
limit: widget.limit,
messageFilters: widget.messageFilters,
messageSearchListController: _messageSearchListController,
emptyBuilder: widget.emptyBuilder ??
@@ -7,6 +7,7 @@ import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:flutter_portal/flutter_portal.dart';
import 'package:jiffy/jiffy.dart';
import 'package:stream_chat_flutter/src/attachment/url_attachment.dart';
import 'package:stream_chat_flutter/src/extension.dart';
import 'package:stream_chat_flutter/src/image_group.dart';
import 'package:stream_chat_flutter/src/message_action.dart';
@@ -15,7 +16,6 @@ import 'package:stream_chat_flutter/src/message_reactions_modal.dart';
import 'package:stream_chat_flutter/src/quoted_message_widget.dart';
import 'package:stream_chat_flutter/src/reaction_bubble.dart';
import 'package:stream_chat_flutter/src/theme/themes.dart';
import 'package:stream_chat_flutter/src/url_attachment.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
/// Widget builder for building attachments
@@ -96,7 +96,7 @@ class MessageWidget extends StatefulWidget {
this.bottomRowBuilder,
this.deletedBottomRowBuilder,
this.onReturnAction,
Map<String, AttachmentBuilder>? customAttachmentBuilders,
this.customAttachmentBuilders,
this.readList,
this.padding,
this.textPadding = const EdgeInsets.symmetric(
@@ -133,6 +133,7 @@ class MessageWidget extends StatefulWidget {
messageTheme: messageTheme,
onShowMessage: onShowMessage,
onReturnAction: onReturnAction,
onAttachmentTap: onAttachmentTap,
),
),
border,
@@ -214,6 +215,11 @@ class MessageWidget extends StatefulWidget {
),
onShowMessage: onShowMessage,
onReturnAction: onReturnAction,
onAttachmentTap: onAttachmentTap != null
? () {
onAttachmentTap(message, attachment);
}
: null,
);
}).toList(),
),
@@ -243,6 +249,11 @@ class MessageWidget extends StatefulWidget {
mediaQueryData.size.width * 0.8,
mediaQueryData.size.height * 0.3,
),
onAttachmentTap: onAttachmentTap != null
? () {
onAttachmentTap(message, attachment);
}
: null,
),
border,
reverse,
@@ -395,6 +406,9 @@ class MessageWidget extends StatefulWidget {
/// Builder for respective attachment types
final Map<String, AttachmentBuilder> attachmentBuilders;
/// Builder for respective attachment types (user facing builder)
final Map<String, AttachmentBuilder>? customAttachmentBuilders;
/// Center user avatar with bottom of the message
final bool translateUserAvatar;
@@ -519,7 +533,7 @@ class MessageWidget extends StatefulWidget {
showPinButton: showPinButton ?? this.showPinButton,
showPinHighlight: showPinHighlight ?? this.showPinHighlight,
customAttachmentBuilders:
customAttachmentBuilders ?? attachmentBuilders,
customAttachmentBuilders ?? this.customAttachmentBuilders,
translateUserAvatar: translateUserAvatar ?? this.translateUserAvatar,
onQuotedMessageTap: onQuotedMessageTap ?? this.onQuotedMessageTap,
onMessageTap: onMessageTap ?? this.onMessageTap,
@@ -567,11 +581,11 @@ class _MessageWidgetState extends State<MessageWidget>
bool get isOnlyEmoji => widget.message.text?.isOnlyEmoji == true;
bool get hasNonUrlAttachments => widget.message.attachments
.where((it) => it.ogScrapeUrl == null)
.where((it) => it.titleLink == null || it.type == 'giphy')
.isNotEmpty;
bool get hasUrlAttachments =>
widget.message.attachments.any((it) => it.ogScrapeUrl != null) == true;
bool get hasUrlAttachments => widget.message.attachments
.any((it) => it.titleLink != null && it.type != 'giphy');
bool get showBottomRow =>
showThreadReplyIndicator ||
@@ -984,9 +998,9 @@ class _MessageWidgetState extends State<MessageWidget>
Widget _buildUrlAttachment() {
final urlAttachment = widget.message.attachments
.firstWhere((element) => element.ogScrapeUrl != null);
.firstWhere((element) => element.titleLink != null);
final host = Uri.parse(urlAttachment.ogScrapeUrl!).host;
final host = Uri.parse(urlAttachment.titleLink!).host;
final splitList = host.split('.');
final hostName = splitList.length == 3 ? splitList[1] : splitList[0];
final hostDisplayName = urlAttachment.authorName?.capitalize() ??
@@ -997,6 +1011,7 @@ class _MessageWidgetState extends State<MessageWidget>
urlAttachment: urlAttachment,
hostDisplayName: hostDisplayName,
textPadding: widget.textPadding,
messageTheme: widget.messageTheme,
);
}
@@ -1155,7 +1170,9 @@ class _MessageWidgetState extends State<MessageWidget>
final attachmentGroups = <String, List<Attachment>>{};
widget.message.attachments
.where((element) => element.ogScrapeUrl == null && element.type != null)
.where((element) =>
(element.titleLink == null && element.type != null) ||
element.type == 'giphy')
.forEach((e) {
if (attachmentGroups[e.type] == null) {
attachmentGroups[e.type!] = [];
@@ -1335,7 +1352,7 @@ class _MessageWidgetState extends State<MessageWidget>
}
if (hasUrlAttachments) {
return _streamChatTheme.colorTheme.linkBg;
return widget.messageTheme.linkBackgroundColor;
}
if (isOnlyEmoji) {

Some files were not shown because too many files have changed in this diff Show More