Skip to content
107 changes: 107 additions & 0 deletions migrations/v10-migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ This guide includes breaking changes grouped by release phase:

### 🚧 v10.0.0-beta.7

- [AttachmentFileUploader](#-attachmentfileuploader)
- [MessageState](#-messagestate)

### 🚧 v10.0.0-beta.4
Expand All @@ -34,6 +35,111 @@ This guide includes breaking changes grouped by release phase:

## 🧪 Migration for v10.0.0-beta.7

### 🛠 AttachmentFileUploader

#### Key Changes:

- `AttachmentFileUploader` interface now includes four new abstract methods: `uploadImage`, `uploadFile`, `removeImage`, and `removeFile`.
- Custom implementations must implement these new standalone upload/removal methods.

#### Migration Steps:

**Before:**
```dart
class CustomAttachmentFileUploader implements AttachmentFileUploader {
// Only needed to implement sendImage, sendFile, deleteImage, deleteFile

@override
Future<SendImageResponse> sendImage(/* ... */) async {
// Implementation
}

@override
Future<SendFileResponse> sendFile(/* ... */) async {
// Implementation
}

@override
Future<EmptyResponse> deleteImage(/* ... */) async {
// Implementation
}

@override
Future<EmptyResponse> deleteFile(/* ... */) async {
// Implementation
}
}
```

**After:**
```dart
class CustomAttachmentFileUploader implements AttachmentFileUploader {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we ask them to extend AttachmentFileUploader we can have the methods default to UnimplementedError, but it wouldn't be a breaking change if we add a method that they might not use directly.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup,make sense

// Must now implement all 8 methods including the new standalone ones

@override
Future<SendImageResponse> sendImage(/* ... */) async {
// Implementation
}

@override
Future<SendFileResponse> sendFile(/* ... */) async {
// Implementation
}

@override
Future<EmptyResponse> deleteImage(/* ... */) async {
// Implementation
}

@override
Future<EmptyResponse> deleteFile(/* ... */) async {
// Implementation
}

// New required methods
@override
Future<UploadImageResponse> uploadImage(
AttachmentFile image, {
ProgressCallback? onSendProgress,
CancelToken? cancelToken,
}) async {
// Implementation for standalone image upload
}

@override
Future<UploadFileResponse> uploadFile(
AttachmentFile file, {
ProgressCallback? onSendProgress,
CancelToken? cancelToken,
}) async {
// Implementation for standalone file upload
}

@override
Future<EmptyResponse> removeImage(
String url, {
CancelToken? cancelToken,
}) async {
// Implementation for standalone image removal
}

@override
Future<EmptyResponse> removeFile(
String url, {
CancelToken? cancelToken,
}) async {
// Implementation for standalone file removal
}
}
```

> ⚠️ **Important:**
> - Custom `AttachmentFileUploader` implementations must now implement four additional methods
> - The new methods support standalone uploads/removals without requiring channel context
> - `UploadImageResponse` and `UploadFileResponse` are aliases for `SendAttachmentResponse`

---

### 🛠 MessageState

#### Key Changes:
Expand Down Expand Up @@ -505,6 +611,7 @@ StreamMessageWidget(
## 🎉 You're Ready to Migrate!

### For v10.0.0-beta.7:
- ✅ Update custom `AttachmentFileUploader` implementations to include the four new abstract methods: `uploadImage`, `uploadFile`, `removeImage`, and `removeFile`
- ✅ Update `MessageState` factory constructors to use `MessageDeleteScope` parameter
- ✅ Update pattern matching callbacks to handle `MessageDeleteScope` instead of `bool hard`
- ✅ Leverage new delete-for-me functionality with `deleteMessageForMe` methods
Expand Down
12 changes: 10 additions & 2 deletions packages/stream_chat/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@
- **Changed `MessageState` factory constructors**: The `deleting`, `deleted`, and `deletingFailed`
factory constructors now accept a `MessageDeleteScope` parameter instead of `bool hard`.
Pattern matching callbacks also receive `MessageDeleteScope scope` instead of `bool hard`.
- **Added new abstract methods to `AttachmentFileUploader`**: The `AttachmentFileUploader` interface
now includes four new abstract methods (`uploadImage`, `uploadFile`, `removeImage`, `removeFile`).
Custom implementations must implement these methods.

For more details, please refer to the [migration guide](../../migrations/v10-migration.md).

✅ Added

Expand All @@ -14,8 +19,11 @@
- `MessageDeleteScope` - New sealed class to represent deletion scope
- `MessageState.deletingForMe`, `MessageState.deletedForMe`, `MessageState.deletingForMeFailed` states
- `Message.deletedOnlyForMe`, `Event.deletedForMe`, `Member.deletedMessages` model fields

For more details, please refer to the [migration guide](../../migrations/v10-migration.md).
- Added standalone file and image upload/removal methods for CDN operations:
- `StreamChatClient.uploadImage()` - Upload an image to the Stream CDN
- `StreamChatClient.uploadFile()` - Upload a file to the Stream CDN
- `StreamChatClient.removeImage()` - Remove an image from the Stream CDN
- `StreamChatClient.removeFile()` - Remove a file from the Stream CDN

## 10.0.0-beta.6

Expand Down
62 changes: 62 additions & 0 deletions packages/stream_chat/lib/src/client/client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -921,6 +921,68 @@ class StreamChatClient {
extraData: extraData,
);

/// Upload an image to the Stream CDN
///
/// Upload progress can be tracked using [onProgress], and the operation can
/// be cancelled using [cancelToken].
///
/// Returns a [UploadImageResponse] once uploaded successfully.
Future<UploadImageResponse> uploadImage(
AttachmentFile image, {
ProgressCallback? onUploadProgress,
CancelToken? cancelToken,
}) =>
_chatApi.fileUploader.uploadImage(
image,
onSendProgress: onUploadProgress,
cancelToken: cancelToken,
);

/// Upload a file to the Stream CDN
///
/// Upload progress can be tracked using [onProgress], and the operation can
/// be cancelled using [cancelToken].
///
/// Returns a [UploadFileResponse] once uploaded successfully.
Future<UploadFileResponse> uploadFile(
AttachmentFile file, {
ProgressCallback? onUploadProgress,
CancelToken? cancelToken,
}) =>
_chatApi.fileUploader.uploadFile(
file,
onSendProgress: onUploadProgress,
cancelToken: cancelToken,
);

/// Remove an image from the Stream CDN using its [url].
///
/// The operation can be cancelled using [cancelToken] if needed.
///
/// Returns an [EmptyResponse] once removed successfully.
Future<EmptyResponse> removeImage(
String url, {
CancelToken? cancelToken,
}) =>
_chatApi.fileUploader.removeImage(
url,
cancelToken: cancelToken,
);

/// Remove a file from the Stream CDN using its [url].
///
/// The operation can be cancelled using [cancelToken] if needed.
///
/// Returns an [EmptyResponse] once removed successfully.
Future<EmptyResponse> removeFile(
String url, {
CancelToken? cancelToken,
}) =>
_chatApi.fileUploader.removeFile(
url,
cancelToken: cancelToken,
);

/// Replaces the [channelId] of type [ChannelType] data with [data].
///
/// Use [updateChannelPartial] for a partial update.
Expand Down
106 changes: 106 additions & 0 deletions packages/stream_chat/lib/src/core/api/attachment_file_uploader.dart
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,54 @@ abstract class AttachmentFileUploader {
CancelToken? cancelToken,
Map<String, Object?>? extraData,
});

// region Standalone upload methods

/// Uploads an image file to the CDN.
///
/// Upload progress can be tracked using [onProgress], and the operation can
/// be cancelled using [cancelToken].
///
/// Returns a [UploadImageResponse] once uploaded successfully.
Future<UploadImageResponse> uploadImage(
AttachmentFile image, {
ProgressCallback? onSendProgress,
CancelToken? cancelToken,
});

/// Uploads a file to the CDN.
///
/// Upload progress can be tracked using [onProgress], and the operation can
/// be cancelled using [cancelToken].
///
/// Returns a [UploadFileResponse] once uploaded successfully.
Future<UploadFileResponse> uploadFile(
AttachmentFile file, {
ProgressCallback? onSendProgress,
CancelToken? cancelToken,
});

/// Removes an image from the CDN using its [url].
///
/// The operation can be cancelled using [cancelToken] if needed.
///
/// Returns a [EmptyResponse] once removed successfully.
Future<EmptyResponse> removeImage(
String url, {
CancelToken? cancelToken,
});

/// Removes a file from the CDN using its [url].
///
/// The operation can be cancelled using [cancelToken] if needed.
///
/// Returns a [EmptyResponse] once removed successfully.
Future<EmptyResponse> removeFile(
String url, {
CancelToken? cancelToken,
});

// endregion
}

/// Stream's default implementation of [AttachmentFileUploader]
Expand Down Expand Up @@ -139,4 +187,62 @@ class StreamAttachmentFileUploader implements AttachmentFileUploader {
);
return EmptyResponse.fromJson(response.data);
}

@override
Future<UploadImageResponse> uploadImage(
AttachmentFile image, {
ProgressCallback? onSendProgress,
CancelToken? cancelToken,
}) async {
final multiPartFile = await image.toMultipartFile();
final response = await _client.postFile(
'/uploads/image',
multiPartFile,
onSendProgress: onSendProgress,
cancelToken: cancelToken,
);
return UploadImageResponse.fromJson(response.data);
}

@override
Future<UploadFileResponse> uploadFile(
AttachmentFile file, {
ProgressCallback? onSendProgress,
CancelToken? cancelToken,
}) async {
final multiPartFile = await file.toMultipartFile();
final response = await _client.postFile(
'/uploads/file',
multiPartFile,
onSendProgress: onSendProgress,
cancelToken: cancelToken,
);
return UploadFileResponse.fromJson(response.data);
}

@override
Future<EmptyResponse> removeImage(
String url, {
CancelToken? cancelToken,
}) async {
final response = await _client.delete(
'/uploads/image',
queryParameters: {'url': url},
cancelToken: cancelToken,
);
return EmptyResponse.fromJson(response.data);
}

@override
Future<EmptyResponse> removeFile(
String url, {
CancelToken? cancelToken,
}) async {
final response = await _client.delete(
'/uploads/file',
queryParameters: {'url': url},
cancelToken: cancelToken,
);
return EmptyResponse.fromJson(response.data);
}
}
6 changes: 6 additions & 0 deletions packages/stream_chat/lib/src/core/api/responses.dart
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,12 @@ class SendFileResponse extends SendAttachmentResponse {
/// Model response for [Channel.sendImage] api call
typedef SendImageResponse = SendAttachmentResponse;

/// Model response for [StreamChatClient.uploadImage] api call
typedef UploadImageResponse = SendAttachmentResponse;

/// Model response for [StreamChatClient.uploadFile] api call
typedef UploadFileResponse = SendAttachmentResponse;

/// Model response for [Channel.sendReaction] api call
@JsonSerializable(createToJson: false)
class SendReactionResponse extends MessageResponse {
Expand Down
Loading