Merge branch 'develop' into feat/positioned-list-experiment
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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 💅
|
||||
|
||||

|
||||
|
||||
@@ -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,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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', () {
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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
-1
@@ -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: [
|
||||
|
||||
+7
-5
@@ -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';
|
||||
|
||||
/// 
|
||||
/// 
|
||||
@@ -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
Reference in New Issue
Block a user