v5: [Composer Redesign] Voice Recording#1267
Conversation
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the βοΈ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
β¨ Finishing Touchesπ§ͺ Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
martinmitrevski
left a comment
There was a problem hiding this comment.
several AI slops, good to do self-review first. Also, the checks are failing.
...eamChatSwiftUI/ChatComposer/Attachments/VoiceRecording/ComposerVoiceRecordingInputView.swift
Outdated
Show resolved
Hide resolved
...eamChatSwiftUI/ChatComposer/Attachments/VoiceRecording/ComposerVoiceRecordingInputView.swift
Outdated
Show resolved
Hide resolved
...eamChatSwiftUI/ChatComposer/Attachments/VoiceRecording/ComposerVoiceRecordingInputView.swift
Show resolved
Hide resolved
...eamChatSwiftUI/ChatComposer/Attachments/VoiceRecording/ComposerVoiceRecordingInputView.swift
Show resolved
Hide resolved
...eamChatSwiftUI/ChatComposer/Attachments/VoiceRecording/ComposerVoiceRecordingInputView.swift
Show resolved
Hide resolved
Sources/StreamChatSwiftUI/ChatComposer/Attachments/ComposerVoiceRecordingAttachmentView.swift
Show resolved
Hide resolved
Sources/StreamChatSwiftUI/ChatComposer/MessageComposerView.swift
Outdated
Show resolved
Hide resolved
Sources/StreamChatSwiftUI/ChatMessageList/AsyncVoiceMessages/VoiceRecordingContainerView.swift
Show resolved
Hide resolved
|
|
||
| // MARK: - Slider Thumb | ||
|
|
||
| static func roundedSliderThumbImage() -> UIImage { |
There was a problem hiding this comment.
why do we need this?
There was a problem hiding this comment.
To have the figma slider in the audio waveform
| // MARK: - Swipe to Reply | ||
|
|
||
| /// Areas that should not trigger swipe-to-reply (e.g. waveform sliders). | ||
| struct SwipeToReplyExcludedFrameKey: PreferenceKey { |
There was a problem hiding this comment.
why do we need this?
There was a problem hiding this comment.
Without this is not possible to scrub the audio in the message list, it will trigger the swipe to reply
Generated by π« Danger |
martinmitrevski
left a comment
There was a problem hiding this comment.
looks good, just 2 things from my side and then we're good
| @Injected(\.utils) private var utils | ||
| @Injected(\.tokens) private var tokens | ||
|
|
||
| var viewModel: MessageComposerViewModel |
There was a problem hiding this comment.
don't we need an @ObservedObject here?
| /// Options for creating the composer input view. | ||
| public final class ComposerInputViewOptions: Sendable { | ||
| /// The view model for the message composer. | ||
| public let viewModel: MessageComposerViewModel |
There was a problem hiding this comment.
maybe also good to revisit if we can avoid sending the whole VM, but just the properties it needs? Since everything else in this class is pointless with the VM - they are all coming from it already.
Public Interface+ public enum VoiceRecordingState: Equatable, Sendable
+
+ case initial
+ case recording
+ case locked
+ case stopped
+ public struct PlayPauseButton: View
+
+ public var body: some View
+
+
+ public init(isPlaying: Bool,onTap: @escaping () -> Void)
+ public struct ComposerVoiceRecordingContainerView: View
+
+ public var body: some View
+
+
+ public init(addedVoiceRecordings: [AddedVoiceRecording],onDiscardAttachment: @escaping (String) -> Void)
+ public final class ComposerVoiceRecordingInputViewOptions: @unchecked Sendable
+
+ public let recordingState: VoiceRecordingState
+ public let audioRecordingInfo: AudioRecordingInfo
+ public let pendingAudioRecordingURL: URL?
+ public let gestureLocation: CGPoint
+ public let stopRecording: @MainActor () -> Void
+ public let confirmRecording: @MainActor () -> Void
+ public let discardRecording: @MainActor () -> Void
+ public let previewRecording: @MainActor () -> Void
+
+
+ public init(recordingState: VoiceRecordingState,audioRecordingInfo: AudioRecordingInfo,pendingAudioRecordingURL: URL?,gestureLocation: CGPoint,stopRecording: @escaping @MainActor () -> Void,confirmRecording: @escaping @MainActor () -> Void,discardRecording: @escaping @MainActor () -> Void,previewRecording: @escaping @MainActor () -> Void)
- public struct AddedVoiceRecordingsView: View
-
- public var body: some View
-
-
- public init(addedVoiceRecordings: [AddedVoiceRecording],onDiscardAttachment: @escaping (String) -> Void)
- public final class ComposerRecordingLockedViewOptions: Sendable
-
- public let viewModel: MessageComposerViewModel
-
-
- public init(viewModel: MessageComposerViewModel)
- public final class ComposerRecordingViewOptions: Sendable
-
- public let viewModel: MessageComposerViewModel
- public let gestureLocation: CGPoint
-
-
- public init(viewModel: MessageComposerViewModel,gestureLocation: CGPoint)
- public enum RecordingState: Equatable, Sendable
-
- case initial
- case showingTip
- case recording(CGPoint)
- case locked
- case stopped
- public final class ComposerRecordingTipViewOptions: Sendable
-
- public init()
extension ViewFactory
- public func makeComposerRecordingView(options: ComposerRecordingViewOptions)-> some View
+ public func makeComposerVoiceRecordingInputView(options: ComposerVoiceRecordingInputViewOptions)-> some View
- public func makeComposerRecordingLockedView(options: ComposerRecordingLockedViewOptions)-> some View
+ public func makeAttachmentPickerView(options: AttachmentPickerViewOptions)-> some View
- public func makeComposerRecordingTipView(options: ComposerRecordingTipViewOptions)-> some View
+ public func makeAttachmentCommandsPickerView(options: AttachmentCommandsPickerViewOptions)-> some View
- public func makeAttachmentPickerView(options: AttachmentPickerViewOptions)-> some View
+ public func makeVoiceRecordingView(options: VoiceRecordingViewOptions)-> some View
- public func makeAttachmentCommandsPickerView(options: AttachmentCommandsPickerViewOptions)-> some View
+ public func makeCustomAttachmentPickerView(options: CustomAttachmentPickerViewOptions)-> some View
- public func makeVoiceRecordingView(options: VoiceRecordingViewOptions)-> some View
+ public func makeCustomAttachmentPreviewView(options: CustomAttachmentPreviewViewOptions)-> some View
- public func makeCustomAttachmentPickerView(options: CustomAttachmentPickerViewOptions)-> some View
+ public func makeAttachmentTypePickerView(options: AttachmentTypePickerViewOptions)-> some View
- public func makeCustomAttachmentPreviewView(options: CustomAttachmentPreviewViewOptions)-> some View
+ public func makeAttachmentMediaPickerView(options: AttachmentMediaPickerViewOptions)-> some View
- public func makeAttachmentTypePickerView(options: AttachmentTypePickerViewOptions)-> some View
+ public func makeAttachmentFilePickerView(options: AttachmentFilePickerViewOptions)-> some View
- public func makeAttachmentMediaPickerView(options: AttachmentMediaPickerViewOptions)-> some View
+ public func makeAttachmentCameraPickerView(options: AttachmentCameraPickerViewOptions)-> some View
- public func makeAttachmentFilePickerView(options: AttachmentFilePickerViewOptions)-> some View
+ public func makeAttachmentPollPickerView(options: AttachmentPollPickerViewOptions)-> some View
- public func makeAttachmentCameraPickerView(options: AttachmentCameraPickerViewOptions)-> some View
+ public func makeSendInChannelView(options: SendInChannelViewOptions)-> some View
- public func makeAttachmentPollPickerView(options: AttachmentPollPickerViewOptions)-> some View
+ public func makeMessageActionsView(options: MessageActionsViewOptions)-> some View
- public func makeSendInChannelView(options: SendInChannelViewOptions)-> some View
+ public func makeBottomReactionsView(options: ReactionsBottomViewOptions)-> some View
- public func makeMessageActionsView(options: MessageActionsViewOptions)-> some View
+ public func makeMessageReactionView(options: MessageReactionViewOptions)-> some View
- public func makeBottomReactionsView(options: ReactionsBottomViewOptions)-> some View
+ public func makeReactionsOverlayView(options: ReactionsOverlayViewOptions)-> some View
- public func makeMessageReactionView(options: MessageReactionViewOptions)-> some View
+ public func makeReactionsContentView(options: ReactionsContentViewOptions)-> some View
- public func makeReactionsOverlayView(options: ReactionsOverlayViewOptions)-> some View
+ public func makeReactionsBackgroundView(options: ReactionsBackgroundOptions)-> some View
- public func makeReactionsContentView(options: ReactionsContentViewOptions)-> some View
+ public func makeMoreReactionsView(options: MoreReactionsViewOptions)-> some View
- public func makeReactionsBackgroundView(options: ReactionsBackgroundOptions)-> some View
+ public func makeReactionsDetailView(options: ReactionsDetailViewOptions)-> some View
- public func makeMoreReactionsView(options: MoreReactionsViewOptions)-> some View
+ public func makeComposerQuotedMessageView(options: ComposerQuotedMessageViewOptions)-> some View
- public func makeReactionsDetailView(options: ReactionsDetailViewOptions)-> some View
+ public func makeChatQuotedMessageView(options: ChatQuotedMessageViewOptions)-> some View
- public func makeComposerQuotedMessageView(options: ComposerQuotedMessageViewOptions)-> some View
+ public func makeQuotedMessageView(options: QuotedMessageViewOptions)-> some View
- public func makeChatQuotedMessageView(options: ChatQuotedMessageViewOptions)-> some View
+ public func makeComposerEditedMessageView(options: ComposerEditedMessageViewOptions)-> some View
- public func makeQuotedMessageView(options: QuotedMessageViewOptions)-> some View
+ public func makeMessageAttachmentPreviewThumbnailView(options: MessageAttachmentPreviewViewOptions)-> some View
- public func makeComposerEditedMessageView(options: ComposerEditedMessageViewOptions)-> some View
+ public func makeMessageAttachmentPreviewIconView(options: MessageAttachmentPreviewIconViewOptions)-> some View
- public func makeMessageAttachmentPreviewThumbnailView(options: MessageAttachmentPreviewViewOptions)-> some View
+ public func makeSuggestionsContainerView(options: SuggestionsContainerViewOptions)-> some View
- public func makeMessageAttachmentPreviewIconView(options: MessageAttachmentPreviewIconViewOptions)-> some View
+ public func makeMessageReadIndicatorView(options: MessageReadIndicatorViewOptions)-> some View
- public func makeSuggestionsContainerView(options: SuggestionsContainerViewOptions)-> some View
+ public func makeNewMessagesIndicatorView(options: NewMessagesIndicatorViewOptions)-> some View
- public func makeMessageReadIndicatorView(options: MessageReadIndicatorViewOptions)-> some View
+ public func makeJumpToUnreadButton(options: JumpToUnreadButtonOptions)-> some View
- public func makeNewMessagesIndicatorView(options: NewMessagesIndicatorViewOptions)-> some View
+ public func makeComposerPollView(options: ComposerPollViewOptions)-> some View
- public func makeJumpToUnreadButton(options: JumpToUnreadButtonOptions)-> some View
+ public func makePollView(options: PollViewOptions)-> some View
- public func makeComposerPollView(options: ComposerPollViewOptions)-> some View
+ public func makeThreadDestination(options: ThreadDestinationOptions)-> @MainActor (ChatThread) -> ChatChannelView<Self>
- public func makePollView(options: PollViewOptions)-> some View
+ public func makeThreadListItem(options: ThreadListItemOptions<ThreadDestination>)-> some View
- public func makeThreadDestination(options: ThreadDestinationOptions)-> @MainActor (ChatThread) -> ChatChannelView<Self>
+ public func makeNoThreadsView(options: NoThreadsViewOptions)-> some View
- public func makeThreadListItem(options: ThreadListItemOptions<ThreadDestination>)-> some View
+ public func makeThreadListLoadingView(options: ThreadListLoadingViewOptions)-> some View
- public func makeNoThreadsView(options: NoThreadsViewOptions)-> some View
+ public func makeThreadListContainerViewModifier(options: ThreadListContainerModifierOptions)-> some ViewModifier
- public func makeThreadListLoadingView(options: ThreadListLoadingViewOptions)-> some View
+ public func makeThreadListHeaderViewModifier(options: ThreadListHeaderViewModifierOptions)-> some ViewModifier
- public func makeThreadListContainerViewModifier(options: ThreadListContainerModifierOptions)-> some ViewModifier
+ public func makeThreadListHeaderView(options: ThreadListHeaderViewOptions)-> some View
- public func makeThreadListHeaderViewModifier(options: ThreadListHeaderViewModifierOptions)-> some ViewModifier
+ public func makeThreadListFooterView(options: ThreadListFooterViewOptions)-> some View
- public func makeThreadListHeaderView(options: ThreadListHeaderViewOptions)-> some View
+ public func makeThreadListBackground(options: ThreadListBackgroundOptions)-> some View
- public func makeThreadListFooterView(options: ThreadListFooterViewOptions)-> some View
+ public func makeThreadListItemBackground(options: ThreadListItemBackgroundOptions)-> some View
- public func makeThreadListBackground(options: ThreadListBackgroundOptions)-> some View
+ public func makeThreadListDividerItem(options: ThreadListDividerItemOptions)-> some View
- public func makeThreadListItemBackground(options: ThreadListItemBackgroundOptions)-> some View
+ public func makeAddUsersView(options: AddUsersViewOptions)-> some View
- public func makeThreadListDividerItem(options: ThreadListDividerItemOptions)-> some View
+ public func makeAttachmentTextView(options: AttachmentTextViewOptions)-> some View
- public func makeAddUsersView(options: AddUsersViewOptions)-> some View
- public func makeAttachmentTextView(options: AttachmentTextViewOptions)-> some View
- public final class ComposerInputViewOptions: Sendable
+ public final class ComposerInputViewOptions: @unchecked Sendable
- public let recordingState: Binding<RecordingState>
+ public let recordingState: Binding<VoiceRecordingState>
- public let composerAssets: [ComposerAsset]
+ public let recordingGestureLocation: Binding<CGPoint>
- public let addedCustomAttachments: [CustomAttachment]
+ public let composerAssets: [ComposerAsset]
- public let addedVoiceRecordings: [AddedVoiceRecording]
+ public let addedCustomAttachments: [CustomAttachment]
- public let quotedMessage: Binding<ChatMessage?>
+ public let addedVoiceRecordings: [AddedVoiceRecording]
- public let editedMessage: Binding<ChatMessage?>
+ public let quotedMessage: Binding<ChatMessage?>
- public let maxMessageLength: Int?
+ public let editedMessage: Binding<ChatMessage?>
- public let cooldownDuration: Int
+ public let maxMessageLength: Int?
- public let hasContent: Bool
+ public let cooldownDuration: Int
- public let canSendMessage: Bool
+ public let hasContent: Bool
- public let onCustomAttachmentTap: @MainActor (CustomAttachment) -> Void
+ public let canSendMessage: Bool
- public let removeAttachmentWithId: @MainActor (String) -> Void
+ public let audioRecordingInfo: AudioRecordingInfo
- public let sendMessage: @MainActor () -> Void
+ public let pendingAudioRecordingURL: URL?
- public let onImagePasted: @MainActor (UIImage) -> Void
+ public let onCustomAttachmentTap: @MainActor (CustomAttachment) -> Void
- public let startRecording: @MainActor () -> Void
+ public let removeAttachmentWithId: @MainActor (String) -> Void
- public let stopRecording: @MainActor () -> Void
+ public let sendMessage: @MainActor () -> Void
- public let sendInChannelShown: Bool
+ public let onImagePasted: @MainActor (UIImage) -> Void
- public let showReplyInChannel: Binding<Bool>
+ public let startRecording: @MainActor () -> Void
-
+ public let stopRecording: @MainActor () -> Void
-
+ public let confirmRecording: @MainActor () -> Void
- public init(channelController: ChatChannelController,text: Binding<String>,selectedRangeLocation: Binding<Int>,command: Binding<ComposerCommand?>,recordingState: Binding<RecordingState>,composerAssets: [ComposerAsset],addedCustomAttachments: [CustomAttachment],addedVoiceRecordings: [AddedVoiceRecording],quotedMessage: Binding<ChatMessage?>,editedMessage: Binding<ChatMessage?>,maxMessageLength: Int?,cooldownDuration: Int,hasContent: Bool,canSendMessage: Bool,onCustomAttachmentTap: @escaping @MainActor (CustomAttachment) -> Void,removeAttachmentWithId: @escaping @MainActor (String) -> Void,sendMessage: @escaping @MainActor () -> Void,onImagePasted: @escaping @MainActor (UIImage) -> Void,startRecording: @escaping @MainActor () -> Void,stopRecording: @escaping @MainActor () -> Void,sendInChannelShown: Bool,showReplyInChannel: Binding<Bool>)
+ public let discardRecording: @MainActor () -> Void
+ public let previewRecording: @MainActor () -> Void
+ public let showRecordingTip: @MainActor () -> Void
+ public let sendInChannelShown: Bool
+ public let showReplyInChannel: Binding<Bool>
+
+
+ public init(channelController: ChatChannelController,text: Binding<String>,selectedRangeLocation: Binding<Int>,command: Binding<ComposerCommand?>,recordingState: Binding<VoiceRecordingState>,recordingGestureLocation: Binding<CGPoint>,composerAssets: [ComposerAsset],addedCustomAttachments: [CustomAttachment],addedVoiceRecordings: [AddedVoiceRecording],quotedMessage: Binding<ChatMessage?>,editedMessage: Binding<ChatMessage?>,maxMessageLength: Int?,cooldownDuration: Int,hasContent: Bool,canSendMessage: Bool,audioRecordingInfo: AudioRecordingInfo,pendingAudioRecordingURL: URL?,onCustomAttachmentTap: @escaping @MainActor (CustomAttachment) -> Void,removeAttachmentWithId: @escaping @MainActor (String) -> Void,sendMessage: @escaping @MainActor () -> Void,onImagePasted: @escaping @MainActor (UIImage) -> Void,startRecording: @escaping @MainActor () -> Void,stopRecording: @escaping @MainActor () -> Void,confirmRecording: @escaping @MainActor () -> Void,discardRecording: @escaping @MainActor () -> Void,previewRecording: @escaping @MainActor () -> Void,showRecordingTip: @escaping @MainActor () -> Void,sendInChannelShown: Bool,showReplyInChannel: Binding<Bool>)
open class WaveformView: UIView
- public var duration: TimeInterval
+ public var isPlaying: Bool
- public var currentTime: TimeInterval
+ public var duration: TimeInterval
- public var waveform: [Float]
+ public var currentTime: TimeInterval
- public static let initial
+ public var waveform: [Float]
-
+ public static let initial
-
+
- public init(isRecording: Bool,duration: TimeInterval,currentTime: TimeInterval,waveform: [Float])
+
+ public init(isRecording: Bool,isPlaying: Bool = false,duration: TimeInterval,currentTime: TimeInterval,waveform: [Float])
@MainActor open class MessageComposerViewModel: ObservableObject
- @Published public var recordingState: RecordingState
+ @Published public var recordingState: VoiceRecordingState
- @Published public var audioRecordingInfo
+ @Published public var recordingGestureLocation: CGPoint
- @Published public var snackBarText: String?
+ @Published public var audioRecordingInfo
- public let channelController: ChatChannelController
+ @Published public var snackBarText: String?
- public var messageController: ChatMessageController?
+ public let channelController: ChatChannelController
- public let eventsController: EventsController
+ public var messageController: ChatMessageController?
- public var quotedMessage: Binding<ChatMessage?>?
+ public let eventsController: EventsController
- public var waveformTargetSamples: Int
+ public var quotedMessage: Binding<ChatMessage?>?
- public internal var pendingAudioRecording: AddedVoiceRecording?
+ public var waveformTargetSamples: Int
- public var canSendPoll: Bool
+ public internal var pendingAudioRecording: AddedVoiceRecording?
- public lazy var commandsHandler
+ public var canSendPoll: Bool
- public var instantCommands: [CommandHandler]
+ public lazy var commandsHandler
- public var mentionedUsers
+ public var instantCommands: [CommandHandler]
- public var canSendMessage: Bool
+ public var mentionedUsers
- public var hasContent: Bool
+ public var canSendMessage: Bool
- public var sendInChannelShown: Bool
+ public var hasContent: Bool
- public var isDirectChannel: Bool
+ public var shouldShowRecordingGestureOverlay: Bool
- public var showSuggestionsOverlay: Bool
+ public var sendInChannelShown: Bool
-
+ public var isDirectChannel: Bool
-
+ public var showSuggestionsOverlay: Bool
- public init(channelController: ChatChannelController,messageController: ChatMessageController?,eventsController: EventsController? = nil,quotedMessage: Binding<ChatMessage?>? = nil)
+
-
+
-
+ public init(channelController: ChatChannelController,messageController: ChatMessageController?,eventsController: EventsController? = nil,quotedMessage: Binding<ChatMessage?>? = nil)
- public func addFileURLs(_ urls: [URL])
+
- public func fillEditedMessage(_ editedMessage: ChatMessage?)
+
- public func fillDraftMessage()
+ public func addFileURLs(_ urls: [URL])
- public func updateDraftMessage(quotedMessage: ChatMessage?,isSilent: Bool = false,extraData: [String: RawJSON] = [:])
+ public func fillEditedMessage(_ editedMessage: ChatMessage?)
- public func deleteDraftMessage()
+ public func fillDraftMessage()
- open func sendMessage(quotedMessage: ChatMessage?,editedMessage: ChatMessage?,isSilent: Bool = false,skipPush: Bool = false,skipEnrichUrl: Bool = false,extraData: [String: RawJSON] = [:],completion: @escaping @MainActor () -> Void)
+ public func updateDraftMessage(quotedMessage: ChatMessage?,isSilent: Bool = false,extraData: [String: RawJSON] = [:])
- public func change(pickerState: AttachmentPickerState)
+ public func deleteDraftMessage()
- public func imageTapped(_ addedAsset: AddedAsset)
+ open func sendMessage(quotedMessage: ChatMessage?,editedMessage: ChatMessage?,isSilent: Bool = false,skipPush: Bool = false,skipEnrichUrl: Bool = false,extraData: [String: RawJSON] = [:],completion: @escaping @MainActor () -> Void)
- public func imagePasted(_ image: UIImage)
+ public func change(pickerState: AttachmentPickerState)
- public func removeAttachment(with id: String)
+ public func imageTapped(_ addedAsset: AddedAsset)
- public func cameraImageAdded(_ image: AddedAsset)
+ public func imagePasted(_ image: UIImage)
- public func isImageSelected(with id: String)-> Bool
+ public func removeAttachment(with id: String)
- public func customAttachmentTapped(_ attachment: CustomAttachment)
+ public func cameraImageAdded(_ image: AddedAsset)
- public func isCustomAttachmentSelected(_ attachment: CustomAttachment)-> Bool
+ public func isImageSelected(with id: String)-> Bool
- public func askForPhotosPermission()
+ public func customAttachmentTapped(_ attachment: CustomAttachment)
- public func handleCommand(for text: Binding<String>,selectedRangeLocation: Binding<Int>,command: Binding<ComposerCommand?>,extraData: [String: Any])
+ public func isCustomAttachmentSelected(_ attachment: CustomAttachment)-> Bool
- open func convertAddedAssetsToPayloads()throws -> [AnyAttachmentPayload]
+ public func askForPhotosPermission()
- public func checkForMentionedUsers(commandId: String?,extraData: [String: Any])
+ public func handleCommand(for text: Binding<String>,selectedRangeLocation: Binding<Int>,command: Binding<ComposerCommand?>,extraData: [String: Any])
- public func clearRemovedMentions()
+ open func convertAddedAssetsToPayloads()throws -> [AnyAttachmentPayload]
- public func clearInputData()
+ public func checkForMentionedUsers(commandId: String?,extraData: [String: Any])
- public func checkChannelCooldown()
+ public func clearRemovedMentions()
- public func updateAddedAssets(_ assets: [AddedAsset])
+ public func clearInputData()
+ public func checkChannelCooldown()
+ public func updateAddedAssets(_ assets: [AddedAsset])
public final class ComposerInputTrailingViewOptions: @unchecked Sendable
- @Binding public var recordingState: RecordingState
+ @Binding public var recordingState: VoiceRecordingState
- public let sendMessage: @MainActor () -> Void
+ public let showRecordingTip: @MainActor () -> Void
-
+ public let sendMessage: @MainActor () -> Void
-
+
- public init(text: Binding<String>,recordingState: Binding<RecordingState>,composerInputState: MessageComposerInputState,startRecording: @escaping @MainActor () -> Void,stopRecording: @escaping @MainActor () -> Void,sendMessage: @escaping @MainActor () -> Void)
+
+ public init(text: Binding<String>,recordingState: Binding<VoiceRecordingState>,composerInputState: MessageComposerInputState,startRecording: @escaping @MainActor () -> Void,stopRecording: @escaping @MainActor () -> Void,showRecordingTip: @escaping @MainActor () -> Void,sendMessage: @escaping @MainActor () -> Void)
public struct ComposerInputView: View, KeyboardReadable
- public init(factory: Factory,channelController: ChatChannelController,text: Binding<String>,selectedRangeLocation: Binding<Int>,command: Binding<ComposerCommand?>,recordingState: Binding<RecordingState>,composerAssets: [ComposerAsset],addedCustomAttachments: [CustomAttachment],addedVoiceRecordings: [AddedVoiceRecording],quotedMessage: Binding<ChatMessage?>,editedMessage: Binding<ChatMessage?>,maxMessageLength: Int? = nil,cooldownDuration: Int,hasContent: Bool,canSendMessage: Bool,onCustomAttachmentTap: @escaping @MainActor (CustomAttachment) -> Void,removeAttachmentWithId: @escaping (String) -> Void,sendMessage: @escaping @MainActor () -> Void,onImagePasted: @escaping @MainActor (UIImage) -> Void,startRecording: @escaping @MainActor () -> Void,stopRecording: @escaping @MainActor () -> Void,sendInChannelShown: Bool,showReplyInChannel: Binding<Bool>)
+ public init(factory: Factory,channelController: ChatChannelController,text: Binding<String>,selectedRangeLocation: Binding<Int>,command: Binding<ComposerCommand?>,recordingState: Binding<VoiceRecordingState>,recordingGestureLocation: Binding<CGPoint>,composerAssets: [ComposerAsset],addedCustomAttachments: [CustomAttachment],addedVoiceRecordings: [AddedVoiceRecording],quotedMessage: Binding<ChatMessage?>,editedMessage: Binding<ChatMessage?>,maxMessageLength: Int? = nil,cooldownDuration: Int,hasContent: Bool,canSendMessage: Bool,audioRecordingInfo: AudioRecordingInfo,pendingAudioRecordingURL: URL?,onCustomAttachmentTap: @escaping @MainActor (CustomAttachment) -> Void,removeAttachmentWithId: @escaping (String) -> Void,sendMessage: @escaping @MainActor () -> Void,onImagePasted: @escaping @MainActor (UIImage) -> Void,startRecording: @escaping @MainActor () -> Void,stopRecording: @escaping @MainActor () -> Void,confirmRecording: @escaping @MainActor () -> Void,discardRecording: @escaping @MainActor () -> Void,previewRecording: @escaping @MainActor () -> Void,showRecordingTip: @escaping @MainActor () -> Void,sendInChannelShown: Bool,showReplyInChannel: Binding<Bool>) |
SDK Size
|
|
StreamChatSwiftUI XCSize
Show 46 more objects
|


π Issue Links
IOS-1385
π― Goal
Redesign the voice recording flow in the message composer to match the new v5 design system.
π Summary
ViewFactory.makeConfirmEditButtonfor the publish/confirm recording action for consistency and customizabilitytogglePlayback,cycleRate,seek,isPlaying,rateTitle) intoVoiceRecordingHandlerto reduce duplication across viewsAssertSnapshotwith all variantsImportant
The images added/referenced in this PR (e.g. SF Symbols used via
Image(systemName:)) still need to be moved to the common module (StreamChat).π Implementation
Voice Recording Input (active recording):
ComposerVoiceRecordingInputViewhandles both the active recording UI and the locked/stopped UI with seamless transitions.VoiceRecordingGestureOverlaycaptures drag gestures on the composer to start/stop recording and detect lock/cancel gestures.LockViewis a floating capsule/circle that morphs based on drag progress.Voice Recording Attachment (added to composer):
ComposerVoiceRecordingAttachmentViewfollows the same naming pattern asComposerFileAttachmentView,ComposerImageAttachmentView, etc.ComposerVoiceRecordingContainerViewwraps aVStackof attachment cards, matching the container pattern ofComposerAttachmentsContainerView.Shared playback logic:
VoiceRecordingHandler(already theAudioPlayingDelegate) now also ownsisPlaying,rate, and exposestogglePlayback(for:),cycleRate(),seek(to:loadingFrom:),updatePlaybackState(for:), andisActive(for:). This eliminated duplicated logic inComposerVoiceRecordingInputView,ComposerVoiceRecordingAttachmentView, andVoiceRecordingView.Waveform views:
RecordingWaveform(used during active recording) andWaveformViewSwiftUI(used for completed recordings) are bothUIViewRepresentablewrappers aroundWaveformViewand now live together inWaveformView.swift. Both share a custom slider thumb viaWaveformView.applyCustomSliderThumb().π¨ Showcase
Simulator.Screen.Recording.-.iPhone.17.Pro.-.2026-03-05.at.01.37.13.mov
π§ͺ Manual Testing Notes
βοΈ Contributor Checklist
docs-contentrepo