[Async Attachment Upload] Initial implementation

Signed-off-by: Sahil Kumar <[email protected]>
This commit is contained in:
Sahil Kumar
2021-02-16 21:21:15 +05:30
parent 628542f99b
commit 0a6948cf32
54 changed files with 2561 additions and 1328 deletions
@@ -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(