diff --git a/.github/actions/setup-ios-runtime/action.yml b/.github/actions/setup-ios-runtime/action.yml
index 89164b238..d54e78feb 100644
--- a/.github/actions/setup-ios-runtime/action.yml
+++ b/.github/actions/setup-ios-runtime/action.yml
@@ -7,7 +7,6 @@ runs:
       shell: bash
       run: |
         sudo rm -rfv ~/Library/Developer/CoreSimulator/* || true
-        brew install blacktop/tap/ipsw
         bundle exec fastlane install_runtime ios:${{ inputs.version }}
         sudo rm -rfv *.dmg || true
         xcrun simctl list runtimes
diff --git a/.github/workflows/cron-checks.yml b/.github/workflows/cron-checks.yml
index fbc6cbc13..e08f24b1f 100644
--- a/.github/workflows/cron-checks.yml
+++ b/.github/workflows/cron-checks.yml
@@ -72,6 +72,7 @@ jobs:
       env:
         INSTALL_ALLURE: true
         INSTALL_YEETD: true
+        INSTALL_IPSW: true
         SKIP_MINT_BOOTSTRAP: true
     - uses: ./.github/actions/setup-ios-runtime
       if: ${{ matrix.setup_runtime }}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ab8faa467..86f0f2b93 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,22 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 
 ### ๐Ÿ”„ Changed
 
+# [4.78.0](https://github.com/GetStream/stream-chat-swiftui/releases/tag/4.78.0)
+_April 24, 2025_
+
+### โœ… Added
+- Add factory methods for gallery and video player view [#808](https://github.com/GetStream/stream-chat-swiftui/pull/808)
+- Add support for editing message attachments [#806](https://github.com/GetStream/stream-chat-swiftui/pull/806)
+### ๐Ÿž Fixed
+- Fix scrolling to the bottom when editing a message [#806](https://github.com/GetStream/stream-chat-swiftui/pull/806)
+- Fix having message edit action on Giphy messages [#806](https://github.com/GetStream/stream-chat-swiftui/pull/806)
+- Fix being able to long press an unsent Giphy message [#806](https://github.com/GetStream/stream-chat-swiftui/pull/806)
+- Fix being able to swipe to reply an unsent Giphy message [#806](https://github.com/GetStream/stream-chat-swiftui/pull/806)
+- Fix translated message showing original text in message actions overlay [#810](https://github.com/GetStream/stream-chat-swiftui/pull/810)
+
+### ๐Ÿ”„ Changed
+- Deprecated `ComposerConfig.attachmentPayloadConverter` in favour of `MessageComposerViewModel.convertAddedAssetsToPayloads()` [#806](https://github.com/GetStream/stream-chat-swiftui/pull/806)
+
 # [4.77.0](https://github.com/GetStream/stream-chat-swiftui/releases/tag/4.77.0)
 _April 10, 2025_
 
diff --git a/Gemfile.lock b/Gemfile.lock
index da771d1cd..2a789d4ae 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -289,7 +289,7 @@ GEM
     netrc (0.11.0)
     nio4r (2.7.3)
     nkf (0.2.0)
-    nokogiri (1.18.4)
+    nokogiri (1.18.8)
       mini_portile2 (~> 2.8.2)
       racc (~> 1.4)
     octokit (9.1.0)
diff --git a/Githubfile b/Githubfile
index 6419bbeff..384620cc5 100644
--- a/Githubfile
+++ b/Githubfile
@@ -5,3 +5,4 @@ export XCRESULTS_VERSION='1.19.1'
 export YEETD_VERSION='1.0'
 export MINT_VERSION='0.17.5'
 export SONAR_VERSION='6.2.1.4610'
+export IPSW_VERSION='3.1.592'
diff --git a/Package.swift b/Package.swift
index bdcc59f11..85a2bacc4 100644
--- a/Package.swift
+++ b/Package.swift
@@ -16,7 +16,7 @@ let package = Package(
         )
     ],
     dependencies: [
-        .package(url: "https://github.com/GetStream/stream-chat-swift.git", from: "4.77.0"),
+        .package(url: "https://github.com/GetStream/stream-chat-swift.git", from: "4.78.0"),
     ],
     targets: [
         .target(
diff --git a/README.md b/README.md
index 9ed8ab2b7..856c56be3 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@
 <p align="center">
   <a href="https://sonarcloud.io/summary/new_code?id=GetStream_stream-chat-swiftui"><img src="https://sonarcloud.io/api/project_badges/measure?project=GetStream_stream-chat-swiftui&metric=coverage" /></a>
 
-  <img id="stream-chat-swiftui-label" alt="StreamChatSwiftUI" src="https://img.shields.io/badge/StreamChatSwiftUI-8.21%20MB-blue"/>
+  <img id="stream-chat-swiftui-label" alt="StreamChatSwiftUI" src="https://img.shields.io/badge/StreamChatSwiftUI-8.29%20MB-blue"/>
 </p>
 
 ## SwiftUI StreamChat SDK
diff --git a/Scripts/bootstrap.sh b/Scripts/bootstrap.sh
index 6db3e6b15..c6afaaa54 100755
--- a/Scripts/bootstrap.sh
+++ b/Scripts/bootstrap.sh
@@ -74,3 +74,12 @@ if [[ ${INSTALL_YEETD-default} == true ]]; then
   puts "Running yeetd daemon"
   yeetd &
 fi
+
+if [[ ${INSTALL_IPSW-default} == true ]]; then
+  puts "Install ipsw v${IPSW_VERSION}"
+  FILE="ipsw_${IPSW_VERSION}_macOS_universal.tar.gz"
+  wget "https://github.com/blacktop/ipsw/releases/download/v${IPSW_VERSION}/${FILE}"
+  tar -xzf "$FILE"
+  chmod +x ipsw
+  sudo mv ipsw /usr/local/bin/
+fi
diff --git a/Sources/StreamChatSwiftUI/ChatChannel/ChannelInfo/MediaAttachmentsView.swift b/Sources/StreamChatSwiftUI/ChatChannel/ChannelInfo/MediaAttachmentsView.swift
index 177076522..9cc09d28e 100644
--- a/Sources/StreamChatSwiftUI/ChatChannel/ChannelInfo/MediaAttachmentsView.swift
+++ b/Sources/StreamChatSwiftUI/ChatChannel/ChannelInfo/MediaAttachmentsView.swift
@@ -58,6 +58,7 @@ public struct MediaAttachmentsView<Factory: ViewFactory>: View {
                                 if !mediaItem.isVideo, let imageAttachment = mediaItem.imageAttachment {
                                     let index = viewModel.allImageAttachments.firstIndex { $0.id == imageAttachment.id } ?? 0
                                     ImageAttachmentContentView(
+                                        factory: factory,
                                         mediaItem: mediaItem,
                                         imageAttachment: imageAttachment,
                                         allImageAttachments: viewModel.allImageAttachments,
@@ -66,8 +67,9 @@ public struct MediaAttachmentsView<Factory: ViewFactory>: View {
                                     )
                                 } else if let videoAttachment = mediaItem.videoAttachment {
                                     VideoAttachmentContentView(
+                                        factory: factory,
                                         attachment: videoAttachment,
-                                        author: mediaItem.author,
+                                        message: mediaItem.message,
                                         width: Self.itemWidth,
                                         ratio: 1,
                                         cornerRadius: 0
@@ -78,9 +80,9 @@ public struct MediaAttachmentsView<Factory: ViewFactory>: View {
                                 BottomRightView {
                                     factory.makeMessageAvatarView(
                                         for: UserDisplayInfo(
-                                            id: mediaItem.author.id,
-                                            name: mediaItem.author.name ?? "",
-                                            imageURL: mediaItem.author.imageURL,
+                                            id: mediaItem.message.author.id,
+                                            name: mediaItem.message.author.name ?? "",
+                                            imageURL: mediaItem.message.author.imageURL,
                                             size: .init(width: 24, height: 24)
                                         )
                                     )
@@ -108,10 +110,11 @@ public struct MediaAttachmentsView<Factory: ViewFactory>: View {
     }
 }
 
-struct ImageAttachmentContentView: View {
+struct ImageAttachmentContentView<Factory: ViewFactory>: View {
 
     @State private var galleryShown = false
 
+    let factory: Factory
     let mediaItem: MediaItem
     let imageAttachment: ChatMessageImageAttachment
     let allImageAttachments: [ChatMessageImageAttachment]
@@ -134,11 +137,11 @@ struct ImageAttachmentContentView: View {
             .clipped()
         }
         .fullScreenCover(isPresented: $galleryShown) {
-            GalleryView(
-                imageAttachments: allImageAttachments,
-                author: mediaItem.author,
+            factory.makeGalleryView(
+                mediaAttachments: allImageAttachments.map { MediaAttachment(from: $0) },
+                message: mediaItem.message,
                 isShown: $galleryShown,
-                selected: index
+                options: .init(selectedIndex: index)
             )
         }
     }
diff --git a/Sources/StreamChatSwiftUI/ChatChannel/ChannelInfo/MediaAttachmentsViewModel.swift b/Sources/StreamChatSwiftUI/ChatChannel/ChannelInfo/MediaAttachmentsViewModel.swift
index bca7fb379..2ce591373 100644
--- a/Sources/StreamChatSwiftUI/ChatChannel/ChannelInfo/MediaAttachmentsViewModel.swift
+++ b/Sources/StreamChatSwiftUI/ChatChannel/ChannelInfo/MediaAttachmentsViewModel.swift
@@ -84,7 +84,7 @@ class MediaAttachmentsViewModel: ObservableObject, ChatMessageSearchControllerDe
                 let mediaItem = MediaItem(
                     id: imageAttachment.id.rawValue,
                     isVideo: false,
-                    author: message.author,
+                    message: message,
                     videoAttachment: nil,
                     imageAttachment: imageAttachment
                 )
@@ -94,7 +94,7 @@ class MediaAttachmentsViewModel: ObservableObject, ChatMessageSearchControllerDe
                 let mediaItem = MediaItem(
                     id: videoAttachment.id.rawValue,
                     isVideo: true,
-                    author: message.author,
+                    message: message,
                     videoAttachment: videoAttachment,
                     imageAttachment: nil
                 )
@@ -110,7 +110,7 @@ class MediaAttachmentsViewModel: ObservableObject, ChatMessageSearchControllerDe
 struct MediaItem: Identifiable {
     let id: String
     let isVideo: Bool
-    let author: ChatUser
+    let message: ChatMessage
 
     var videoAttachment: ChatMessageVideoAttachment?
     var imageAttachment: ChatMessageImageAttachment?
diff --git a/Sources/StreamChatSwiftUI/ChatChannel/ChatChannelView.swift b/Sources/StreamChatSwiftUI/ChatChannel/ChatChannelView.swift
index a0e866366..4269a3385 100644
--- a/Sources/StreamChatSwiftUI/ChatChannel/ChatChannelView.swift
+++ b/Sources/StreamChatSwiftUI/ChatChannel/ChatChannelView.swift
@@ -115,7 +115,9 @@ public struct ChatChannelView<Factory: ViewFactory>: View, KeyboardReadable {
                         messageController: viewModel.messageController,
                         quotedMessage: $viewModel.quotedMessage,
                         editedMessage: $viewModel.editedMessage,
-                        onMessageSent: viewModel.scrollToLastMessage
+                        onMessageSent: {
+                            viewModel.messageSentTapped()
+                        }
                     )
                     .opacity((
                         utils.messageListConfig.messagePopoverEnabled && messageDisplayInfo != nil && !viewModel
diff --git a/Sources/StreamChatSwiftUI/ChatChannel/ChatChannelViewModel.swift b/Sources/StreamChatSwiftUI/ChatChannel/ChatChannelViewModel.swift
index f9376c7b6..b6e5c90bc 100644
--- a/Sources/StreamChatSwiftUI/ChatChannel/ChatChannelViewModel.swift
+++ b/Sources/StreamChatSwiftUI/ChatChannel/ChatChannelViewModel.swift
@@ -264,7 +264,15 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
             }
         }
     }
-        
+
+    /// The user tapped on the message sent button.
+    public func messageSentTapped() {
+        // only scroll if the message is not being edited
+        if editedMessage == nil {
+            scrollToLastMessage()
+        }
+    }
+
     public func jumpToMessage(messageId: String) -> Bool {
         if messageId == .unknownMessageId {
             if firstUnreadMessageId == nil, let lastReadMessageId {
diff --git a/Sources/StreamChatSwiftUI/ChatChannel/Composer/ComposerConfig.swift b/Sources/StreamChatSwiftUI/ChatChannel/Composer/ComposerConfig.swift
index 3c3225cf5..3fa0b7fef 100644
--- a/Sources/StreamChatSwiftUI/ChatChannel/Composer/ComposerConfig.swift
+++ b/Sources/StreamChatSwiftUI/ChatChannel/Composer/ComposerConfig.swift
@@ -17,6 +17,14 @@ public struct ComposerConfig {
     public var inputPaddingsConfig: PaddingsConfig
     public var adjustMessageOnSend: (String) -> (String)
     public var adjustMessageOnRead: (String) -> (String)
+
+    @available(
+        *,
+        deprecated,
+        message: """
+        Override the MessageComposerViewModel.inputAttachmentsAsPayloads() in order to convert the message attachments to payloads.
+        """
+    )
     public var attachmentPayloadConverter: (ChatMessage) -> [AnyAttachmentPayload]
 
     public init(
@@ -44,8 +52,9 @@ public struct ComposerConfig {
         self.isVoiceRecordingEnabled = isVoiceRecordingEnabled
     }
     
-    public static var defaultAttachmentPayloadConverter: (ChatMessage) -> [AnyAttachmentPayload] = { message in
-        message.allAttachments.toAnyAttachmentPayload()
+    public static var defaultAttachmentPayloadConverter: (ChatMessage) -> [AnyAttachmentPayload] = { _ in
+        /// This now returns empty array by default since attachmentPayloadConverter has been deprecated.
+        []
     }
 }
 
diff --git a/Sources/StreamChatSwiftUI/ChatChannel/Composer/ComposerModels.swift b/Sources/StreamChatSwiftUI/ChatChannel/Composer/ComposerModels.swift
index be0dee00a..c8b50be37 100644
--- a/Sources/StreamChatSwiftUI/ChatChannel/Composer/ComposerModels.swift
+++ b/Sources/StreamChatSwiftUI/ChatChannel/Composer/ComposerModels.swift
@@ -27,25 +27,34 @@ public struct AddedAsset: Identifiable, Equatable {
     public let url: URL
     public let type: AssetType
     public var extraData: [String: RawJSON] = [:]
-    
+
+    /// The payload of the attachment, in case the attachment has been uploaded to server already.
+    /// This is mostly used when editing an existing message that contains attachments.
+    public var payload: AttachmentPayload?
+
     public init(
         image: UIImage,
         id: String,
         url: URL,
         type: AssetType,
-        extraData: [String: RawJSON] = [:]
+        extraData: [String: RawJSON] = [:],
+        payload: AttachmentPayload? = nil
     ) {
         self.image = image
         self.id = id
         self.url = url
         self.type = type
         self.extraData = extraData
+        self.payload = payload
     }
 }
 
 extension AddedAsset {
     func toAttachmentPayload() throws -> AnyAttachmentPayload {
-        try AnyAttachmentPayload(
+        if let payload = self.payload {
+            return AnyAttachmentPayload(payload: payload)
+        }
+        return try AnyAttachmentPayload(
             localFileURL: url,
             attachmentType: type == .video ? .video : .image,
             extraData: extraData
@@ -63,7 +72,8 @@ extension AnyChatMessageAttachment {
                 id: imageAttachment.id.rawValue,
                 url: imageAttachment.imageURL,
                 type: .image,
-                extraData: imageAttachment.extraData ?? [:]
+                extraData: imageAttachment.extraData ?? [:],
+                payload: imageAttachment.payload
             )
         } else if let videoAttachment = attachment(payloadType: VideoAttachmentPayload.self),
                   let thumbnail = imageThumbnail(for: videoAttachment.payload) {
@@ -72,7 +82,8 @@ extension AnyChatMessageAttachment {
                 id: videoAttachment.id.rawValue,
                 url: videoAttachment.videoURL,
                 type: .video,
-                extraData: videoAttachment.extraData ?? [:]
+                extraData: videoAttachment.extraData ?? [:],
+                payload: videoAttachment.payload
             )
         }
         return nil
diff --git a/Sources/StreamChatSwiftUI/ChatChannel/Composer/MessageComposerView.swift b/Sources/StreamChatSwiftUI/ChatChannel/Composer/MessageComposerView.swift
index 591a6847a..1e921a6cb 100644
--- a/Sources/StreamChatSwiftUI/ChatChannel/Composer/MessageComposerView.swift
+++ b/Sources/StreamChatSwiftUI/ChatChannel/Composer/MessageComposerView.swift
@@ -102,9 +102,11 @@ public struct MessageComposerView<Factory: ViewFactory>: View, KeyboardReadable
                         quotedMessage: quotedMessage,
                         editedMessage: editedMessage
                     ) {
+                        // Calling onMessageSent() before erasing the edited and quoted message
+                        // so that onMessageSent can use them for state handling.
+                        onMessageSent()
                         quotedMessage = nil
                         editedMessage = nil
-                        onMessageSent()
                     }
                 }
                 .environmentObject(viewModel)
@@ -208,11 +210,10 @@ public struct MessageComposerView<Factory: ViewFactory>: View, KeyboardReadable
         )
         .modifier(factory.makeComposerViewModifier())
         .onChange(of: editedMessage) { _ in
-            viewModel.text = editedMessage?.text ?? ""
+            viewModel.fillEditedMessage(editedMessage)
             if editedMessage != nil {
                 becomeFirstResponder()
                 editedMessageWillShow = true
-                viewModel.selectedRangeLocation = editedMessage?.text.count ?? 0
             }
         }
         .onAppear(perform: {
diff --git a/Sources/StreamChatSwiftUI/ChatChannel/Composer/MessageComposerViewModel.swift b/Sources/StreamChatSwiftUI/ChatChannel/Composer/MessageComposerViewModel.swift
index b6ad2af13..ce5416521 100644
--- a/Sources/StreamChatSwiftUI/ChatChannel/Composer/MessageComposerViewModel.swift
+++ b/Sources/StreamChatSwiftUI/ChatChannel/Composer/MessageComposerViewModel.swift
@@ -11,7 +11,17 @@ import SwiftUI
 open class MessageComposerViewModel: ObservableObject {
     @Injected(\.chatClient) private var chatClient
     @Injected(\.utils) internal var utils
-    
+
+    var attachmentsConverter = MessageAttachmentsConverter()
+    var composerAssets: ComposerAssets {
+        ComposerAssets(
+            mediaAssets: addedAssets,
+            fileAssets: addedFileURLs.map { FileAddedAsset(url: $0, payload: addedRemoteFileURLs[$0]) },
+            voiceAssets: addedVoiceRecordings,
+            customAssets: addedCustomAttachments
+        )
+    }
+
     @Published public var pickerState: AttachmentPickerState = .photos {
         didSet {
             if pickerState == .camera {
@@ -67,7 +77,9 @@ open class MessageComposerViewModel: ObservableObject {
     }
 
     @Published public var selectedRangeLocation: Int = 0
-    
+
+    /// An helper property to store additional information of file attachments.
+    private var addedRemoteFileURLs: [URL: FileAttachmentPayload] = [:]
     @Published public var addedFileURLs = [URL]() {
         didSet {
             if totalAttachmentsCount > chatClient.config.maxAttachmentCountPerMessage
@@ -262,48 +274,23 @@ open class MessageComposerViewModel: ObservableObject {
         )
     }
 
-    /// Populates the draft message in the composer with the current controller's draft information.
-    public func fillDraftMessage() {
-        guard let message = draftMessage else {
+    /// Populates the composer with the edited message.
+    public func fillEditedMessage(_ editedMessage: ChatMessage?) {
+        guard let message = editedMessage else {
+            clearInputData()
             return
         }
 
-        text = message.text
-        mentionedUsers = message.mentionedUsers
-        quotedMessage?.wrappedValue = message.quotedMessage
-        showReplyInChannel = message.showReplyInChannel
-
-        var addedAssets: [AddedAsset] = []
-        var addedFileURLs: [URL] = []
-        var addedVoiceRecordings: [AddedVoiceRecording] = []
-        var addedCustomAttachments: [CustomAttachment] = []
+        fillComposer(with: message)
+    }
 
-        message.attachments.forEach { attachment in
-            switch attachment.type {
-            case .image, .video:
-                guard let addedAsset = attachment.toAddedAsset() else { break }
-                addedAssets.append(addedAsset)
-            case .file:
-                guard let url = attachment.attachment(payloadType: FileAttachmentPayload.self)?.assetURL else {
-                    break
-                }
-                addedFileURLs.append(url)
-            case .voiceRecording:
-                guard let addedVoiceRecording = attachment.toAddedVoiceRecording() else { break }
-                addedVoiceRecordings.append(addedVoiceRecording)
-            case .linkPreview, .audio, .giphy, .unknown:
-                break
-            default:
-                guard let anyAttachmentPayload = [attachment].toAnyAttachmentPayload().first else { break }
-                let customAttachment = CustomAttachment(id: attachment.id.rawValue, content: anyAttachmentPayload)
-                addedCustomAttachments.append(customAttachment)
-            }
+    /// Populates the draft message in the composer with the current controller's draft information.
+    public func fillDraftMessage() {
+        guard let draft = draftMessage else {
+            return
         }
 
-        self.addedAssets = addedAssets
-        self.addedFileURLs = addedFileURLs
-        self.addedVoiceRecordings = addedVoiceRecordings
-        self.addedCustomAttachments = addedCustomAttachments
+        fillComposer(with: ChatMessage(draft))
     }
 
     /// Updates the draft message locally and on the server.
@@ -315,7 +302,7 @@ open class MessageComposerViewModel: ObservableObject {
         guard utils.messageListConfig.draftMessagesEnabled && sendButtonEnabled else {
             return
         }
-        let attachments = try? inputAttachmentsAsPayloads()
+        let attachments = try? convertAddedAssetsToPayloads()
         let mentionedUserIds = mentionedUsers.map(\.id)
         let availableCommands = channelController.channel?.config.commands ?? []
         let command = availableCommands.first { composerCommand?.id == "/\($0.name)" }
@@ -358,11 +345,6 @@ open class MessageComposerViewModel: ObservableObject {
         }
     }
 
-    /// Checks if the previous value of the content in the composer was not empty and the current value is empty.
-    private func shouldDeleteDraftMessage(oldValue: any Collection) -> Bool {
-        !oldValue.isEmpty && !sendButtonEnabled
-    }
-
     open func sendMessage(
         quotedMessage: ChatMessage?,
         editedMessage: ChatMessage?,
@@ -375,7 +357,7 @@ open class MessageComposerViewModel: ObservableObject {
         defer {
             checkChannelCooldown()
         }
-        
+
         if let composerCommand = composerCommand, composerCommand.id != "instantCommands" {
             commandsHandler.executeOnMessageSent(
                 composerCommand: composerCommand
@@ -393,12 +375,16 @@ open class MessageComposerViewModel: ObservableObject {
         let mentionedUserIds = mentionedUsers.map(\.id)
         
         if let editedMessage = editedMessage {
-            edit(message: editedMessage, completion: completion)
+            edit(
+                message: editedMessage,
+                attachments: try? convertAddedAssetsToPayloads(),
+                completion: completion
+            )
             return
         }
         
         do {
-            let attachments = try inputAttachmentsAsPayloads()
+            let attachments = try convertAddedAssetsToPayloads()
             if let messageController = messageController {
                 messageController.createNewReply(
                     text: messageText,
@@ -641,9 +627,41 @@ open class MessageComposerViewModel: ObservableObject {
             extraData: extraData
         )
     }
-    
+
+    /// Converts all added assets to payloads.
+    open func convertAddedAssetsToPayloads() throws -> [AnyAttachmentPayload] {
+        try attachmentsConverter.assetsToPayloads(composerAssets)
+    }
+
     // MARK: - private
-    
+
+    private func fillComposer(with message: ChatMessage) {
+        text = message.text
+        mentionedUsers = message.mentionedUsers
+        quotedMessage?.wrappedValue = message.quotedMessage
+        showReplyInChannel = message.showReplyInChannel
+        selectedRangeLocation = message.text.count
+
+        attachmentsConverter.attachmentsToAssets(message.allAttachments) { [weak self] assets in
+            self?.updateComposerAssets(assets)
+        }
+    }
+
+    private func updateComposerAssets(_ assets: ComposerAssets) {
+        addedAssets = assets.mediaAssets
+        addedFileURLs = assets.fileAssets.map(\.url)
+        addedRemoteFileURLs = assets.fileAssets.reduce(into: [:]) { result, asset in
+            result[asset.url] = asset.payload
+        }
+        addedVoiceRecordings = assets.voiceAssets
+        addedCustomAttachments = assets.customAssets
+    }
+
+    /// Checks if the previous value of the content in the composer was not empty and the current value is empty.
+    private func shouldDeleteDraftMessage(oldValue: any Collection) -> Bool {
+        !oldValue.isEmpty && !sendButtonEnabled
+    }
+
     private func fetchAssets() {
         let fetchOptions = PHFetchOptions()
         let supportedTypes = utils.composerConfig.gallerySupportedTypes
@@ -663,30 +681,6 @@ open class MessageComposerViewModel: ObservableObject {
         }
     }
 
-    private func inputAttachmentsAsPayloads() throws -> [AnyAttachmentPayload] {
-        var attachments = try addedAssets.map { try $0.toAttachmentPayload() }
-        attachments += try addedFileURLs.map { url in
-            _ = url.startAccessingSecurityScopedResource()
-            return try AnyAttachmentPayload(localFileURL: url, attachmentType: .file)
-        }
-        attachments += try addedVoiceRecordings.map { recording in
-            _ = recording.url.startAccessingSecurityScopedResource()
-            var localMetadata = AnyAttachmentLocalMetadata()
-            localMetadata.duration = recording.duration
-            localMetadata.waveformData = recording.waveform
-            return try AnyAttachmentPayload(
-                localFileURL: recording.url,
-                attachmentType: .voiceRecording,
-                localMetadata: localMetadata
-            )
-        }
-
-        attachments += addedCustomAttachments.map { attachment in
-            attachment.content
-        }
-        return attachments
-    }
-
     private func checkForMentionedUsers(
         commandId: String?,
         extraData: [String: Any]
@@ -708,6 +702,7 @@ open class MessageComposerViewModel: ObservableObject {
     
     private func edit(
         message: ChatMessage,
+        attachments: [AnyAttachmentPayload]?,
         completion: @escaping () -> Void
     ) {
         guard let channelId = channelController.channel?.cid else {
@@ -717,10 +712,16 @@ open class MessageComposerViewModel: ObservableObject {
             cid: channelId,
             messageId: message.id
         )
-        
+
+        var newAttachments = attachments ?? []
+        let fallbackAttachments = utils.composerConfig.attachmentPayloadConverter(message)
+        if !fallbackAttachments.isEmpty {
+            newAttachments = fallbackAttachments
+        }
+
         messageController.editMessage(
             text: adjustedText,
-            attachments: utils.composerConfig.attachmentPayloadConverter(message)
+            attachments: newAttachments
         ) { [weak self] error in
             if error != nil {
                 self?.errorShown = true
@@ -827,7 +828,7 @@ open class MessageComposerViewModel: ObservableObject {
 
     /// Same as clearText() but it just clears the command id.
     private func clearCommandText() {
-        guard let command = composerCommand else { return }
+        guard composerCommand != nil else { return }
         let currentText = text
         if let value = getValueOfCommand(currentText) {
             text = value
@@ -871,7 +872,9 @@ open class MessageComposerViewModel: ObservableObject {
             attachmentSizeExceeded = !canAdd
             return canAdd
         } catch {
-            return false
+            // If for some reason we can't access the file size, we delegate
+            // the decision to the server.
+            return true
         }
     }
     
@@ -902,3 +905,116 @@ extension MessageComposerViewModel: EventsControllerDelegate {
         }
     }
 }
+
+// The assets added to the composer.
+struct ComposerAssets {
+    // Image and Video Assets.
+    var mediaAssets: [AddedAsset] = []
+    // File Assets.
+    var fileAssets: [FileAddedAsset] = []
+    // Voice Assets.
+    var voiceAssets: [AddedVoiceRecording] = []
+    // Custom Assets.
+    var customAssets: [CustomAttachment] = []
+}
+
+// A asset containing file information.
+// If it has a payload, it means that the file is already uploaded to the server.
+struct FileAddedAsset {
+    var url: URL
+    var payload: FileAttachmentPayload?
+}
+
+// The converter responsible to map attachments to assets and vice versa.
+class MessageAttachmentsConverter {
+    let queue = DispatchQueue(label: "MessageAttachmentsConverter")
+
+    /// Converts the added assets to payloads.
+    func assetsToPayloads(_ assets: ComposerAssets) throws -> [AnyAttachmentPayload] {
+        let mediaAssets = assets.mediaAssets
+        let fileAssets = assets.fileAssets
+        let voiceAssets = assets.voiceAssets
+        let customAssets = assets.customAssets
+
+        var attachments = try mediaAssets.map { try $0.toAttachmentPayload() }
+        attachments += try fileAssets.map { file in
+            _ = file.url.startAccessingSecurityScopedResource()
+            if let filePayload = file.payload {
+                return AnyAttachmentPayload(payload: filePayload)
+            }
+            return try AnyAttachmentPayload(localFileURL: file.url, attachmentType: .file)
+        }
+        attachments += try voiceAssets.map { recording in
+            _ = recording.url.startAccessingSecurityScopedResource()
+            var localMetadata = AnyAttachmentLocalMetadata()
+            localMetadata.duration = recording.duration
+            localMetadata.waveformData = recording.waveform
+            return try AnyAttachmentPayload(
+                localFileURL: recording.url,
+                attachmentType: .voiceRecording,
+                localMetadata: localMetadata
+            )
+        }
+
+        attachments += customAssets.map { attachment in
+            attachment.content
+        }
+        return attachments
+    }
+
+    /// Converts the attachments to assets.
+    ///
+    /// This operation is asynchronous to make sure loading expensive assets are not done in the main thread.
+    func attachmentsToAssets(
+        _ attachments: [AnyChatMessageAttachment],
+        completion: @escaping (ComposerAssets) -> Void
+    ) {
+        queue.async {
+            let addedAssets = self.attachmentsToAssets(attachments)
+            DispatchQueue.main.async {
+                completion(addedAssets)
+            }
+        }
+    }
+
+    /// Converts the attachments to assets synchronously.
+    ///
+    /// This operation is synchronous and should only be used if all attachments are already loaded.
+    /// Like for example, for draft messages.
+    func attachmentsToAssets(
+        _ attachments: [AnyChatMessageAttachment]
+    ) -> ComposerAssets {
+        var addedAssets = ComposerAssets()
+
+        attachments.forEach { attachment in
+            switch attachment.type {
+            case .image, .video:
+                guard let addedAsset = attachment.toAddedAsset() else { break }
+                addedAssets.mediaAssets.append(addedAsset)
+            case .file:
+                guard let filePayload = attachment.attachment(payloadType: FileAttachmentPayload.self) else {
+                    break
+                }
+                let fileAsset = FileAddedAsset(
+                    url: filePayload.assetURL,
+                    payload: filePayload.payload
+                )
+                addedAssets.fileAssets.append(fileAsset)
+            case .voiceRecording:
+                guard let addedVoiceRecording = attachment.toAddedVoiceRecording() else { break }
+                addedAssets.voiceAssets.append(addedVoiceRecording)
+            case .linkPreview, .audio, .giphy, .unknown:
+                break
+            default:
+                guard let anyAttachmentPayload = [attachment].toAnyAttachmentPayload().first else { break }
+                let customAttachment = CustomAttachment(
+                    id: attachment.id.rawValue,
+                    content: anyAttachmentPayload
+                )
+                addedAssets.customAssets.append(customAttachment)
+            }
+        }
+
+        return addedAssets
+    }
+}
diff --git a/Sources/StreamChatSwiftUI/ChatChannel/Gallery/GalleryView.swift b/Sources/StreamChatSwiftUI/ChatChannel/Gallery/GalleryView.swift
index 7ecd4762f..6681b401d 100644
--- a/Sources/StreamChatSwiftUI/ChatChannel/Gallery/GalleryView.swift
+++ b/Sources/StreamChatSwiftUI/ChatChannel/Gallery/GalleryView.swift
@@ -28,19 +28,7 @@ public struct GalleryView: View {
         isShown: Binding<Bool>,
         selected: Int
     ) {
-        let mediaAttachments = imageAttachments.map { attachment in
-            let url: URL
-            if let state = attachment.uploadingState {
-                url = state.localFileURL
-            } else {
-                url = attachment.imageURL
-            }
-            return MediaAttachment(
-                url: url,
-                type: .image,
-                uploadingState: attachment.uploadingState
-            )
-        }
+        let mediaAttachments = imageAttachments.map { MediaAttachment(from: $0) }
         self.init(
             mediaAttachments: mediaAttachments,
             author: author,
@@ -49,7 +37,7 @@ public struct GalleryView: View {
         )
     }
     
-    init(
+    public init(
         mediaAttachments: [MediaAttachment],
         author: ChatUser,
         isShown: Binding<Bool>,
diff --git a/Sources/StreamChatSwiftUI/ChatChannel/MessageList/ImageAttachmentView.swift b/Sources/StreamChatSwiftUI/ChatChannel/MessageList/ImageAttachmentView.swift
index 36d1b5c78..eacde8615 100644
--- a/Sources/StreamChatSwiftUI/ChatChannel/MessageList/ImageAttachmentView.swift
+++ b/Sources/StreamChatSwiftUI/ChatChannel/MessageList/ImageAttachmentView.swift
@@ -62,11 +62,11 @@ public struct ImageAttachmentContainer<Factory: ViewFactory>: View {
         .fullScreenCover(isPresented: $galleryShown, onDismiss: {
             self.selectedIndex = 0
         }) {
-            GalleryView(
+            factory.makeGalleryView(
                 mediaAttachments: sources,
-                author: message.author,
+                message: message,
                 isShown: $galleryShown,
-                selected: selectedIndex
+                options: .init(selectedIndex: selectedIndex)
             )
         }
         .accessibilityIdentifier("ImageAttachmentContainer")
@@ -431,7 +431,7 @@ extension ChatMessage {
     }
 }
 
-struct MediaAttachment {
+public struct MediaAttachment {
     @Injected(\.utils) var utils
     
     let url: URL
@@ -460,7 +460,29 @@ struct MediaAttachment {
     }
 }
 
+extension MediaAttachment {
+    init(from attachment: ChatMessageImageAttachment) {
+        let url: URL
+        if let state = attachment.uploadingState {
+            url = state.localFileURL
+        } else {
+            url = attachment.imageURL
+        }
+        self.init(
+            url: url,
+            type: .image,
+            uploadingState: attachment.uploadingState
+        )
+    }
+}
+
 enum MediaAttachmentType {
     case image
     case video
 }
+
+/// Options for the gallery view.
+public struct MediaViewsOptions {
+    /// The index of the selected media item.
+    public let selectedIndex: Int
+}
diff --git a/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageContainerView.swift b/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageContainerView.swift
index 21ae47c17..eeecbc7f7 100644
--- a/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageContainerView.swift
+++ b/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageContainerView.swift
@@ -35,6 +35,10 @@ public struct MessageContainerView<Factory: ViewFactory>: View {
     private let replyThreshold: CGFloat = 60
     private let paddingValue: CGFloat = 8
 
+    var isSwipeToReplyPossible: Bool {
+        message.isInteractionEnabled && channel.config.repliesEnabled
+    }
+
     public init(
         factory: Factory,
         channel: ChatChannel,
@@ -124,9 +128,7 @@ public struct MessageContainerView<Factory: ViewFactory>: View {
                         }
                     }
                     .onLongPressGesture(perform: {
-                        if !message.isDeleted {
-                            handleGestureForMessage(showsMessageActions: true)
-                        }
+                        handleGestureForMessage(showsMessageActions: true)
                     })
                     .offset(x: min(self.offsetX, maximumHorizontalSwipeDisplacement))
                     .simultaneousGesture(
@@ -135,7 +137,7 @@ public struct MessageContainerView<Factory: ViewFactory>: View {
                             coordinateSpace: .local
                         )
                         .updating($offset) { (value, gestureState, _) in
-                            if message.isDeleted || !channel.config.repliesEnabled {
+                            guard isSwipeToReplyPossible else {
                                 return
                             }
                             // Using updating since onEnded is not called if the gesture is canceled.
@@ -363,10 +365,14 @@ public struct MessageContainerView<Factory: ViewFactory>: View {
         }
     }
 
-    private func handleGestureForMessage(
+    func handleGestureForMessage(
         showsMessageActions: Bool,
         showsBottomContainer: Bool = true
     ) {
+        guard message.isInteractionEnabled else {
+            return
+        }
+
         computeFrame.toggle()
         DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
             triggerHapticFeedback(style: .medium)
diff --git a/Sources/StreamChatSwiftUI/ChatChannel/MessageList/VideoAttachmentView.swift b/Sources/StreamChatSwiftUI/ChatChannel/MessageList/VideoAttachmentView.swift
index c285dfe87..534240fd8 100644
--- a/Sources/StreamChatSwiftUI/ChatChannel/MessageList/VideoAttachmentView.swift
+++ b/Sources/StreamChatSwiftUI/ChatChannel/MessageList/VideoAttachmentView.swift
@@ -24,6 +24,7 @@ public struct VideoAttachmentsContainer<Factory: ViewFactory>: View {
                     )
 
                     VideoAttachmentsList(
+                        factory: factory,
                         message: message,
                         width: width
                     )
@@ -38,6 +39,7 @@ public struct VideoAttachmentsContainer<Factory: ViewFactory>: View {
                 )
             } else {
                 VideoAttachmentsList(
+                    factory: factory,
                     message: message,
                     width: width
                 )
@@ -63,12 +65,18 @@ public struct VideoAttachmentsContainer<Factory: ViewFactory>: View {
     }
 }
 
-public struct VideoAttachmentsList: View {
+public struct VideoAttachmentsList<Factory: ViewFactory>: View {
 
+    let factory: Factory
     let message: ChatMessage
     let width: CGFloat
 
-    public init(message: ChatMessage, width: CGFloat) {
+    public init(
+        factory: Factory = DefaultViewFactory.shared,
+        message: ChatMessage,
+        width: CGFloat
+    ) {
+        self.factory = factory
         self.message = message
         self.width = width
     }
@@ -77,6 +85,7 @@ public struct VideoAttachmentsList: View {
         VStack {
             ForEach(message.videoAttachments, id: \.self) { attachment in
                 VideoAttachmentView(
+                    factory: factory,
                     attachment: attachment,
                     message: message,
                     width: width
@@ -90,8 +99,9 @@ public struct VideoAttachmentsList: View {
     }
 }
 
-public struct VideoAttachmentView: View {
+public struct VideoAttachmentView<Factory: ViewFactory>: View {
 
+    let factory: Factory
     let attachment: ChatMessageVideoAttachment
     let message: ChatMessage
     let width: CGFloat
@@ -99,12 +109,14 @@ public struct VideoAttachmentView: View {
     var cornerRadius: CGFloat = 24
 
     public init(
+        factory: Factory = DefaultViewFactory.shared,
         attachment: ChatMessageVideoAttachment,
         message: ChatMessage,
         width: CGFloat,
         ratio: CGFloat = 0.75,
         cornerRadius: CGFloat = 24
     ) {
+        self.factory = factory
         self.attachment = attachment
         self.message = message
         self.width = width
@@ -118,8 +130,9 @@ public struct VideoAttachmentView: View {
 
     public var body: some View {
         VideoAttachmentContentView(
+            factory: factory,
             attachment: attachment,
-            author: message.author,
+            message: message,
             width: width,
             ratio: ratio,
             cornerRadius: cornerRadius
@@ -128,7 +141,7 @@ public struct VideoAttachmentView: View {
     }
 }
 
-struct VideoAttachmentContentView: View {
+struct VideoAttachmentContentView<Factory: ViewFactory>: View {
 
     @Injected(\.utils) private var utils
     @Injected(\.images) private var images
@@ -137,8 +150,9 @@ struct VideoAttachmentContentView: View {
         utils.videoPreviewLoader
     }
 
+    let factory: Factory
     let attachment: ChatMessageVideoAttachment
-    let author: ChatUser
+    let message: ChatMessage
     let width: CGFloat
     var ratio: CGFloat = 0.75
     var cornerRadius: CGFloat = 24
@@ -183,10 +197,11 @@ struct VideoAttachmentContentView: View {
         .frame(width: width, height: width * ratio)
         .cornerRadius(cornerRadius)
         .fullScreenCover(isPresented: $fullScreenShown) {
-            VideoPlayerView(
+            factory.makeVideoPlayerView(
                 attachment: attachment,
-                author: author,
-                isShown: $fullScreenShown
+                message: message,
+                isShown: $fullScreenShown,
+                options: .init(selectedIndex: 0)
             )
         }
         .onAppear {
diff --git a/Sources/StreamChatSwiftUI/ChatChannel/Reactions/MessageActions/DefaultMessageActions.swift b/Sources/StreamChatSwiftUI/ChatChannel/Reactions/MessageActions/DefaultMessageActions.swift
index 0a112b41e..8dc7cc287 100644
--- a/Sources/StreamChatSwiftUI/ChatChannel/Reactions/MessageActions/DefaultMessageActions.swift
+++ b/Sources/StreamChatSwiftUI/ChatChannel/Reactions/MessageActions/DefaultMessageActions.swift
@@ -146,7 +146,7 @@ public extension MessageAction {
         }
 
         if message.isSentByCurrentUser {
-            if message.poll == nil {
+            if message.poll == nil && message.giphyAttachments.isEmpty {
                 let editAction = editMessageAction(
                     for: message,
                     channel: channel,
diff --git a/Sources/StreamChatSwiftUI/ChatChannel/Reactions/ReactionsOverlayView.swift b/Sources/StreamChatSwiftUI/ChatChannel/Reactions/ReactionsOverlayView.swift
index 371d1a35e..7d20b5e1a 100644
--- a/Sources/StreamChatSwiftUI/ChatChannel/Reactions/ReactionsOverlayView.swift
+++ b/Sources/StreamChatSwiftUI/ChatChannel/Reactions/ReactionsOverlayView.swift
@@ -111,24 +111,13 @@ public struct ReactionsOverlayView<Factory: ViewFactory>: View {
                     Group {
                         if messageDisplayInfo.frame.height > messageContainerHeight {
                             ScrollView {
-                                MessageView(
-                                    factory: factory,
-                                    message: messageDisplayInfo.message,
-                                    contentWidth: messageDisplayInfo.contentWidth,
-                                    isFirst: messageDisplayInfo.isFirst,
-                                    scrolledId: .constant(nil)
-                                )
+                                messageView
                             }
                         } else {
-                            MessageView(
-                                factory: factory,
-                                message: messageDisplayInfo.message,
-                                contentWidth: messageDisplayInfo.contentWidth,
-                                isFirst: messageDisplayInfo.isFirst,
-                                scrolledId: .constant(nil)
-                            )
+                            messageView
                         }
                     }
+                    .environment(\.channelTranslationLanguage, channel.membership?.language)
                     .scaleEffect(popIn || willPopOut ? 1 : 0.95)
                     .animation(willPopOut ? .easeInOut : popInAnimation, value: popIn)
                     .offset(
@@ -228,6 +217,16 @@ public struct ReactionsOverlayView<Factory: ViewFactory>: View {
         }
     }
 
+    private var messageView: some View {
+        MessageView(
+            factory: factory,
+            message: messageDisplayInfo.message,
+            contentWidth: messageDisplayInfo.contentWidth,
+            isFirst: messageDisplayInfo.isFirst,
+            scrolledId: .constant(nil)
+        )
+    }
+
     private func dismissReactionsOverlay(completion: @escaping () -> Void) {
         withAnimation {
             willPopOut = true
diff --git a/Sources/StreamChatSwiftUI/DefaultViewFactory.swift b/Sources/StreamChatSwiftUI/DefaultViewFactory.swift
index 4f350e9e3..3e14a3829 100644
--- a/Sources/StreamChatSwiftUI/DefaultViewFactory.swift
+++ b/Sources/StreamChatSwiftUI/DefaultViewFactory.swift
@@ -445,6 +445,33 @@ extension ViewFactory {
         )
     }
     
+    public func makeGalleryView(
+        mediaAttachments: [MediaAttachment],
+        message: ChatMessage,
+        isShown: Binding<Bool>,
+        options: MediaViewsOptions
+    ) -> some View {
+        GalleryView(
+            mediaAttachments: mediaAttachments,
+            author: message.author,
+            isShown: isShown,
+            selected: options.selectedIndex
+        )
+    }
+    
+    public func makeVideoPlayerView(
+        attachment: ChatMessageVideoAttachment,
+        message: ChatMessage,
+        isShown: Binding<Bool>,
+        options: MediaViewsOptions
+    ) -> some View {
+        VideoPlayerView(
+            attachment: attachment,
+            author: message.author,
+            isShown: isShown
+        )
+    }
+    
     public func makeDeletedMessageView(
         for message: ChatMessage,
         isFirst: Bool,
diff --git a/Sources/StreamChatSwiftUI/Generated/SystemEnvironment+Version.swift b/Sources/StreamChatSwiftUI/Generated/SystemEnvironment+Version.swift
index 5c41ce9f3..72dcbfde0 100644
--- a/Sources/StreamChatSwiftUI/Generated/SystemEnvironment+Version.swift
+++ b/Sources/StreamChatSwiftUI/Generated/SystemEnvironment+Version.swift
@@ -7,5 +7,5 @@ import Foundation
 
 enum SystemEnvironment {
   /// A Stream Chat version.
-  public static let version: String = "4.77.0"
+  public static let version: String = "4.78.0"
 }
diff --git a/Sources/StreamChatSwiftUI/Info.plist b/Sources/StreamChatSwiftUI/Info.plist
index 0ca5409a0..1bd3d64a3 100644
--- a/Sources/StreamChatSwiftUI/Info.plist
+++ b/Sources/StreamChatSwiftUI/Info.plist
@@ -15,7 +15,7 @@
 	<key>CFBundlePackageType</key>
 	<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
 	<key>CFBundleShortVersionString</key>
-	<string>4.77.0</string>
+	<string>4.78.0</string>
 	<key>CFBundleVersion</key>
 	<string>$(CURRENT_PROJECT_VERSION)</string>
 	<key>NSPhotoLibraryUsageDescription</key>
diff --git a/Sources/StreamChatSwiftUI/ViewFactory.swift b/Sources/StreamChatSwiftUI/ViewFactory.swift
index 7f4db11d9..762eaadc8 100644
--- a/Sources/StreamChatSwiftUI/ViewFactory.swift
+++ b/Sources/StreamChatSwiftUI/ViewFactory.swift
@@ -439,6 +439,36 @@ public protocol ViewFactory: AnyObject {
         availableWidth: CGFloat,
         scrolledId: Binding<String?>
     ) -> VideoAttachmentViewType
+    
+    associatedtype GalleryViewType: View
+    /// Creates the gallery view.
+    /// - Parameters:
+    ///  - mediaAttachments: the media attachments that will be displayed.
+    ///  - message: the message whose attachments will be displayed.
+    ///  - isShown: whether the gallery is shown.
+    ///  - options: additional options used to configure the gallery view.
+    ///  - Returns: view displayed in the gallery slot.
+    func makeGalleryView(
+        mediaAttachments: [MediaAttachment],
+        message: ChatMessage,
+        isShown: Binding<Bool>,
+        options: MediaViewsOptions
+    ) -> GalleryViewType
+    
+    associatedtype VideoPlayerViewType: View
+    /// Creates the video player view.
+    /// - Parameters:
+    ///  - attachment: the video attachment that will be displayed.
+    ///  - message: the message whose attachments will be displayed.
+    ///  - isShown: whether the video player is shown.
+    ///  - options: additional options used to configure the gallery view.
+    ///  - Returns: view displayed in the video player slot.
+    func makeVideoPlayerView(
+        attachment: ChatMessageVideoAttachment,
+        message: ChatMessage,
+        isShown: Binding<Bool>,
+        options: MediaViewsOptions
+    ) -> VideoPlayerViewType
 
     associatedtype DeletedMessageViewType: View
     /// Creates the deleted message view.
diff --git a/StreamChatSwiftUI-XCFramework.podspec b/StreamChatSwiftUI-XCFramework.podspec
index ec4507e72..388996a85 100644
--- a/StreamChatSwiftUI-XCFramework.podspec
+++ b/StreamChatSwiftUI-XCFramework.podspec
@@ -1,6 +1,6 @@
 Pod::Spec.new do |spec|
   spec.name = 'StreamChatSwiftUI-XCFramework'
-  spec.version = '4.77.0'
+  spec.version = '4.78.0'
   spec.summary = 'StreamChat SwiftUI Chat Components'
   spec.description = 'StreamChatSwiftUI SDK offers flexible SwiftUI components able to display data provided by StreamChat SDK.'
 
@@ -19,7 +19,7 @@ Pod::Spec.new do |spec|
 
   spec.framework = 'Foundation', 'UIKit', 'SwiftUI'
 
-  spec.dependency 'StreamChat-XCFramework', '~> 4.77.0'
+  spec.dependency 'StreamChat-XCFramework', '~> 4.78.0'
 
   spec.cocoapods_version = '>= 1.11.0'
 end
diff --git a/StreamChatSwiftUI.podspec b/StreamChatSwiftUI.podspec
index faa018d68..4de9e323b 100644
--- a/StreamChatSwiftUI.podspec
+++ b/StreamChatSwiftUI.podspec
@@ -1,6 +1,6 @@
 Pod::Spec.new do |spec|
   spec.name = 'StreamChatSwiftUI'
-  spec.version = '4.77.0'
+  spec.version = '4.78.0'
   spec.summary = 'StreamChat SwiftUI Chat Components'
   spec.description = 'StreamChatSwiftUI SDK offers flexible SwiftUI components able to display data provided by StreamChat SDK.'
 
@@ -19,5 +19,5 @@ Pod::Spec.new do |spec|
 
   spec.framework = 'Foundation', 'UIKit', 'SwiftUI'
 
-  spec.dependency 'StreamChat', '~> 4.77.0'
+  spec.dependency 'StreamChat', '~> 4.78.0'
 end
diff --git a/StreamChatSwiftUI.xcodeproj/project.pbxproj b/StreamChatSwiftUI.xcodeproj/project.pbxproj
index 7749b5609..582fad6af 100644
--- a/StreamChatSwiftUI.xcodeproj/project.pbxproj
+++ b/StreamChatSwiftUI.xcodeproj/project.pbxproj
@@ -3880,7 +3880,7 @@
 			repositoryURL = "https://github.com/GetStream/stream-chat-swift.git";
 			requirement = {
 				kind = upToNextMajorVersion;
-				minimumVersion = 4.77.0;
+				minimumVersion = 4.78.0;
 			};
 		};
 		E3A1C01A282BAC66002D1E26 /* XCRemoteSwiftPackageReference "sentry-cocoa" */ = {
diff --git a/StreamChatSwiftUIArtifacts.json b/StreamChatSwiftUIArtifacts.json
index fc04839db..74ca01551 100644
--- a/StreamChatSwiftUIArtifacts.json
+++ b/StreamChatSwiftUIArtifacts.json
@@ -1 +1 @@
-{"4.40.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.40.0/StreamChatSwiftUI.zip","4.41.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.41.0/StreamChatSwiftUI.zip","4.42.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.42.0/StreamChatSwiftUI.zip","4.43.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.43.0/StreamChatSwiftUI.zip","4.44.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.44.0/StreamChatSwiftUI.zip","4.45.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.45.0/StreamChatSwiftUI.zip","4.46.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.46.0/StreamChatSwiftUI.zip","4.47.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.47.0/StreamChatSwiftUI.zip","4.47.1":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.47.1/StreamChatSwiftUI.zip","4.48.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.48.0/StreamChatSwiftUI.zip","4.49.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.49.0/StreamChatSwiftUI.zip","4.50.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.50.0/StreamChatSwiftUI.zip","4.50.1":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.50.1/StreamChatSwiftUI.zip","4.51.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.51.0/StreamChatSwiftUI.zip","4.52.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.52.0/StreamChatSwiftUI.zip","4.53.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.53.0/StreamChatSwiftUI.zip","4.54.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.54.0/StreamChatSwiftUI.zip","4.55.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.55.0/StreamChatSwiftUI.zip","4.56.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.56.0/StreamChatSwiftUI.zip","4.57.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.57.0/StreamChatSwiftUI.zip","4.58.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.58.0/StreamChatSwiftUI.zip","4.59.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.59.0/StreamChatSwiftUI.zip","4.60.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.60.0/StreamChatSwiftUI.zip","4.61.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.61.0/StreamChatSwiftUI.zip","4.62.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.62.0/StreamChatSwiftUI.zip","4.63.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.63.0/StreamChatSwiftUI.zip","4.64.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.64.0/StreamChatSwiftUI.zip","4.65.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.65.0/StreamChatSwiftUI.zip","4.66.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.66.0/StreamChatSwiftUI.zip","4.67.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.67.0/StreamChatSwiftUI.zip","4.68.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.68.0/StreamChatSwiftUI.zip","4.69.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.69.0/StreamChatSwiftUI.zip","4.70.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.70.0/StreamChatSwiftUI.zip","4.71.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.71.0/StreamChatSwiftUI.zip","4.72.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.72.0/StreamChatSwiftUI.zip","4.73.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.73.0/StreamChatSwiftUI.zip","4.74.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.74.0/StreamChatSwiftUI.zip","4.75.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.75.0/StreamChatSwiftUI.zip","4.76.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.76.0/StreamChatSwiftUI.zip","4.77.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.77.0/StreamChatSwiftUI.zip"}
\ No newline at end of file
+{"4.40.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.40.0/StreamChatSwiftUI.zip","4.41.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.41.0/StreamChatSwiftUI.zip","4.42.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.42.0/StreamChatSwiftUI.zip","4.43.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.43.0/StreamChatSwiftUI.zip","4.44.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.44.0/StreamChatSwiftUI.zip","4.45.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.45.0/StreamChatSwiftUI.zip","4.46.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.46.0/StreamChatSwiftUI.zip","4.47.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.47.0/StreamChatSwiftUI.zip","4.47.1":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.47.1/StreamChatSwiftUI.zip","4.48.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.48.0/StreamChatSwiftUI.zip","4.49.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.49.0/StreamChatSwiftUI.zip","4.50.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.50.0/StreamChatSwiftUI.zip","4.50.1":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.50.1/StreamChatSwiftUI.zip","4.51.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.51.0/StreamChatSwiftUI.zip","4.52.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.52.0/StreamChatSwiftUI.zip","4.53.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.53.0/StreamChatSwiftUI.zip","4.54.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.54.0/StreamChatSwiftUI.zip","4.55.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.55.0/StreamChatSwiftUI.zip","4.56.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.56.0/StreamChatSwiftUI.zip","4.57.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.57.0/StreamChatSwiftUI.zip","4.58.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.58.0/StreamChatSwiftUI.zip","4.59.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.59.0/StreamChatSwiftUI.zip","4.60.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.60.0/StreamChatSwiftUI.zip","4.61.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.61.0/StreamChatSwiftUI.zip","4.62.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.62.0/StreamChatSwiftUI.zip","4.63.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.63.0/StreamChatSwiftUI.zip","4.64.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.64.0/StreamChatSwiftUI.zip","4.65.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.65.0/StreamChatSwiftUI.zip","4.66.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.66.0/StreamChatSwiftUI.zip","4.67.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.67.0/StreamChatSwiftUI.zip","4.68.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.68.0/StreamChatSwiftUI.zip","4.69.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.69.0/StreamChatSwiftUI.zip","4.70.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.70.0/StreamChatSwiftUI.zip","4.71.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.71.0/StreamChatSwiftUI.zip","4.72.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.72.0/StreamChatSwiftUI.zip","4.73.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.73.0/StreamChatSwiftUI.zip","4.74.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.74.0/StreamChatSwiftUI.zip","4.75.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.75.0/StreamChatSwiftUI.zip","4.76.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.76.0/StreamChatSwiftUI.zip","4.77.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.77.0/StreamChatSwiftUI.zip","4.78.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.78.0/StreamChatSwiftUI.zip"}
\ No newline at end of file
diff --git a/StreamChatSwiftUITests/Tests/ChatChannel/ChatChannelViewModel_Tests.swift b/StreamChatSwiftUITests/Tests/ChatChannel/ChatChannelViewModel_Tests.swift
index d0e89166d..64e67f5f8 100644
--- a/StreamChatSwiftUITests/Tests/ChatChannel/ChatChannelViewModel_Tests.swift
+++ b/StreamChatSwiftUITests/Tests/ChatChannel/ChatChannelViewModel_Tests.swift
@@ -94,6 +94,45 @@ class ChatChannelViewModel_Tests: StreamChatTestCase {
         XCTAssert(viewModel.scrolledId!.contains(messageId))
     }
 
+    func test_chatChannelVM_messageSentTapped() {
+        // Given
+        let messageId: String = .unique
+        let message = ChatMessage.mock(
+            id: messageId,
+            cid: .unique,
+            text: "Test message",
+            author: ChatUser.mock(id: chatClient.currentUserId!)
+        )
+        let channelController = makeChannelController(messages: [message])
+        let viewModel = ChatChannelViewModel(channelController: channelController)
+
+        // When
+        viewModel.messageSentTapped()
+
+        // Then
+        XCTAssert(viewModel.scrolledId!.contains(messageId))
+    }
+
+    func test_chatChannelVM_messageSentTapped_whenEditingMessage_shouldNotScroll() {
+        // Given
+        let messageId: String = .unique
+        let message = ChatMessage.mock(
+            id: messageId,
+            cid: .unique,
+            text: "Test message",
+            author: ChatUser.mock(id: chatClient.currentUserId!)
+        )
+        let channelController = makeChannelController(messages: [message])
+        let viewModel = ChatChannelViewModel(channelController: channelController)
+        viewModel.editedMessage = .unique
+
+        // When
+        viewModel.messageSentTapped()
+
+        // Then
+        XCTAssertNil(viewModel.scrolledId)
+    }
+
     func test_chatChannelVM_currentDateString() {
         // Given
         let expectedDate = "Jan 01"
diff --git a/StreamChatSwiftUITests/Tests/ChatChannel/MessageActions_Tests.swift b/StreamChatSwiftUITests/Tests/ChatChannel/MessageActions_Tests.swift
index 12b55f026..83e10a087 100644
--- a/StreamChatSwiftUITests/Tests/ChatChannel/MessageActions_Tests.swift
+++ b/StreamChatSwiftUITests/Tests/ChatChannel/MessageActions_Tests.swift
@@ -4,6 +4,7 @@
 
 @testable import StreamChat
 @testable import StreamChatSwiftUI
+@testable import StreamChatTestTools
 import XCTest
 
 class MessageActions_Tests: StreamChatTestCase {
@@ -251,7 +252,47 @@ class MessageActions_Tests: StreamChatTestCase {
         XCTAssert(messageActions[2].title == "Edit Message")
         XCTAssert(messageActions[3].title == "Delete Message")
     }
-    
+
+    func test_messageActions_giphyMessage_shouldNotContainEditActtion() throws {
+        // Given
+        let channel = mockDMChannel
+        let message = ChatMessage.mock(
+            id: .unique,
+            cid: channel.cid,
+            text: "Test",
+            author: .mock(id: chatClient.currentUserId!),
+            attachments: [
+                .dummy(
+                    type: .giphy,
+                    payload: try JSONEncoder().encode(GiphyAttachmentPayload(
+                        title: "Test",
+                        previewURL: URL(string: "Url")!
+                    ))
+                )
+            ],
+            isSentByCurrentUser: true
+        )
+        let factory = DefaultViewFactory.shared
+
+        // When
+        let messageActions = MessageAction.defaultActions(
+            factory: factory,
+            for: message,
+            channel: channel,
+            chatClient: chatClient,
+            onFinish: { _ in },
+            onError: { _ in }
+        )
+
+        // Then
+        XCTAssertEqual(messageActions.count, 5)
+        XCTAssertEqual(messageActions[0].title, "Reply")
+        XCTAssertEqual(messageActions[1].title, "Thread Reply")
+        XCTAssertEqual(messageActions[2].title, "Pin to conversation")
+        XCTAssertEqual(messageActions[3].title, "Copy Message")
+        XCTAssertEqual(messageActions[4].title, "Delete Message")
+    }
+
     // MARK: - Private
     
     private var mockDMChannel: ChatChannel {
diff --git a/StreamChatSwiftUITests/Tests/ChatChannel/MessageComposerView_Tests.swift b/StreamChatSwiftUITests/Tests/ChatChannel/MessageComposerView_Tests.swift
index 7d77aabf0..3ed23f001 100644
--- a/StreamChatSwiftUITests/Tests/ChatChannel/MessageComposerView_Tests.swift
+++ b/StreamChatSwiftUITests/Tests/ChatChannel/MessageComposerView_Tests.swift
@@ -582,6 +582,7 @@ class MessageComposerView_Tests: StreamChatTestCase {
             draftMessage: draftMessage
         )
         let viewModel = MessageComposerViewModel(channelController: channelController, messageController: nil)
+        viewModel.attachmentsConverter = SyncAttachmentsConverter()
 
         return MessageComposerView(
             viewFactory: factory,
@@ -629,4 +630,168 @@ class MessageComposerView_Tests: StreamChatTestCase {
 
         AssertSnapshot(view, variants: .onlyUserInterfaceStyles, size: size)
     }
+
+    // MARK: - Editing
+
+    func test_composerView_editingMessageWithText() {
+        let size = CGSize(width: defaultScreenSize.width, height: 100)
+        let mockEditedMessage = ChatMessage.mock(
+            id: .unique,
+            cid: .unique,
+            text: "This is a message being edited",
+            author: .mock(id: .unique)
+        )
+
+        let view = makeComposerViewWithEditedMessage(mockEditedMessage)
+            .frame(width: size.width, height: size.height)
+
+        AssertSnapshot(view, variants: [.defaultLight], size: size)
+    }
+
+    func test_composerView_editingMessageWithImageAttachment() throws {
+        let size = CGSize(width: defaultScreenSize.width, height: 200)
+        let mockEditedMessage = ChatMessage.mock(
+            id: .unique,
+            cid: .unique,
+            text: "Message with image",
+            author: .mock(id: .unique),
+            attachments: [
+                .dummy(
+                    type: .image,
+                    payload: try JSONEncoder().encode(
+                        ImageAttachmentPayload(
+                            title: nil,
+                            imageRemoteURL: TestImages.yoda.url,
+                            file: .init(type: .jpeg, size: 10, mimeType: nil)
+                        )
+                    )
+                )
+            ]
+        )
+
+        let view = makeComposerViewWithEditedMessage(mockEditedMessage)
+            .frame(width: size.width, height: size.height)
+
+        AssertSnapshot(view, variants: [.defaultLight], size: size)
+    }
+
+    func test_composerView_editingMessageWithVideoAttachment() throws {
+        let size = CGSize(width: defaultScreenSize.width, height: 200)
+        let mockEditedMessage = ChatMessage.mock(
+            id: .unique,
+            cid: .unique,
+            text: "Message with video",
+            author: .mock(id: .unique),
+            attachments: [
+                .dummy(
+                    type: .video,
+                    payload: try JSONEncoder().encode(
+                        VideoAttachmentPayload(
+                            title: nil,
+                            videoRemoteURL: TestImages.yoda.url,
+                            thumbnailURL: TestImages.yoda.url,
+                            file: .init(type: .mov, size: 10, mimeType: nil),
+                            extraData: nil
+                        )
+                    )
+                )
+            ]
+        )
+
+        let view = makeComposerViewWithEditedMessage(mockEditedMessage)
+            .frame(width: size.width, height: size.height)
+
+        AssertSnapshot(view, variants: [.defaultLight], size: size)
+    }
+
+    func test_composerView_editingMessageWithFileAttachment() throws {
+        let size = CGSize(width: defaultScreenSize.width, height: 200)
+        let mockEditedMessage = ChatMessage.mock(
+            id: .unique,
+            cid: .unique,
+            text: "Message with file",
+            author: .mock(id: .unique),
+            attachments: [
+                .dummy(
+                    type: .file,
+                    payload: try JSONEncoder().encode(
+                        FileAttachmentPayload(
+                            title: "Test",
+                            assetRemoteURL: .localYodaQuote,
+                            file: .init(type: .txt, size: 10, mimeType: nil),
+                            extraData: nil
+                        )
+                    )
+                )
+            ]
+        )
+
+        let view = makeComposerViewWithEditedMessage(mockEditedMessage)
+            .frame(width: size.width, height: size.height)
+
+        AssertSnapshot(view, variants: [.defaultLight], size: size)
+    }
+
+    func test_composerView_editingMessageWithVoiceRecording() throws {
+        let url: URL = URL(fileURLWithPath: "/tmp/\(UUID().uuidString)")
+        let duration: TimeInterval = 100
+        let waveformData: [Float] = .init(repeating: 0.5, count: 10)
+        try Data(count: 1024).write(to: url)
+        defer { try? FileManager.default.removeItem(at: url) }
+
+        let size = CGSize(width: defaultScreenSize.width, height: 200)
+        let mockEditedMessage = ChatMessage.mock(
+            id: .unique,
+            cid: .unique,
+            text: "Message with voice recording",
+            author: .mock(id: .unique),
+            attachments: [
+                .dummy(
+                    type: .voiceRecording,
+                    payload: try JSONEncoder().encode(
+                        VoiceRecordingAttachmentPayload(
+                            title: "Audio",
+                            voiceRecordingRemoteURL: url,
+                            file: .init(type: .aac, size: 120, mimeType: "audio/aac"),
+                            duration: duration,
+                            waveformData: waveformData,
+                            extraData: nil
+                        )
+                    )
+                )
+            ]
+        )
+
+        let view = makeComposerViewWithEditedMessage(mockEditedMessage)
+            .frame(width: size.width, height: size.height)
+
+        AssertSnapshot(view, variants: [.defaultLight], size: size)
+    }
+
+    private func makeComposerViewWithEditedMessage(_ editedMessage: ChatMessage) -> some View {
+        let factory = DefaultViewFactory.shared
+        let channelController = ChatChannelTestHelpers.makeChannelController(chatClient: chatClient)
+        let viewModel = MessageComposerViewModel(channelController: channelController, messageController: nil)
+        viewModel.attachmentsConverter = SyncAttachmentsConverter()
+        viewModel.fillEditedMessage(editedMessage)
+
+        return MessageComposerView(
+            viewFactory: factory,
+            viewModel: viewModel,
+            channelController: channelController,
+            quotedMessage: .constant(nil),
+            editedMessage: .constant(editedMessage),
+            onMessageSent: {}
+        )
+    }
+}
+
+class SyncAttachmentsConverter: MessageAttachmentsConverter {
+    override func attachmentsToAssets(
+        _ attachments: [AnyChatMessageAttachment],
+        completion: @escaping (ComposerAssets) -> Void
+    ) {
+        let addedAssets = attachmentsToAssets(attachments)
+        completion(addedAssets)
+    }
 }
diff --git a/StreamChatSwiftUITests/Tests/ChatChannel/MessageContainerView_Tests.swift b/StreamChatSwiftUITests/Tests/ChatChannel/MessageContainerView_Tests.swift
index 21a1cdb7f..bbd514b6f 100644
--- a/StreamChatSwiftUITests/Tests/ChatChannel/MessageContainerView_Tests.swift
+++ b/StreamChatSwiftUITests/Tests/ChatChannel/MessageContainerView_Tests.swift
@@ -339,6 +339,146 @@ class MessageContainerView_Tests: StreamChatTestCase {
         AssertSnapshot(view, size: CGSize(width: 375, height: 200))
     }
 
+    func test_handleGestureForMessage_whenMessageIsInteractable_shouldLongPress() {
+        // Given
+        let message = ChatMessage.mock(
+            id: .unique,
+            cid: .unique,
+            text: "Hello",
+            localState: nil,
+            isSentByCurrentUser: true
+        )
+
+        let exp = expectation(description: "Long press triggered")
+        let view = MessageContainerView(
+            factory: DefaultViewFactory.shared,
+            channel: .mockDMChannel(),
+            message: message,
+            width: defaultScreenSize.width,
+            showsAllInfo: true,
+            isInThread: false,
+            isLast: false,
+            scrolledId: .constant(nil),
+            quotedMessage: .constant(nil)
+        ) { _ in
+            exp.fulfill()
+        }
+
+        view.handleGestureForMessage(showsMessageActions: false, showsBottomContainer: false)
+
+        waitForExpectations(timeout: defaultTimeout) { error in
+            XCTAssertNil(error, "Long press was not triggered")
+        }
+    }
+
+    func test_handleGestureForMessage_whenMessageNotInteractable_shouldNotLongPress() {
+        // Given
+        let message = ChatMessage.mock(
+            id: .unique,
+            cid: .unique,
+            text: "Hello",
+            type: .ephemeral,
+            localState: nil,
+            isSentByCurrentUser: true
+        )
+
+        let exp = expectation(description: "Long press should not be triggered")
+        exp.isInverted = true
+        let view = MessageContainerView(
+            factory: DefaultViewFactory.shared,
+            channel: .mockDMChannel(),
+            message: message,
+            width: defaultScreenSize.width,
+            showsAllInfo: true,
+            isInThread: false,
+            isLast: false,
+            scrolledId: .constant(nil),
+            quotedMessage: .constant(nil)
+        ) { _ in
+            exp.fulfill()
+        }
+
+        view.handleGestureForMessage(showsMessageActions: false, showsBottomContainer: false)
+
+        waitForExpectations(timeout: 1)
+    }
+
+    func test_isSwipeToReplyPossible_whenRepliesEnabled_whenMessageInteractable_shouldBeTrue() {
+        let message = ChatMessage.mock(
+            id: .unique,
+            cid: .unique,
+            text: "Hello",
+            localState: nil,
+            isSentByCurrentUser: true
+        )
+
+        let view = MessageContainerView(
+            factory: DefaultViewFactory.shared,
+            channel: .mockDMChannel(config: .mock(repliesEnabled: true)),
+            message: message,
+            width: defaultScreenSize.width,
+            showsAllInfo: true,
+            isInThread: false,
+            isLast: false,
+            scrolledId: .constant(nil),
+            quotedMessage: .constant(nil),
+            onLongPress: { _ in }
+        )
+
+        XCTAssertTrue(view.isSwipeToReplyPossible)
+    }
+
+    func test_isSwipeToReplyPossible_whenRepliesDisabled_whenMessageInteractable_shouldBeFalse() {
+        let message = ChatMessage.mock(
+            id: .unique,
+            cid: .unique,
+            text: "Hello",
+            localState: nil,
+            isSentByCurrentUser: true
+        )
+
+        let view = MessageContainerView(
+            factory: DefaultViewFactory.shared,
+            channel: .mockDMChannel(config: .mock(repliesEnabled: false)),
+            message: message,
+            width: defaultScreenSize.width,
+            showsAllInfo: true,
+            isInThread: false,
+            isLast: false,
+            scrolledId: .constant(nil),
+            quotedMessage: .constant(nil),
+            onLongPress: { _ in }
+        )
+
+        XCTAssertFalse(view.isSwipeToReplyPossible)
+    }
+
+    func test_isSwipeToReplyPossible_whenRepliesEnabled_whenMessageNotInteractable_shouldBeFalse() {
+        let message = ChatMessage.mock(
+            id: .unique,
+            cid: .unique,
+            text: "Hello",
+            type: .ephemeral,
+            localState: nil,
+            isSentByCurrentUser: true
+        )
+
+        let view = MessageContainerView(
+            factory: DefaultViewFactory.shared,
+            channel: .mockDMChannel(config: .mock(repliesEnabled: true)),
+            message: message,
+            width: defaultScreenSize.width,
+            showsAllInfo: true,
+            isInThread: false,
+            isLast: false,
+            scrolledId: .constant(nil),
+            quotedMessage: .constant(nil),
+            onLongPress: { _ in }
+        )
+
+        XCTAssertFalse(view.isSwipeToReplyPossible)
+    }
+
     // MARK: - private
 
     func testMessageViewContainer(message: ChatMessage) -> some View {
diff --git a/StreamChatSwiftUITests/Tests/ChatChannel/ReactionsOverlayView_Tests.swift b/StreamChatSwiftUITests/Tests/ChatChannel/ReactionsOverlayView_Tests.swift
index 56a61e4c6..03c4f6dbd 100644
--- a/StreamChatSwiftUITests/Tests/ChatChannel/ReactionsOverlayView_Tests.swift
+++ b/StreamChatSwiftUITests/Tests/ChatChannel/ReactionsOverlayView_Tests.swift
@@ -225,6 +225,36 @@ class ReactionsOverlayView_Tests: StreamChatTestCase {
         // Then
         XCTAssert(offset == 12.5)
     }
+
+    func test_reactionsOverlayView_translated() {
+        // Given
+        let testMessage = ChatMessage.mock(
+            id: "test",
+            cid: .unique,
+            text: "Hello",
+            author: .mock(id: "test", name: "martin"),
+            translations: [.portuguese: "Olรก"]
+        )
+        let messageDisplayInfo = MessageDisplayInfo(
+            message: testMessage,
+            frame: self.messageDisplayInfo.frame,
+            contentWidth: self.messageDisplayInfo.contentWidth,
+            isFirst: true
+        )
+        let view = VerticallyCenteredView {
+            ReactionsOverlayView(
+                factory: DefaultViewFactory.shared,
+                channel: .mock(cid: .unique, membership: .mock(id: "test", language: .portuguese)),
+                currentSnapshot: self.overlayImage,
+                messageDisplayInfo: messageDisplayInfo,
+                onBackgroundTap: {},
+                onActionExecuted: { _ in }
+            )
+        }
+
+        // Then
+        assertSnapshot(matching: view, as: .image(perceptualPrecision: precision))
+    }
 }
 
 struct VerticallyCenteredView<Content: View>: View {
diff --git a/StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageComposerView_Tests/test_composerView_editingMessageWithFileAttachment.default-light.png b/StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageComposerView_Tests/test_composerView_editingMessageWithFileAttachment.default-light.png
new file mode 100644
index 000000000..e24488116
Binary files /dev/null and b/StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageComposerView_Tests/test_composerView_editingMessageWithFileAttachment.default-light.png differ
diff --git a/StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageComposerView_Tests/test_composerView_editingMessageWithImageAttachment.default-light.png b/StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageComposerView_Tests/test_composerView_editingMessageWithImageAttachment.default-light.png
new file mode 100644
index 000000000..fa9494b93
Binary files /dev/null and b/StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageComposerView_Tests/test_composerView_editingMessageWithImageAttachment.default-light.png differ
diff --git a/StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageComposerView_Tests/test_composerView_editingMessageWithText.default-light.png b/StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageComposerView_Tests/test_composerView_editingMessageWithText.default-light.png
new file mode 100644
index 000000000..d7c9666ed
Binary files /dev/null and b/StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageComposerView_Tests/test_composerView_editingMessageWithText.default-light.png differ
diff --git a/StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageComposerView_Tests/test_composerView_editingMessageWithVideoAttachment.default-light.png b/StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageComposerView_Tests/test_composerView_editingMessageWithVideoAttachment.default-light.png
new file mode 100644
index 000000000..9962427cf
Binary files /dev/null and b/StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageComposerView_Tests/test_composerView_editingMessageWithVideoAttachment.default-light.png differ
diff --git a/StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageComposerView_Tests/test_composerView_editingMessageWithVoiceRecording.default-light.png b/StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageComposerView_Tests/test_composerView_editingMessageWithVoiceRecording.default-light.png
new file mode 100644
index 000000000..ce3dde67f
Binary files /dev/null and b/StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageComposerView_Tests/test_composerView_editingMessageWithVoiceRecording.default-light.png differ
diff --git a/StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/ReactionsOverlayView_Tests/test_reactionsOverlayView_translated.1.png b/StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/ReactionsOverlayView_Tests/test_reactionsOverlayView_translated.1.png
new file mode 100644
index 000000000..c346eb2fb
Binary files /dev/null and b/StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/ReactionsOverlayView_Tests/test_reactionsOverlayView_translated.1.png differ
diff --git a/StreamChatSwiftUITests/Tests/Utils/ViewFactory_Tests.swift b/StreamChatSwiftUITests/Tests/Utils/ViewFactory_Tests.swift
index a2fed546e..8387b3faa 100644
--- a/StreamChatSwiftUITests/Tests/Utils/ViewFactory_Tests.swift
+++ b/StreamChatSwiftUITests/Tests/Utils/ViewFactory_Tests.swift
@@ -968,6 +968,38 @@ class ViewFactory_Tests: StreamChatTestCase {
         // Then
         XCTAssert(view is ChannelAvatarView)
     }
+    
+    func test_viewFactory_makeGalleryView() {
+        // Given
+        let viewFactory = DefaultViewFactory.shared
+        
+        // When
+        let view = viewFactory.makeGalleryView(
+            mediaAttachments: [],
+            message: .mock(),
+            isShown: .constant(true),
+            options: .init(selectedIndex: 0)
+        )
+            
+        // Then
+        XCTAssert(view is GalleryView)
+    }
+    
+    func test_viewFactory_makeVideoPlayerView() {
+        // Given
+        let viewFactory = DefaultViewFactory.shared
+        
+        // When
+        let view = viewFactory.makeVideoPlayerView(
+            attachment: .mock(id: .unique),
+            message: .mock(),
+            isShown: .constant(true),
+            options: .init(selectedIndex: 0)
+        )
+            
+        // Then
+        XCTAssert(view is VideoPlayerView)
+    }
 }
 
 extension ChannelAction: Equatable {