Repository: GetStream/stream-chat-flutter

Issue:
Deleting image message does not call delete the image from backend
### Which packages are you using?

stream_chat_flutter

### On what platforms did you experience the issue?

Android

### What version are you using?

7.0.1

### What happened?

We implemented our own `AttachmentFileUploader`. But we find it strange that after deleting image/file message from chat it does not call `deleteImage`/`deleteFile` methods on AttachmentFileUploader.

### Steps to reproduce

```bash
1. Upload image.
2. Delete image.
3. Observe deleteImage function not called.
```


### Supporting info to reproduce

_No response_

### Relevant log output

_No response_

### Flutter analyze output

_No response_

### Flutter doctor output

_No response_

### Code of Conduct

- [X] I agree to follow this project's Code of Conduct



Fix patch:
diff --git a/packages/stream_chat/CHANGELOG.md b/packages/stream_chat/CHANGELOG.md
index 00c1b1b68c..07314700e6 100644
--- a/packages/stream_chat/CHANGELOG.md
+++ b/packages/stream_chat/CHANGELOG.md
@@ -1,3 +1,10 @@
+## Unreleased
+
+🐞 Fixed
+
+- [[#1837]](https://github.com/GetStream/stream-chat-flutter/issues/1837) Delete image and file 
+  attachments from the CDN, when the message get's hard deleted.
+
 ## 7.0.1
 
 🐞 Fixed
diff --git a/packages/stream_chat/lib/src/client/channel.dart b/packages/stream_chat/lib/src/client/channel.dart
index 7ffd8a8af0..516cbb9852 100644
--- a/packages/stream_chat/lib/src/client/channel.dart
+++ b/packages/stream_chat/lib/src/client/channel.dart
@@ -862,6 +862,18 @@ class Channel {
 
       state?.deleteMessage(deletedMessage, hardDelete: hard);
 
+      if (hard) {
+        deletedMessage.attachments.forEach((attachment) {
+          if (attachment.uploadState.isSuccess) {
+            if (attachment.type == AttachmentType.image) {
+              deleteImage(attachment.imageUrl!);
+            } else if (attachment.type == AttachmentType.file) {
+              deleteFile(attachment.assetUrl!);
+            }
+          }
+        });
+      }
+
       return response;
     } catch (e) {
       if (e is StreamChatNetworkError && e.isRetriable) {


Test patch:
diff --git a/packages/stream_chat/test/src/client/channel_test.dart b/packages/stream_chat/test/src/client/channel_test.dart
index 17cb7679af..9db275f4e2 100644
--- a/packages/stream_chat/test/src/client/channel_test.dart
+++ b/packages/stream_chat/test/src/client/channel_test.dart
@@ -772,6 +772,47 @@ void main() {
         verify(() => client.deleteMessage(messageId)).called(1);
       });
 
+      test('should delete attachments for hard delete', () async {
+        final attachments = List.generate(
+          3,
+          (index) => Attachment(
+            id: 'test-attachment-id-$index',
+            type: index.isEven ? 'image' : 'file',
+            file: AttachmentFile(size: index * 33, path: 'test-file-path'),
+            imageUrl: index.isEven ? 'test-image-url-$index' : null,
+            assetUrl: index.isOdd ? 'test-asset-url-$index' : null,
+            uploadState: const UploadState.success(),
+          ),
+        );
+        const messageId = 'test-message-id';
+        final message = Message(
+          attachments: attachments,
+          id: messageId,
+          createdAt: DateTime.now(),
+          state: MessageState.sent,
+        );
+
+        when(() => client.deleteMessage(messageId, hard: true))
+            .thenAnswer((_) async => EmptyResponse());
+
+        when(() => client.deleteImage(any(), channelId, channelType))
+            .thenAnswer((_) async => EmptyResponse());
+
+        when(() => client.deleteFile(any(), channelId, channelType))
+            .thenAnswer((_) async => EmptyResponse());
+
+        final res = await channel.deleteMessage(message, hard: true);
+
+        expect(res, isNotNull);
+
+        verify(() => client.deleteMessage(messageId, hard: true)).called(1);
+        verify(() => client.deleteImage(
+              any(),
+              channelId,
+              channelType,
+            )).called(2);
+      });
+
       test(
         '''should directly update the state with message as deleted if the state is sending or failed''',
         () async {