[Async Attachment Upload] Initial implementation
Signed-off-by: Sahil Kumar <[email protected]>
This commit is contained in:
@@ -3,6 +3,7 @@ include: package:pedantic/analysis_options.yaml
|
||||
analyzer:
|
||||
exclude:
|
||||
- lib/**/*.g.dart
|
||||
- lib/**/*.freezed.dart
|
||||
- example/*
|
||||
- test/*
|
||||
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:stream_chat/src/models/attachment_file.dart';
|
||||
import '../client.dart';
|
||||
import '../extensions/string_extension.dart';
|
||||
|
||||
///
|
||||
abstract class AttachmentUploader {
|
||||
///
|
||||
Future<String> uploadImage(
|
||||
AttachmentFile file,
|
||||
String channelId,
|
||||
String channelType, {
|
||||
ProgressCallback onSendProgress,
|
||||
CancelToken cancelToken,
|
||||
});
|
||||
|
||||
///
|
||||
Future<String> uploadFile(
|
||||
AttachmentFile file,
|
||||
String channelId,
|
||||
String channelType, {
|
||||
ProgressCallback onSendProgress,
|
||||
CancelToken cancelToken,
|
||||
});
|
||||
}
|
||||
|
||||
///
|
||||
class StreamAttachmentUploader implements AttachmentUploader {
|
||||
final StreamChatClient _client;
|
||||
|
||||
///
|
||||
const StreamAttachmentUploader(this._client);
|
||||
|
||||
@override
|
||||
Future<String> uploadImage(
|
||||
AttachmentFile file,
|
||||
String channelId,
|
||||
String channelType, {
|
||||
ProgressCallback onSendProgress,
|
||||
CancelToken cancelToken,
|
||||
}) async {
|
||||
final filename = file.path?.split('/')?.last;
|
||||
final mimeType = filename.mimeType;
|
||||
final res = await _client.sendImage(
|
||||
await MultipartFile.fromFile(
|
||||
file.path,
|
||||
filename: filename,
|
||||
contentType: mimeType,
|
||||
),
|
||||
channelId,
|
||||
channelType,
|
||||
onSendProgress: onSendProgress,
|
||||
cancelToken: cancelToken,
|
||||
);
|
||||
return res.file;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> uploadFile(
|
||||
AttachmentFile file,
|
||||
String channelId,
|
||||
String channelType, {
|
||||
ProgressCallback onSendProgress,
|
||||
CancelToken cancelToken,
|
||||
}) async {
|
||||
final filename = file.path?.split('/')?.last;
|
||||
final mimeType = filename.mimeType;
|
||||
final res = await _client.sendFile(
|
||||
await MultipartFile.fromFile(
|
||||
file.path,
|
||||
filename: filename,
|
||||
contentType: mimeType,
|
||||
),
|
||||
channelId,
|
||||
channelType,
|
||||
onSendProgress: onSendProgress,
|
||||
cancelToken: cancelToken,
|
||||
);
|
||||
return res.file;
|
||||
}
|
||||
}
|
||||
@@ -2,15 +2,16 @@ import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:pedantic/pedantic.dart' show unawaited;
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:rxdart/rxdart.dart';
|
||||
import 'package:stream_chat/src/api/retry_queue.dart';
|
||||
import 'package:stream_chat/src/event_type.dart';
|
||||
import 'package:stream_chat/src/models/attachment_file.dart';
|
||||
import 'package:stream_chat/src/models/channel_state.dart';
|
||||
import 'package:stream_chat/src/models/user.dart';
|
||||
import 'package:stream_chat/stream_chat.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
import '../client.dart';
|
||||
import '../models/event.dart';
|
||||
@@ -171,19 +172,148 @@ class Channel {
|
||||
/// Call [watch] to initialize the client or instantiate it using [Channel.fromState]
|
||||
Future<bool> get initialized => _initializedCompleter.future;
|
||||
|
||||
/// Send a message to this channel
|
||||
final _cancelableAttachmentUploadRequest = <String, CancelToken>{};
|
||||
final _messageAttachmentsUploadCompleter = <String, Completer>{};
|
||||
|
||||
/// Cancels [attachmentId] upload request. Throws exception if the request hasn't
|
||||
/// even started yet, Already completed or Already cancelled.
|
||||
///
|
||||
/// Optionally, provide a [reason] for the cancellation.
|
||||
void cancelAttachmentUpload(
|
||||
String attachmentId, {
|
||||
String reason,
|
||||
}) {
|
||||
final cancelToken = _cancelableAttachmentUploadRequest[attachmentId];
|
||||
if (cancelToken == null) {
|
||||
throw Exception(
|
||||
"Upload request for this Attachment hasn't started yet or else Already completed",
|
||||
);
|
||||
}
|
||||
if (cancelToken.isCancelled) throw Exception('Already cancelled');
|
||||
cancelToken.cancel(reason);
|
||||
}
|
||||
|
||||
/// Retries the failed [attachmentId] upload request.
|
||||
Future<void> retryAttachmentUpload(String messageId, String attachmentId) {
|
||||
return _uploadAttachments(messageId, [attachmentId]);
|
||||
}
|
||||
|
||||
Future<void> _uploadAttachments(
|
||||
String messageId,
|
||||
Iterable<String> attachmentIds,
|
||||
) {
|
||||
var message = state.messages.firstWhere(
|
||||
(it) => it.id == messageId,
|
||||
orElse: () => null,
|
||||
);
|
||||
|
||||
if (message == null) {
|
||||
throw Exception('Error, Message not found');
|
||||
}
|
||||
|
||||
final attachments = message.attachments.where((it) {
|
||||
if (it.uploadState.isSuccess) return false;
|
||||
return attachmentIds.contains(it.id);
|
||||
});
|
||||
|
||||
if (attachments.isEmpty) {
|
||||
client.logger.info('No attachments available to upload');
|
||||
if (message.attachments.every((it) => it.uploadState.isSuccess)) {
|
||||
_messageAttachmentsUploadCompleter.remove(messageId)?.complete(message);
|
||||
}
|
||||
return Future.value();
|
||||
}
|
||||
|
||||
client.logger.info('Found ${attachments.length} attachments');
|
||||
return Future.wait(attachments.map((it) {
|
||||
client.logger.info('Uploading ${it.id} attachment...');
|
||||
|
||||
void updateAttachment(Attachment attachment) {
|
||||
message = message.copyWith(
|
||||
attachments: message.attachments.map((it) {
|
||||
if (it.id != attachment.id) return it;
|
||||
return attachment;
|
||||
}).toList(growable: false));
|
||||
state?.addMessage(message);
|
||||
}
|
||||
|
||||
void onSendProgress(int sent, int total) {
|
||||
updateAttachment(it.copyWith(
|
||||
uploadState: UploadState.inProgress(uploaded: sent, total: total),
|
||||
));
|
||||
}
|
||||
|
||||
final isImage = it.type == 'image';
|
||||
final uploader = _client.attachmentUploader;
|
||||
final cancelToken = CancelToken();
|
||||
Future<String> future;
|
||||
if (isImage) {
|
||||
future = uploader.uploadImage(it.file, id, type,
|
||||
onSendProgress: onSendProgress, cancelToken: cancelToken);
|
||||
} else {
|
||||
future = uploader.uploadFile(it.file, id, type,
|
||||
onSendProgress: onSendProgress, cancelToken: cancelToken);
|
||||
}
|
||||
_cancelableAttachmentUploadRequest[it.id] = cancelToken;
|
||||
return future.then((url) {
|
||||
client.logger.info('Attachment ${it.id} uploaded successfully...');
|
||||
if (isImage) {
|
||||
updateAttachment(
|
||||
it.copyWith(imageUrl: url, uploadState: UploadState.success()),
|
||||
);
|
||||
} else {
|
||||
updateAttachment(
|
||||
it.copyWith(assetUrl: url, uploadState: UploadState.success()),
|
||||
);
|
||||
}
|
||||
}).catchError((e, stk) {
|
||||
updateAttachment(
|
||||
it.copyWith(uploadState: UploadState.failed(error: e.toString())),
|
||||
);
|
||||
}).whenComplete(() {
|
||||
_cancelableAttachmentUploadRequest.remove(it.id);
|
||||
});
|
||||
})).whenComplete(() {
|
||||
if (message.attachments.every((it) => it.uploadState.isSuccess)) {
|
||||
_messageAttachmentsUploadCompleter.remove(messageId)?.complete(message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Send a [message] to this channel. Optionally pass a [attachmentUploader]
|
||||
/// for custom attachments upload.
|
||||
///
|
||||
/// Waits for a [_messageAttachmentsUploadCompleter] to complete
|
||||
/// before actually sending the message.
|
||||
Future<SendMessageResponse> sendMessage(Message message) async {
|
||||
final messageId = message.id ?? Uuid().v4();
|
||||
// Cancelling previous completer in case it's called again in the process
|
||||
// Eg. Updating the message while the previous call is in progress.
|
||||
_messageAttachmentsUploadCompleter
|
||||
.remove(message.id)
|
||||
?.completeError('Message Cancelled');
|
||||
|
||||
final quotedMessage = state?.messages?.firstWhere(
|
||||
(m) => m.id == message?.quotedMessageId,
|
||||
orElse: () => null,
|
||||
);
|
||||
final newMessage = message.copyWith(
|
||||
message = message.copyWith(
|
||||
createdAt: message.createdAt ?? DateTime.now(),
|
||||
user: _client.state.user,
|
||||
id: messageId,
|
||||
quotedMessage: quotedMessage,
|
||||
status: MessageSendingStatus.sending,
|
||||
attachments: [
|
||||
...message.attachments.map(
|
||||
(it) {
|
||||
if (it.uploadState.isSuccess) return it;
|
||||
return it.copyWith(
|
||||
uploadState: UploadState.inProgress(
|
||||
uploaded: 0,
|
||||
total: it.file?.size ?? it.extraData['file_size'],
|
||||
),
|
||||
);
|
||||
},
|
||||
)
|
||||
],
|
||||
);
|
||||
|
||||
if (message.parentId != null && message.id == null) {
|
||||
@@ -195,17 +325,24 @@ class Channel {
|
||||
));
|
||||
}
|
||||
|
||||
state?.addMessage(newMessage);
|
||||
state?.addMessage(message);
|
||||
|
||||
try {
|
||||
final attachmentsUploadCompleter = Completer<Message>();
|
||||
_messageAttachmentsUploadCompleter[message.id] =
|
||||
attachmentsUploadCompleter;
|
||||
|
||||
unawaited(_uploadAttachments(
|
||||
message.id,
|
||||
message.attachments.map((it) => it.id),
|
||||
));
|
||||
|
||||
message = await attachmentsUploadCompleter.future;
|
||||
|
||||
final response = await _client.post(
|
||||
'$_channelURL/message',
|
||||
data: {
|
||||
'message': message
|
||||
.copyWith(
|
||||
id: messageId,
|
||||
)
|
||||
.toJson()
|
||||
'message': message.toJson(),
|
||||
},
|
||||
);
|
||||
|
||||
@@ -216,7 +353,104 @@ class Channel {
|
||||
return res;
|
||||
} catch (error) {
|
||||
if (error is DioError && error.type != DioErrorType.RESPONSE) {
|
||||
state?.retryQueue?.add([newMessage]);
|
||||
state?.retryQueue?.add([message]);
|
||||
}
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates the [message] in this channel. Optionally pass a [attachmentUploader]
|
||||
/// for custom attachments upload.
|
||||
///
|
||||
/// Waits for a [_messageAttachmentsUploadCompleter] to complete
|
||||
/// before actually updating the message.
|
||||
Future<UpdateMessageResponse> updateMessage(Message message) async {
|
||||
// Cancelling previous completer in case it's called again in the process
|
||||
// Eg. Updating the message while the previous call is in progress.
|
||||
_messageAttachmentsUploadCompleter
|
||||
.remove(message.id)
|
||||
?.completeError('Message Cancelled');
|
||||
|
||||
message = message.copyWith(
|
||||
status: MessageSendingStatus.updating,
|
||||
updatedAt: message.updatedAt ?? DateTime.now(),
|
||||
attachments: [
|
||||
...message.attachments.map(
|
||||
(it) {
|
||||
if (it.uploadState.isSuccess) return it;
|
||||
return it.copyWith(
|
||||
uploadState: UploadState.inProgress(
|
||||
uploaded: 0,
|
||||
total: it.file?.size ?? it.extraData['file_size'],
|
||||
),
|
||||
);
|
||||
},
|
||||
)
|
||||
],
|
||||
);
|
||||
|
||||
state?.addMessage(message);
|
||||
|
||||
try {
|
||||
final attachmentsUploadCompleter = Completer<Message>();
|
||||
_messageAttachmentsUploadCompleter[message.id] =
|
||||
attachmentsUploadCompleter;
|
||||
|
||||
unawaited(_uploadAttachments(
|
||||
message.id,
|
||||
message.attachments.map((it) => it.id),
|
||||
));
|
||||
|
||||
message = await attachmentsUploadCompleter.future;
|
||||
|
||||
final response = await _client.updateMessage(message);
|
||||
state?.addMessage(response?.message?.copyWith(
|
||||
ownReactions: message.ownReactions,
|
||||
));
|
||||
return response;
|
||||
} catch (error) {
|
||||
if (error is DioError && error.type != DioErrorType.RESPONSE) {
|
||||
state?.retryQueue?.add([message]);
|
||||
}
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
/// Deletes the [message] from the channel.
|
||||
Future<EmptyResponse> deleteMessage(Message message) async {
|
||||
// Directly deleting the local messages which are not yet sent to server
|
||||
if (message.status == MessageSendingStatus.sending ||
|
||||
message.status == MessageSendingStatus.failed) {
|
||||
state.addMessage(message.copyWith(
|
||||
type: 'deleted',
|
||||
status: MessageSendingStatus.sent,
|
||||
));
|
||||
|
||||
// Removing the attachments upload completer to stop the `sendMessage`
|
||||
// waiting for attachments to complete.
|
||||
_messageAttachmentsUploadCompleter
|
||||
.remove(message.id)
|
||||
?.completeError(Exception('Message deleted'));
|
||||
return EmptyResponse();
|
||||
}
|
||||
|
||||
try {
|
||||
message = message.copyWith(
|
||||
type: 'deleted',
|
||||
status: MessageSendingStatus.deleting,
|
||||
deletedAt: message.deletedAt ?? DateTime.now(),
|
||||
);
|
||||
|
||||
state?.addMessage(message);
|
||||
|
||||
final response = await _client.deleteMessage(message);
|
||||
|
||||
state?.addMessage(message.copyWith(status: MessageSendingStatus.sent));
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
if (error is DioError && error.type != DioErrorType.RESPONSE) {
|
||||
state?.retryQueue?.add([message]);
|
||||
}
|
||||
rethrow;
|
||||
}
|
||||
@@ -226,26 +460,30 @@ class Channel {
|
||||
Future<SendFileResponse> sendFile(
|
||||
MultipartFile file, {
|
||||
ProgressCallback onSendProgress,
|
||||
}) async {
|
||||
final response = await _client.post(
|
||||
'$_channelURL/file',
|
||||
data: FormData.fromMap({'file': file}),
|
||||
CancelToken cancelToken,
|
||||
}) {
|
||||
return _client.sendFile(
|
||||
file,
|
||||
id,
|
||||
type,
|
||||
onSendProgress: onSendProgress,
|
||||
cancelToken: cancelToken,
|
||||
);
|
||||
return _client.decode(response.data, SendFileResponse.fromJson);
|
||||
}
|
||||
|
||||
/// Send an image to this channel
|
||||
Future<SendImageResponse> sendImage(
|
||||
MultipartFile file, {
|
||||
MultipartFile image, {
|
||||
ProgressCallback onSendProgress,
|
||||
}) async {
|
||||
final response = await _client.post(
|
||||
'$_channelURL/image',
|
||||
data: FormData.fromMap({'file': file}),
|
||||
CancelToken cancelToken,
|
||||
}) {
|
||||
return _client.sendImage(
|
||||
image,
|
||||
id,
|
||||
type,
|
||||
onSendProgress: onSendProgress,
|
||||
cancelToken: cancelToken,
|
||||
);
|
||||
return _client.decode(response.data, SendImageResponse.fromJson);
|
||||
}
|
||||
|
||||
/// Delete a file from this channel
|
||||
@@ -975,7 +1213,13 @@ class ChannelClientState {
|
||||
?.getChannelThreads(_channel.cid)
|
||||
?.then((threads) {
|
||||
_threads = threads;
|
||||
retryFailedMessages();
|
||||
})?.then((_) {
|
||||
_channel._client.chatPersistenceClient
|
||||
?.getChannelStateByCid(_channel.cid)
|
||||
?.then((state) {
|
||||
updateChannelState(state);
|
||||
retryFailedMessages();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -125,10 +125,7 @@ class RetryQueue {
|
||||
Future<void> _sendMessage(Message message) async {
|
||||
if (message.status == MessageSendingStatus.failed_update ||
|
||||
message.status == MessageSendingStatus.updating) {
|
||||
await channel.client.updateMessage(
|
||||
message,
|
||||
channel.cid,
|
||||
);
|
||||
await channel.updateMessage(message);
|
||||
} else if (message.status == MessageSendingStatus.failed ||
|
||||
message.status == MessageSendingStatus.sending) {
|
||||
await channel.sendMessage(
|
||||
@@ -136,10 +133,7 @@ class RetryQueue {
|
||||
);
|
||||
} else if (message.status == MessageSendingStatus.failed_delete ||
|
||||
message.status == MessageSendingStatus.deleting) {
|
||||
await channel.client.deleteMessage(
|
||||
message,
|
||||
channel.cid,
|
||||
);
|
||||
await channel.client.deleteMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import 'package:stream_chat/src/models/own_user.dart';
|
||||
import 'package:stream_chat/version.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
import 'api/attachment_uploader.dart';
|
||||
import 'api/channel.dart';
|
||||
import 'api/connection_status.dart';
|
||||
import 'api/requests.dart';
|
||||
@@ -78,6 +79,7 @@ class StreamChatClient {
|
||||
Duration receiveTimeout = const Duration(seconds: 6),
|
||||
Dio httpClient,
|
||||
RetryPolicy retryPolicy,
|
||||
this.attachmentUploader,
|
||||
}) {
|
||||
_retryPolicy ??= RetryPolicy(
|
||||
retryTimeout: (StreamChatClient client, int attempt, ApiError error) =>
|
||||
@@ -86,6 +88,8 @@ class StreamChatClient {
|
||||
attempt < 5,
|
||||
);
|
||||
|
||||
attachmentUploader ??= StreamAttachmentUploader(this);
|
||||
|
||||
state = ClientState(this);
|
||||
|
||||
_setupLogger();
|
||||
@@ -97,6 +101,9 @@ class StreamChatClient {
|
||||
/// Chat persistence client
|
||||
ChatPersistenceClient chatPersistenceClient;
|
||||
|
||||
/// Attachment uploader
|
||||
AttachmentUploader attachmentUploader;
|
||||
|
||||
/// Whether the chat persistence is available or not
|
||||
bool get persistenceEnabled => chatPersistenceClient != null;
|
||||
|
||||
@@ -795,12 +802,14 @@ class StreamChatClient {
|
||||
String path, {
|
||||
dynamic data,
|
||||
ProgressCallback onSendProgress,
|
||||
CancelToken cancelToken,
|
||||
}) async {
|
||||
try {
|
||||
final response = await httpClient.post<String>(
|
||||
path,
|
||||
data: data,
|
||||
onSendProgress: onSendProgress,
|
||||
cancelToken: cancelToken,
|
||||
);
|
||||
return response;
|
||||
} on DioError catch (error) {
|
||||
@@ -1030,6 +1039,40 @@ class StreamChatClient {
|
||||
response.data, SearchMessagesResponse.fromJson);
|
||||
}
|
||||
|
||||
/// Send a [file] to the [channelId] of type [channelType]
|
||||
Future<SendFileResponse> sendFile(
|
||||
MultipartFile file,
|
||||
String channelId,
|
||||
String channelType, {
|
||||
ProgressCallback onSendProgress,
|
||||
CancelToken cancelToken,
|
||||
}) async {
|
||||
final response = await post(
|
||||
'/channels/$channelType/$channelId/file',
|
||||
data: FormData.fromMap({'file': file}),
|
||||
onSendProgress: onSendProgress,
|
||||
cancelToken: cancelToken,
|
||||
);
|
||||
return decode(response.data, SendFileResponse.fromJson);
|
||||
}
|
||||
|
||||
/// Send a [image] to the [channelId] of type [channelType]
|
||||
Future<SendImageResponse> sendImage(
|
||||
MultipartFile image,
|
||||
String channelId,
|
||||
String channelType, {
|
||||
ProgressCallback onSendProgress,
|
||||
CancelToken cancelToken,
|
||||
}) async {
|
||||
final response = await post(
|
||||
'/channels/$channelType/$channelId/image',
|
||||
data: FormData.fromMap({'file': image}),
|
||||
onSendProgress: onSendProgress,
|
||||
cancelToken: cancelToken,
|
||||
);
|
||||
return decode(response.data, SendImageResponse.fromJson);
|
||||
}
|
||||
|
||||
/// Add a device for Push Notifications.
|
||||
Future<EmptyResponse> addDevice(String id, PushProvider pushProvider) async {
|
||||
final response = await post('/devices', data: {
|
||||
@@ -1202,77 +1245,18 @@ class StreamChatClient {
|
||||
}
|
||||
|
||||
/// Update the given message
|
||||
Future<UpdateMessageResponse> updateMessage(
|
||||
Message message, [
|
||||
String cid,
|
||||
]) async {
|
||||
message = message.copyWith(
|
||||
status: MessageSendingStatus.updating,
|
||||
updatedAt: message.updatedAt ?? DateTime.now(),
|
||||
Future<UpdateMessageResponse> updateMessage(Message message) async {
|
||||
final response = await post(
|
||||
'/messages/${message.id}',
|
||||
data: {'message': message},
|
||||
);
|
||||
|
||||
final channel = state?.channels != null ? state?.channels[cid] : null;
|
||||
channel?.state?.addMessage(message);
|
||||
|
||||
return post('/messages/${message.id}', data: {'message': message})
|
||||
.then((res) {
|
||||
final updateMessageResponse = decode(
|
||||
res?.data,
|
||||
UpdateMessageResponse.fromJson,
|
||||
);
|
||||
|
||||
channel?.state?.addMessage(updateMessageResponse?.message?.copyWith(
|
||||
ownReactions: message.ownReactions,
|
||||
));
|
||||
|
||||
return updateMessageResponse;
|
||||
}).catchError((error) {
|
||||
if (error is DioError &&
|
||||
error.type != DioErrorType.RESPONSE &&
|
||||
state?.channels != null) {
|
||||
channel?.state?.retryQueue?.add([message]);
|
||||
}
|
||||
throw error;
|
||||
});
|
||||
return decode(response.data, UpdateMessageResponse.fromJson);
|
||||
}
|
||||
|
||||
/// Deletes the given message
|
||||
Future<EmptyResponse> deleteMessage(Message message, [String cid]) async {
|
||||
if (message.status == MessageSendingStatus.failed) {
|
||||
state.channels[cid].state.addMessage(message.copyWith(
|
||||
type: 'deleted',
|
||||
status: MessageSendingStatus.sent,
|
||||
));
|
||||
return EmptyResponse();
|
||||
}
|
||||
|
||||
try {
|
||||
message = message.copyWith(
|
||||
type: 'deleted',
|
||||
status: MessageSendingStatus.deleting,
|
||||
deletedAt: message.deletedAt ?? DateTime.now(),
|
||||
);
|
||||
|
||||
if (state?.channels != null) {
|
||||
state.channels[cid]?.state?.addMessage(message);
|
||||
}
|
||||
|
||||
final response = await delete('/messages/${message.id}');
|
||||
|
||||
if (state?.channels != null) {
|
||||
state.channels[cid]?.state
|
||||
?.addMessage(message.copyWith(status: MessageSendingStatus.sent));
|
||||
}
|
||||
|
||||
return decode(response.data, EmptyResponse.fromJson);
|
||||
} catch (error) {
|
||||
if (error is DioError &&
|
||||
error.type != DioErrorType.RESPONSE &&
|
||||
state?.channels != null) {
|
||||
state.channels[cid]?.state?.retryQueue?.add([message]);
|
||||
}
|
||||
rethrow;
|
||||
}
|
||||
Future<EmptyResponse> deleteMessage(Message message) async {
|
||||
final response = await delete('/messages/${message.id}');
|
||||
return decode(response.data, EmptyResponse.fromJson);
|
||||
}
|
||||
|
||||
/// Get a message by id
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import 'package:http_parser/http_parser.dart' as http_parser;
|
||||
import 'package:mime/mime.dart';
|
||||
|
||||
/// Useful extension functions for [String]
|
||||
extension StringX on String {
|
||||
/// Returns the mime type from the passed file name.
|
||||
http_parser.MediaType get mimeType {
|
||||
if (this == null) return null;
|
||||
if (toLowerCase().endsWith('heic')) {
|
||||
return http_parser.MediaType.parse('image/heic');
|
||||
} else {
|
||||
return http_parser.MediaType.parse(lookupMimeType(this));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
// ignore_for_file: public_member_api_docs
|
||||
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
import 'package:stream_chat/src/models/attachment_file.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
import 'action.dart';
|
||||
import 'serialization.dart';
|
||||
@@ -52,10 +54,21 @@ class Attachment {
|
||||
|
||||
final Uri localUri;
|
||||
|
||||
/// The file present inside this attachment.
|
||||
final AttachmentFile file;
|
||||
|
||||
/// The current upload state of the attachment
|
||||
final UploadState uploadState;
|
||||
|
||||
/// Map of custom channel extraData
|
||||
@JsonKey(includeIfNull: false)
|
||||
final Map<String, dynamic> extraData;
|
||||
|
||||
/// The attachment ID.
|
||||
///
|
||||
/// This is created locally for uniquely identifying a attachment.
|
||||
final String id;
|
||||
|
||||
/// Known top level fields.
|
||||
/// Useful for [Serialization] methods.
|
||||
static const topLevelFields = [
|
||||
@@ -79,11 +92,20 @@ class Attachment {
|
||||
'actions',
|
||||
];
|
||||
|
||||
/// Known db specific top level fields.
|
||||
/// Useful for [Serialization] methods.
|
||||
static const dbSpecificTopLevelFields = [
|
||||
'id',
|
||||
'upload_state',
|
||||
'file',
|
||||
];
|
||||
|
||||
/// Constructor used for json serialization
|
||||
Attachment({
|
||||
String id,
|
||||
this.type,
|
||||
this.titleLink,
|
||||
this.title,
|
||||
String title,
|
||||
this.thumbUrl,
|
||||
this.text,
|
||||
this.pretext,
|
||||
@@ -100,8 +122,11 @@ class Attachment {
|
||||
this.assetUrl,
|
||||
this.actions,
|
||||
this.extraData,
|
||||
this.localUri,
|
||||
});
|
||||
this.file,
|
||||
this.uploadState,
|
||||
}) : id = id ?? Uuid().v4(),
|
||||
title = title ?? file?.name,
|
||||
localUri = file?.path != null ? Uri.parse(file.path) : null;
|
||||
|
||||
/// Create a new instance from a json
|
||||
factory Attachment.fromJson(Map<String, dynamic> json) {
|
||||
@@ -111,9 +136,21 @@ class Attachment {
|
||||
|
||||
/// Serialize to json
|
||||
Map<String, dynamic> toJson() => Serialization.moveFromExtraDataToRoot(
|
||||
_$AttachmentToJson(this), topLevelFields);
|
||||
_$AttachmentToJson(this), topLevelFields)
|
||||
..removeWhere((key, value) => dbSpecificTopLevelFields.contains(key));
|
||||
|
||||
/// Create a new instance from a db data
|
||||
factory Attachment.fromData(Map<String, dynamic> json) {
|
||||
return _$AttachmentFromJson(Serialization.moveToExtraDataFromRoot(
|
||||
json, topLevelFields + dbSpecificTopLevelFields));
|
||||
}
|
||||
|
||||
/// Serialize to db data
|
||||
Map<String, dynamic> toData() => Serialization.moveFromExtraDataToRoot(
|
||||
_$AttachmentToJson(this), topLevelFields + dbSpecificTopLevelFields);
|
||||
|
||||
Attachment copyWith({
|
||||
String id,
|
||||
String type,
|
||||
String titleLink,
|
||||
String title,
|
||||
@@ -132,10 +169,12 @@ class Attachment {
|
||||
String authorIcon,
|
||||
String assetUrl,
|
||||
List<Action> actions,
|
||||
Uri localUri,
|
||||
AttachmentFile file,
|
||||
UploadState uploadState,
|
||||
Map<String, dynamic> extraData,
|
||||
}) =>
|
||||
Attachment(
|
||||
id: id ?? this.id,
|
||||
type: type ?? this.type,
|
||||
titleLink: titleLink ?? this.titleLink,
|
||||
title: title ?? this.title,
|
||||
@@ -154,7 +193,8 @@ class Attachment {
|
||||
authorIcon: authorIcon ?? this.authorIcon,
|
||||
assetUrl: assetUrl ?? this.assetUrl,
|
||||
actions: actions ?? this.actions,
|
||||
localUri: localUri ?? this.localUri,
|
||||
file: file ?? this.file,
|
||||
uploadState: uploadState ?? this.uploadState,
|
||||
extraData: extraData ?? this.extraData,
|
||||
);
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ part of 'attachment.dart';
|
||||
|
||||
Attachment _$AttachmentFromJson(Map json) {
|
||||
return Attachment(
|
||||
id: json['id'] as String,
|
||||
type: json['type'] as String,
|
||||
titleLink: json['title_link'] as String,
|
||||
title: json['title'] as String,
|
||||
@@ -35,9 +36,16 @@ Attachment _$AttachmentFromJson(Map json) {
|
||||
extraData: (json['extra_data'] as Map)?.map(
|
||||
(k, e) => MapEntry(k as String, e),
|
||||
),
|
||||
localUri: json['local_uri'] == null
|
||||
file: json['file'] == null
|
||||
? null
|
||||
: Uri.parse(json['local_uri'] as String),
|
||||
: AttachmentFile.fromJson((json['file'] as Map)?.map(
|
||||
(k, e) => MapEntry(k as String, e),
|
||||
)),
|
||||
uploadState: json['upload_state'] == null
|
||||
? null
|
||||
: UploadState.fromJson((json['upload_state'] as Map)?.map(
|
||||
(k, e) => MapEntry(k as String, e),
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -68,7 +76,9 @@ Map<String, dynamic> _$AttachmentToJson(Attachment instance) {
|
||||
writeNotNull('author_icon', instance.authorIcon);
|
||||
writeNotNull('asset_url', instance.assetUrl);
|
||||
writeNotNull('actions', instance.actions?.map((e) => e?.toJson())?.toList());
|
||||
writeNotNull('local_uri', instance.localUri?.toString());
|
||||
writeNotNull('file', instance.file?.toJson());
|
||||
writeNotNull('upload_state', instance.uploadState?.toJson());
|
||||
writeNotNull('extra_data', instance.extraData);
|
||||
writeNotNull('id', instance.id);
|
||||
return val;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'attachment_file.freezed.dart';
|
||||
|
||||
part 'attachment_file.g.dart';
|
||||
|
||||
///
|
||||
@freezed
|
||||
abstract class UploadState with _$UploadState {
|
||||
///
|
||||
const factory UploadState.inProgress({int uploaded, int total}) = InProgress;
|
||||
|
||||
///
|
||||
const factory UploadState.success() = Success;
|
||||
|
||||
///
|
||||
const factory UploadState.failed({@required String error}) = Failed;
|
||||
|
||||
/// Creates a new instance from a json
|
||||
factory UploadState.fromJson(Map<String, dynamic> json) =>
|
||||
_$UploadStateFromJson(json);
|
||||
}
|
||||
|
||||
///
|
||||
extension UploadStateX on UploadState {
|
||||
///
|
||||
bool get isInProgress => this is InProgress;
|
||||
|
||||
///
|
||||
bool get isSuccess => this is Success;
|
||||
|
||||
///
|
||||
bool get isFailed => this is Failed;
|
||||
}
|
||||
|
||||
Uint8List _fromString(String bytes) => Uint8List.fromList(bytes.codeUnits);
|
||||
|
||||
String _toString(Uint8List bytes) => String.fromCharCodes(bytes);
|
||||
|
||||
///
|
||||
@JsonSerializable()
|
||||
class AttachmentFile {
|
||||
///
|
||||
const AttachmentFile({
|
||||
this.path,
|
||||
this.name,
|
||||
this.bytes,
|
||||
this.size,
|
||||
});
|
||||
|
||||
/// The absolute path for a cached copy of this file. It can be used to create a
|
||||
/// file instance with a descriptor for the given path.
|
||||
/// ```
|
||||
/// final File myFile = File(platformFile.path);
|
||||
/// ```
|
||||
final String path;
|
||||
|
||||
/// File name including its extension.
|
||||
final String name;
|
||||
|
||||
/// Byte data for this file. Particularly useful if you want to manipulate its data
|
||||
/// or easily upload to somewhere else.
|
||||
@JsonKey(toJson: _toString, fromJson: _fromString)
|
||||
final Uint8List bytes;
|
||||
|
||||
/// The file size in bytes.
|
||||
final int size;
|
||||
|
||||
/// File extension for this file.
|
||||
String get extension => name?.split('.')?.last;
|
||||
|
||||
/// Create a new instance from a json
|
||||
factory AttachmentFile.fromJson(Map<String, dynamic> json) {
|
||||
return _$AttachmentFileFromJson(json);
|
||||
}
|
||||
|
||||
/// Serialize to json
|
||||
Map<String, dynamic> toJson() => _$AttachmentFileToJson(this);
|
||||
}
|
||||
@@ -0,0 +1,486 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: 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
|
||||
|
||||
part of 'attachment_file.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
T _$identity<T>(T value) => value;
|
||||
UploadState _$UploadStateFromJson(Map<String, dynamic> json) {
|
||||
switch (json['runtimeType'] as String) {
|
||||
case 'inProgress':
|
||||
return InProgress.fromJson(json);
|
||||
case 'success':
|
||||
return Success.fromJson(json);
|
||||
case 'failed':
|
||||
return Failed.fromJson(json);
|
||||
|
||||
default:
|
||||
throw FallThroughError();
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$UploadStateTearOff {
|
||||
const _$UploadStateTearOff();
|
||||
|
||||
// ignore: unused_element
|
||||
InProgress inProgress({int uploaded, int total}) {
|
||||
return InProgress(
|
||||
uploaded: uploaded,
|
||||
total: total,
|
||||
);
|
||||
}
|
||||
|
||||
// ignore: unused_element
|
||||
Success success() {
|
||||
return const Success();
|
||||
}
|
||||
|
||||
// ignore: unused_element
|
||||
Failed failed({@required String error}) {
|
||||
return Failed(
|
||||
error: error,
|
||||
);
|
||||
}
|
||||
|
||||
// ignore: unused_element
|
||||
UploadState fromJson(Map<String, Object> json) {
|
||||
return UploadState.fromJson(json);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
// ignore: unused_element
|
||||
const $UploadState = _$UploadStateTearOff();
|
||||
|
||||
/// @nodoc
|
||||
mixin _$UploadState {
|
||||
@optionalTypeArgs
|
||||
TResult when<TResult extends Object>({
|
||||
@required TResult inProgress(int uploaded, int total),
|
||||
@required TResult success(),
|
||||
@required TResult failed(String error),
|
||||
});
|
||||
@optionalTypeArgs
|
||||
TResult maybeWhen<TResult extends Object>({
|
||||
TResult inProgress(int uploaded, int total),
|
||||
TResult success(),
|
||||
TResult failed(String error),
|
||||
@required TResult orElse(),
|
||||
});
|
||||
@optionalTypeArgs
|
||||
TResult map<TResult extends Object>({
|
||||
@required TResult inProgress(InProgress value),
|
||||
@required TResult success(Success value),
|
||||
@required TResult failed(Failed value),
|
||||
});
|
||||
@optionalTypeArgs
|
||||
TResult maybeMap<TResult extends Object>({
|
||||
TResult inProgress(InProgress value),
|
||||
TResult success(Success value),
|
||||
TResult failed(Failed value),
|
||||
@required TResult orElse(),
|
||||
});
|
||||
Map<String, dynamic> toJson();
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $UploadStateCopyWith<$Res> {
|
||||
factory $UploadStateCopyWith(
|
||||
UploadState value, $Res Function(UploadState) then) =
|
||||
_$UploadStateCopyWithImpl<$Res>;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$UploadStateCopyWithImpl<$Res> implements $UploadStateCopyWith<$Res> {
|
||||
_$UploadStateCopyWithImpl(this._value, this._then);
|
||||
|
||||
final UploadState _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function(UploadState) _then;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $InProgressCopyWith<$Res> {
|
||||
factory $InProgressCopyWith(
|
||||
InProgress value, $Res Function(InProgress) then) =
|
||||
_$InProgressCopyWithImpl<$Res>;
|
||||
$Res call({int uploaded, int total});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$InProgressCopyWithImpl<$Res> extends _$UploadStateCopyWithImpl<$Res>
|
||||
implements $InProgressCopyWith<$Res> {
|
||||
_$InProgressCopyWithImpl(InProgress _value, $Res Function(InProgress) _then)
|
||||
: super(_value, (v) => _then(v as InProgress));
|
||||
|
||||
@override
|
||||
InProgress get _value => super._value as InProgress;
|
||||
|
||||
@override
|
||||
$Res call({
|
||||
Object uploaded = freezed,
|
||||
Object total = freezed,
|
||||
}) {
|
||||
return _then(InProgress(
|
||||
uploaded: uploaded == freezed ? _value.uploaded : uploaded as int,
|
||||
total: total == freezed ? _value.total : total as int,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@JsonSerializable()
|
||||
|
||||
/// @nodoc
|
||||
class _$InProgress implements InProgress {
|
||||
const _$InProgress({this.uploaded, this.total});
|
||||
|
||||
factory _$InProgress.fromJson(Map<String, dynamic> json) =>
|
||||
_$_$InProgressFromJson(json);
|
||||
|
||||
@override
|
||||
final int uploaded;
|
||||
@override
|
||||
final int total;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'UploadState.inProgress(uploaded: $uploaded, total: $total)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(dynamic other) {
|
||||
return identical(this, other) ||
|
||||
(other is InProgress &&
|
||||
(identical(other.uploaded, uploaded) ||
|
||||
const DeepCollectionEquality()
|
||||
.equals(other.uploaded, uploaded)) &&
|
||||
(identical(other.total, total) ||
|
||||
const DeepCollectionEquality().equals(other.total, total)));
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
runtimeType.hashCode ^
|
||||
const DeepCollectionEquality().hash(uploaded) ^
|
||||
const DeepCollectionEquality().hash(total);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
$InProgressCopyWith<InProgress> get copyWith =>
|
||||
_$InProgressCopyWithImpl<InProgress>(this, _$identity);
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult when<TResult extends Object>({
|
||||
@required TResult inProgress(int uploaded, int total),
|
||||
@required TResult success(),
|
||||
@required TResult failed(String error),
|
||||
}) {
|
||||
assert(inProgress != null);
|
||||
assert(success != null);
|
||||
assert(failed != null);
|
||||
return inProgress(uploaded, total);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult maybeWhen<TResult extends Object>({
|
||||
TResult inProgress(int uploaded, int total),
|
||||
TResult success(),
|
||||
TResult failed(String error),
|
||||
@required TResult orElse(),
|
||||
}) {
|
||||
assert(orElse != null);
|
||||
if (inProgress != null) {
|
||||
return inProgress(uploaded, total);
|
||||
}
|
||||
return orElse();
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult map<TResult extends Object>({
|
||||
@required TResult inProgress(InProgress value),
|
||||
@required TResult success(Success value),
|
||||
@required TResult failed(Failed value),
|
||||
}) {
|
||||
assert(inProgress != null);
|
||||
assert(success != null);
|
||||
assert(failed != null);
|
||||
return inProgress(this);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult maybeMap<TResult extends Object>({
|
||||
TResult inProgress(InProgress value),
|
||||
TResult success(Success value),
|
||||
TResult failed(Failed value),
|
||||
@required TResult orElse(),
|
||||
}) {
|
||||
assert(orElse != null);
|
||||
if (inProgress != null) {
|
||||
return inProgress(this);
|
||||
}
|
||||
return orElse();
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$_$InProgressToJson(this)..['runtimeType'] = 'inProgress';
|
||||
}
|
||||
}
|
||||
|
||||
abstract class InProgress implements UploadState {
|
||||
const factory InProgress({int uploaded, int total}) = _$InProgress;
|
||||
|
||||
factory InProgress.fromJson(Map<String, dynamic> json) =
|
||||
_$InProgress.fromJson;
|
||||
|
||||
int get uploaded;
|
||||
int get total;
|
||||
@JsonKey(ignore: true)
|
||||
$InProgressCopyWith<InProgress> get copyWith;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $SuccessCopyWith<$Res> {
|
||||
factory $SuccessCopyWith(Success value, $Res Function(Success) then) =
|
||||
_$SuccessCopyWithImpl<$Res>;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$SuccessCopyWithImpl<$Res> extends _$UploadStateCopyWithImpl<$Res>
|
||||
implements $SuccessCopyWith<$Res> {
|
||||
_$SuccessCopyWithImpl(Success _value, $Res Function(Success) _then)
|
||||
: super(_value, (v) => _then(v as Success));
|
||||
|
||||
@override
|
||||
Success get _value => super._value as Success;
|
||||
}
|
||||
|
||||
@JsonSerializable()
|
||||
|
||||
/// @nodoc
|
||||
class _$Success implements Success {
|
||||
const _$Success();
|
||||
|
||||
factory _$Success.fromJson(Map<String, dynamic> json) =>
|
||||
_$_$SuccessFromJson(json);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'UploadState.success()';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(dynamic other) {
|
||||
return identical(this, other) || (other is Success);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => runtimeType.hashCode;
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult when<TResult extends Object>({
|
||||
@required TResult inProgress(int uploaded, int total),
|
||||
@required TResult success(),
|
||||
@required TResult failed(String error),
|
||||
}) {
|
||||
assert(inProgress != null);
|
||||
assert(success != null);
|
||||
assert(failed != null);
|
||||
return success();
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult maybeWhen<TResult extends Object>({
|
||||
TResult inProgress(int uploaded, int total),
|
||||
TResult success(),
|
||||
TResult failed(String error),
|
||||
@required TResult orElse(),
|
||||
}) {
|
||||
assert(orElse != null);
|
||||
if (success != null) {
|
||||
return success();
|
||||
}
|
||||
return orElse();
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult map<TResult extends Object>({
|
||||
@required TResult inProgress(InProgress value),
|
||||
@required TResult success(Success value),
|
||||
@required TResult failed(Failed value),
|
||||
}) {
|
||||
assert(inProgress != null);
|
||||
assert(success != null);
|
||||
assert(failed != null);
|
||||
return success(this);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult maybeMap<TResult extends Object>({
|
||||
TResult inProgress(InProgress value),
|
||||
TResult success(Success value),
|
||||
TResult failed(Failed value),
|
||||
@required TResult orElse(),
|
||||
}) {
|
||||
assert(orElse != null);
|
||||
if (success != null) {
|
||||
return success(this);
|
||||
}
|
||||
return orElse();
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$_$SuccessToJson(this)..['runtimeType'] = 'success';
|
||||
}
|
||||
}
|
||||
|
||||
abstract class Success implements UploadState {
|
||||
const factory Success() = _$Success;
|
||||
|
||||
factory Success.fromJson(Map<String, dynamic> json) = _$Success.fromJson;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $FailedCopyWith<$Res> {
|
||||
factory $FailedCopyWith(Failed value, $Res Function(Failed) then) =
|
||||
_$FailedCopyWithImpl<$Res>;
|
||||
$Res call({String error});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$FailedCopyWithImpl<$Res> extends _$UploadStateCopyWithImpl<$Res>
|
||||
implements $FailedCopyWith<$Res> {
|
||||
_$FailedCopyWithImpl(Failed _value, $Res Function(Failed) _then)
|
||||
: super(_value, (v) => _then(v as Failed));
|
||||
|
||||
@override
|
||||
Failed get _value => super._value as Failed;
|
||||
|
||||
@override
|
||||
$Res call({
|
||||
Object error = freezed,
|
||||
}) {
|
||||
return _then(Failed(
|
||||
error: error == freezed ? _value.error : error as String,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@JsonSerializable()
|
||||
|
||||
/// @nodoc
|
||||
class _$Failed implements Failed {
|
||||
const _$Failed({@required this.error}) : assert(error != null);
|
||||
|
||||
factory _$Failed.fromJson(Map<String, dynamic> json) =>
|
||||
_$_$FailedFromJson(json);
|
||||
|
||||
@override
|
||||
final String error;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'UploadState.failed(error: $error)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(dynamic other) {
|
||||
return identical(this, other) ||
|
||||
(other is Failed &&
|
||||
(identical(other.error, error) ||
|
||||
const DeepCollectionEquality().equals(other.error, error)));
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
runtimeType.hashCode ^ const DeepCollectionEquality().hash(error);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
$FailedCopyWith<Failed> get copyWith =>
|
||||
_$FailedCopyWithImpl<Failed>(this, _$identity);
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult when<TResult extends Object>({
|
||||
@required TResult inProgress(int uploaded, int total),
|
||||
@required TResult success(),
|
||||
@required TResult failed(String error),
|
||||
}) {
|
||||
assert(inProgress != null);
|
||||
assert(success != null);
|
||||
assert(failed != null);
|
||||
return failed(error);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult maybeWhen<TResult extends Object>({
|
||||
TResult inProgress(int uploaded, int total),
|
||||
TResult success(),
|
||||
TResult failed(String error),
|
||||
@required TResult orElse(),
|
||||
}) {
|
||||
assert(orElse != null);
|
||||
if (failed != null) {
|
||||
return failed(error);
|
||||
}
|
||||
return orElse();
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult map<TResult extends Object>({
|
||||
@required TResult inProgress(InProgress value),
|
||||
@required TResult success(Success value),
|
||||
@required TResult failed(Failed value),
|
||||
}) {
|
||||
assert(inProgress != null);
|
||||
assert(success != null);
|
||||
assert(failed != null);
|
||||
return failed(this);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult maybeMap<TResult extends Object>({
|
||||
TResult inProgress(InProgress value),
|
||||
TResult success(Success value),
|
||||
TResult failed(Failed value),
|
||||
@required TResult orElse(),
|
||||
}) {
|
||||
assert(orElse != null);
|
||||
if (failed != null) {
|
||||
return failed(this);
|
||||
}
|
||||
return orElse();
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$_$FailedToJson(this)..['runtimeType'] = 'failed';
|
||||
}
|
||||
}
|
||||
|
||||
abstract class Failed implements UploadState {
|
||||
const factory Failed({@required String error}) = _$Failed;
|
||||
|
||||
factory Failed.fromJson(Map<String, dynamic> json) = _$Failed.fromJson;
|
||||
|
||||
String get error;
|
||||
@JsonKey(ignore: true)
|
||||
$FailedCopyWith<Failed> get copyWith;
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'attachment_file.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
AttachmentFile _$AttachmentFileFromJson(Map json) {
|
||||
return AttachmentFile(
|
||||
path: json['path'] as String,
|
||||
name: json['name'] as String,
|
||||
bytes: _fromString(json['bytes'] as String),
|
||||
size: json['size'] as int,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> _$AttachmentFileToJson(AttachmentFile instance) =>
|
||||
<String, dynamic>{
|
||||
'path': instance.path,
|
||||
'name': instance.name,
|
||||
'bytes': _toString(instance.bytes),
|
||||
'size': instance.size,
|
||||
};
|
||||
|
||||
_$InProgress _$_$InProgressFromJson(Map json) {
|
||||
return _$InProgress(
|
||||
uploaded: json['uploaded'] as int,
|
||||
total: json['total'] as int,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> _$_$InProgressToJson(_$InProgress instance) =>
|
||||
<String, dynamic>{
|
||||
'uploaded': instance.uploaded,
|
||||
'total': instance.total,
|
||||
};
|
||||
|
||||
_$Success _$_$SuccessFromJson(Map json) {
|
||||
return _$Success();
|
||||
}
|
||||
|
||||
Map<String, dynamic> _$_$SuccessToJson(_$Success instance) =>
|
||||
<String, dynamic>{};
|
||||
|
||||
_$Failed _$_$FailedFromJson(Map json) {
|
||||
return _$Failed(
|
||||
error: json['error'] as String,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> _$_$FailedToJson(_$Failed instance) => <String, dynamic>{
|
||||
'error': instance.error,
|
||||
};
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
import 'attachment.dart';
|
||||
import 'reaction.dart';
|
||||
@@ -163,7 +164,7 @@ class Message {
|
||||
|
||||
/// Constructor used for json serialization
|
||||
Message({
|
||||
this.id,
|
||||
String id,
|
||||
this.text,
|
||||
this.type,
|
||||
this.attachments,
|
||||
@@ -187,7 +188,7 @@ class Message {
|
||||
this.extraData,
|
||||
this.deletedAt,
|
||||
this.status = MessageSendingStatus.sent,
|
||||
});
|
||||
}) : id = id ?? Uuid().v4();
|
||||
|
||||
/// Create a new instance from a json
|
||||
factory Message.fromJson(Map<String, dynamic> json) => _$MessageFromJson(
|
||||
|
||||
@@ -10,10 +10,12 @@ export './src/api/connection_status.dart';
|
||||
export './src/api/requests.dart';
|
||||
export './src/api/requests.dart';
|
||||
export './src/api/responses.dart';
|
||||
export './src/api/attachment_uploader.dart' show AttachmentUploader;
|
||||
export './src/client.dart';
|
||||
export './src/event_type.dart';
|
||||
export './src/models/action.dart';
|
||||
export './src/models/attachment.dart';
|
||||
export './src/models/attachment_file.dart';
|
||||
export './src/models/channel_config.dart';
|
||||
export './src/models/channel_model.dart';
|
||||
export './src/models/channel_state.dart';
|
||||
@@ -27,4 +29,5 @@ export './src/models/own_user.dart';
|
||||
export './src/models/reaction.dart';
|
||||
export './src/models/read.dart';
|
||||
export './src/models/user.dart';
|
||||
export './src/extensions/string_extension.dart';
|
||||
export './src/db/chat_persistence_client.dart';
|
||||
|
||||
@@ -19,9 +19,11 @@ dependencies:
|
||||
collection: ^1.14.13
|
||||
pedantic: ^1.9.2
|
||||
meta: ^1.2.4
|
||||
freezed_annotation: ^0.12.0
|
||||
|
||||
dev_dependencies:
|
||||
build_runner: ^1.10.0
|
||||
json_serializable: ^3.3.0
|
||||
test: ^1.15.7
|
||||
mockito: ^4.1.1
|
||||
freezed: ^0.12.7
|
||||
|
||||
Reference in New Issue
Block a user