Skip to content

Implement commands attachment picker#1167

Open
martinmitrevski wants to merge 11 commits intov5from
commands-attachment-picker
Open

Implement commands attachment picker#1167
martinmitrevski wants to merge 11 commits intov5from
commands-attachment-picker

Conversation

@martinmitrevski
Copy link
Contributor

πŸ”— Issue Links

Resolves https://linear.app/stream/issue/IOS-1398/implement-commands-attachment-picker.

🎯 Goal

Describe why we are making this change.

πŸ“ Summary

Provide bullet points with the most important changes in the codebase.

πŸ›  Implementation

Provide a detailed description of the implementation and explain your decisions if you find them relevant.

🎨 Showcase

Add relevant screenshots and/or videos/gifs to easily see what this PR changes, if applicable.

Before After
img img

πŸ§ͺ Manual Testing Notes

Explain how this change can be tested manually, if applicable.

β˜‘οΈ Contributor Checklist

  • I have signed the Stream CLA (required)
  • This change should be manually QAed
  • Changelog is updated with client-facing changes
  • Changelog is updated with new localization keys
  • New code is covered by unit tests
  • Documentation has been updated in the docs-content repo

@martinmitrevski martinmitrevski requested a review from a team as a code owner February 4, 2026 16:52
@coderabbitai
Copy link

coderabbitai bot commented Feb 4, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

  • πŸ” Trigger a full review
✨ Finishing touches
πŸ§ͺ Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch commands-attachment-picker

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.

❀️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link

github-actions bot commented Feb 4, 2026

1 Message
πŸ“– There seems to be app changes but CHANGELOG wasn't modified.
Please include an entry if the PR includes user-facing changes.
You can find it at CHANGELOG.md.

Generated by 🚫 Danger

Co-authored-by: Stream Bot <ci@getstream.io>
Copy link
Member

@nuno-vieira nuno-vieira left a comment

Choose a reason for hiding this comment

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

Looks good overall, just a couple of comments πŸ‘

Comment on lines 60 to 97
private var commandItems: [CommandItem] {
let commandSymbol = utils.commandsConfig.instantCommandsSymbol
return [
CommandItem(
id: "\(commandSymbol)giphy",
displayInfo: CommandDisplayInfo(
displayName: L10n.Composer.Commands.giphy,
icon: images.commandGiphy,
format: "\(commandSymbol)giphy [\(L10n.Composer.Commands.Format.text)]",
isInstant: true
),
replacesMessageSent: false,
usesTintedIcon: false
),
CommandItem(
id: "\(commandSymbol)mute",
displayInfo: CommandDisplayInfo(
displayName: L10n.Composer.Commands.mute,
icon: UIImage(systemName: "speaker.slash") ?? images.commandMute,
format: "\(commandSymbol)mute [\(L10n.Composer.Commands.Format.username)]",
isInstant: true
),
replacesMessageSent: true,
usesTintedIcon: true
),
CommandItem(
id: "\(commandSymbol)unmute",
displayInfo: CommandDisplayInfo(
displayName: L10n.Composer.Commands.unmute,
icon: UIImage(systemName: "speaker.wave.1") ?? images.commandUnmute,
format: "\(commandSymbol)unmute [\(L10n.Composer.Commands.Format.username)]",
isInstant: true
),
replacesMessageSent: true,
usesTintedIcon: true
)
]
}
Copy link
Member

Choose a reason for hiding this comment

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

The customer won't be able to easily change this for something else. It would be nice if this came from a View Model or something that can be overridden. It is also not using the channel config commands and capabilities

Copy link
Contributor Author

Choose a reason for hiding this comment

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

updated, it now uses the same config from the composer vm

Comment on lines 78 to 89
icon: UIImage(systemName: "speaker.slash") ?? images.commandMute,
format: "\(commandSymbol)mute [\(L10n.Composer.Commands.Format.username)]",
isInstant: true
),
replacesMessageSent: true,
usesTintedIcon: true
),
CommandItem(
id: "\(commandSymbol)unmute",
displayInfo: CommandDisplayInfo(
displayName: L10n.Composer.Commands.unmute,
icon: UIImage(systemName: "speaker.wave.1") ?? images.commandUnmute,
Copy link
Member

Choose a reason for hiding this comment

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

These 2 images are hard-coded and not coming from images config

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed

Comment on lines 151 to 156
private struct CommandItem: Identifiable {
let id: String
let displayInfo: CommandDisplayInfo
let replacesMessageSent: Bool
let usesTintedIcon: Bool
}
Copy link
Member

Choose a reason for hiding this comment

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

Any reason why this is private?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

not really, updated it

@Stream-SDK-Bot
Copy link
Collaborator

SDK Size

title v5 branch diff status
StreamChatSwiftUI 10.06 MB 10.09 MB +31 KB 🟒

@Stream-SDK-Bot
Copy link
Collaborator

StreamChatSwiftUI XCSize

Object Diff (bytes)
AttachmentCommandsPickerView.o +18043
AttachmentPickerView.o +3175
MessageComposerView.o +2182
AttachmentViewFactoryOptions.o +1013
MessageComposerViewModel.o +712
Show 14 more objects
Object Diff (bytes)
DefaultViewFactory.o +544
ChatChannelExtensions.o +435
AsyncTask.o +320
ChatChannelInfoViewModel.o +298
ChatChannelListItem.o -214
MessageListHelperViews.o -204
DeletedMessageView.o -172
VoiceRecordingContainerView.o -128
ViewFactory.o +112
FileAttachmentView.o +112
ShareButtonView.o -64
ActionBannerView.o +60
StreamChatCommonUI_-2A9222176197F8FB_PackageProduct +60
AsyncPipelineTask.o +58

@github-actions
Copy link

github-actions bot commented Feb 6, 2026

Public Interface

+ public struct AttachmentCommandsPickerView: View  
+ 
+   public var body: some View
+   
+ 
+   public init(instantCommands: [CommandHandler] = [],onCommandSelected: @escaping @MainActor (ComposerCommand) -> Void)

+ public final class AttachmentCommandsPickerViewOptions: Sendable  
+ 
+   public let instantCommands: [CommandHandler]
+   public let onCommandSelected: @MainActor (ComposerCommand) -> Void
+   
+ 
+   public init(instantCommands: [CommandHandler] = [],onCommandSelected: @escaping @MainActor (ComposerCommand) -> Void)



 public class InstantCommandsHandler: CommandHandler  
-   
+   public let commands: [CommandHandler]
- 
+   
-   public init(commands: [CommandHandler],symbol: String = "/",id: String = "instantCommands")
+ 
-   
+   public init(commands: [CommandHandler],symbol: String = "/",id: String = "instantCommands")
- 
+   
-   public func canHandleCommand(in text: String,caretLocation: Int)-> ComposerCommand?
+ 
-   public func commandHandler(for command: ComposerCommand)-> CommandHandler?
+   public func canHandleCommand(in text: String,caretLocation: Int)-> ComposerCommand?
-   public func showSuggestions(for command: ComposerCommand)-> Future<SuggestionInfo, Error>
+   public func commandHandler(for command: ComposerCommand)-> CommandHandler?
-   public func handleCommand(for text: Binding<String>,selectedRangeLocation: Binding<Int>,command: Binding<ComposerCommand?>,extraData: [String: Any])
+   public func showSuggestions(for command: ComposerCommand)-> Future<SuggestionInfo, Error>
-   public func executeOnMessageSent(composerCommand: ComposerCommand,completion: @escaping @MainActor (Error?) -> Void)
+   public func handleCommand(for text: Binding<String>,selectedRangeLocation: Binding<Int>,command: Binding<ComposerCommand?>,extraData: [String: Any])
-   public func canBeExecuted(composerCommand: ComposerCommand)-> Bool
+   public func executeOnMessageSent(composerCommand: ComposerCommand,completion: @escaping @MainActor (Error?) -> Void)
+   public func canBeExecuted(composerCommand: ComposerCommand)-> Bool

 public struct AttachmentPickerView: View  
-   public init(viewFactory: Factory,selectedPickerState: Binding<AttachmentPickerState>,filePickerShown: Binding<Bool>,cameraPickerShown: Binding<Bool>,addedFileURLs: Binding<[URL]>,onPickerStateChange: @escaping @MainActor (AttachmentPickerState) -> Void,photoLibraryAssets: PHFetchResult<PHAsset>? = nil,onAssetTap: @escaping @MainActor (AddedAsset) -> Void,onCustomAttachmentTap: @escaping @MainActor (CustomAttachment) -> Void,isAssetSelected: @escaping @MainActor (String) -> Bool,addedCustomAttachments: [CustomAttachment],cameraImageAdded: @escaping @MainActor (AddedAsset) -> Void,askForAssetsAccessPermissions: @escaping () -> Void,isDisplayed: Bool,height: CGFloat,selectedAssetIds: [String]? = nil,channelController: ChatChannelController,messageController: ChatMessageController?,canSendPoll: Bool)
+   public init(viewFactory: Factory,selectedPickerState: Binding<AttachmentPickerState>,filePickerShown: Binding<Bool>,cameraPickerShown: Binding<Bool>,addedFileURLs: Binding<[URL]>,onPickerStateChange: @escaping @MainActor (AttachmentPickerState) -> Void,photoLibraryAssets: PHFetchResult<PHAsset>? = nil,onAssetTap: @escaping @MainActor (AddedAsset) -> Void,onCustomAttachmentTap: @escaping @MainActor (CustomAttachment) -> Void,isAssetSelected: @escaping @MainActor (String) -> Bool,addedCustomAttachments: [CustomAttachment],cameraImageAdded: @escaping @MainActor (AddedAsset) -> Void,askForAssetsAccessPermissions: @escaping () -> Void,isDisplayed: Bool,height: CGFloat,selectedAssetIds: [String]? = nil,channelController: ChatChannelController,messageController: ChatMessageController?,canSendPoll: Bool,instantCommands: [CommandHandler],onCommandSelected: @escaping @MainActor (ComposerCommand) -> Void)

 @MainActor open class MessageComposerViewModel: ObservableObject  
-   public var mentionedUsers
+   public var instantCommands: [CommandHandler]
-   public var isSendMessageEnabled: Bool
+   public var mentionedUsers
-   public var sendButtonEnabled: Bool
+   public var isSendMessageEnabled: Bool
-   public var sendInChannelShown: Bool
+   public var sendButtonEnabled: Bool
-   public var isDirectChannel: Bool
+   public var sendInChannelShown: Bool
-   public var showCommandsOverlay: Bool
+   public var isDirectChannel: Bool
-   public var inputComposerShouldScroll: Bool
+   public var showCommandsOverlay: Bool
-   
+   public var inputComposerShouldScroll: 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 fillEditedMessage(_ editedMessage: ChatMessage?)
+ 
-   public func fillDraftMessage()
+   public func fillEditedMessage(_ editedMessage: ChatMessage?)
-   public func updateDraftMessage(quotedMessage: ChatMessage?,isSilent: Bool = false,extraData: [String: RawJSON] = [:])
+   public func fillDraftMessage()
-   public func deleteDraftMessage()
+   public func updateDraftMessage(quotedMessage: ChatMessage?,isSilent: Bool = false,extraData: [String: RawJSON] = [:])
-   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 deleteDraftMessage()
-   public func change(pickerState: AttachmentPickerState)
+   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 imageTapped(_ addedAsset: AddedAsset)
+   public func change(pickerState: AttachmentPickerState)
-   public func imagePasted(_ image: UIImage)
+   public func imageTapped(_ addedAsset: AddedAsset)
-   public func removeAttachment(with id: String)
+   public func imagePasted(_ image: UIImage)
-   public func cameraImageAdded(_ image: AddedAsset)
+   public func removeAttachment(with id: String)
-   public func isImageSelected(with id: String)-> Bool
+   public func cameraImageAdded(_ image: AddedAsset)
-   public func customAttachmentTapped(_ attachment: CustomAttachment)
+   public func isImageSelected(with id: String)-> Bool
-   public func isCustomAttachmentSelected(_ attachment: CustomAttachment)-> Bool
+   public func customAttachmentTapped(_ attachment: CustomAttachment)
-   public func askForPhotosPermission()
+   public func isCustomAttachmentSelected(_ attachment: CustomAttachment)-> Bool
-   public func handleCommand(for text: Binding<String>,selectedRangeLocation: Binding<Int>,command: Binding<ComposerCommand?>,extraData: [String: Any])
+   public func askForPhotosPermission()
-   open func convertAddedAssetsToPayloads()throws -> [AnyAttachmentPayload]
+   public func handleCommand(for text: Binding<String>,selectedRangeLocation: Binding<Int>,command: Binding<ComposerCommand?>,extraData: [String: Any])
-   public func checkForMentionedUsers(commandId: String?,extraData: [String: Any])
+   open func convertAddedAssetsToPayloads()throws -> [AnyAttachmentPayload]
-   public func clearRemovedMentions()
+   public func checkForMentionedUsers(commandId: String?,extraData: [String: Any])
-   public func clearInputData()
+   public func clearRemovedMentions()
-   public func checkChannelCooldown()
+   public func clearInputData()
-   public func updateAddedAssets(_ assets: [AddedAsset])
+   public func checkChannelCooldown()
+   public func updateAddedAssets(_ assets: [AddedAsset])

 public final class AttachmentPickerViewOptions: Sendable  
-   
+   public let instantCommands: [CommandHandler]
- 
+   public let onCommandSelected: @MainActor (ComposerCommand) -> Void
-   public init(attachmentPickerState: Binding<AttachmentPickerState>,filePickerShown: Binding<Bool>,cameraPickerShown: Binding<Bool>,addedFileURLs: Binding<[URL]>,onPickerStateChange: @escaping @MainActor (AttachmentPickerState) -> Void,photoLibraryAssets: PHFetchResult<PHAsset>?,onAssetTap: @escaping @MainActor (AddedAsset) -> Void,onCustomAttachmentTap: @escaping @MainActor (CustomAttachment) -> Void,isAssetSelected: @escaping @MainActor (String) -> Bool,addedCustomAttachments: [CustomAttachment],cameraImageAdded: @escaping @MainActor (AddedAsset) -> Void,askForAssetsAccessPermissions: @escaping @MainActor () -> Void,isDisplayed: Bool,height: CGFloat,popupHeight: CGFloat,selectedAssetIds: [String]? = nil,channelController: ChatChannelController,messageController: ChatMessageController?,canSendPoll: Bool)
+   
+ 
+   public init(attachmentPickerState: Binding<AttachmentPickerState>,filePickerShown: Binding<Bool>,cameraPickerShown: Binding<Bool>,addedFileURLs: Binding<[URL]>,onPickerStateChange: @escaping @MainActor (AttachmentPickerState) -> Void,photoLibraryAssets: PHFetchResult<PHAsset>?,onAssetTap: @escaping @MainActor (AddedAsset) -> Void,onCustomAttachmentTap: @escaping @MainActor (CustomAttachment) -> Void,isAssetSelected: @escaping @MainActor (String) -> Bool,addedCustomAttachments: [CustomAttachment],cameraImageAdded: @escaping @MainActor (AddedAsset) -> Void,askForAssetsAccessPermissions: @escaping @MainActor () -> Void,isDisplayed: Bool,height: CGFloat,popupHeight: CGFloat,selectedAssetIds: [String]? = nil,channelController: ChatChannelController,messageController: ChatMessageController?,canSendPoll: Bool,instantCommands: [CommandHandler],onCommandSelected: @escaping @MainActor (ComposerCommand) -> Void)

 public class CommandsHandler: CommandHandler  
-   public let id: String
+   public let commands: [CommandHandler]
-   public var displayInfo: CommandDisplayInfo?
+   public let id: String
-   
+   public var displayInfo: CommandDisplayInfo?
- 
+   
-   public init(commands: [CommandHandler])
+ 
-   
+   public init(commands: [CommandHandler])
- 
+   
-   public func canHandleCommand(in text: String,caretLocation: Int)-> ComposerCommand?
+ 
-   public func commandHandler(for command: ComposerCommand)-> CommandHandler?
+   public func canHandleCommand(in text: String,caretLocation: Int)-> ComposerCommand?
-   public func showSuggestions(for command: ComposerCommand)-> Future<SuggestionInfo, Error>
+   public func commandHandler(for command: ComposerCommand)-> CommandHandler?
-   public func handleCommand(for text: Binding<String>,selectedRangeLocation: Binding<Int>,command: Binding<ComposerCommand?>,extraData: [String: Any])
+   public func showSuggestions(for command: ComposerCommand)-> Future<SuggestionInfo, Error>
-   public func executeOnMessageSent(composerCommand: ComposerCommand,completion: @escaping @MainActor (Error?) -> Void)
+   public func handleCommand(for text: Binding<String>,selectedRangeLocation: Binding<Int>,command: Binding<ComposerCommand?>,extraData: [String: Any])
-   public func canBeExecuted(composerCommand: ComposerCommand)-> Bool
+   public func executeOnMessageSent(composerCommand: ComposerCommand,completion: @escaping @MainActor (Error?) -> Void)
+   public func canBeExecuted(composerCommand: ComposerCommand)-> Bool

 public enum AttachmentPickerState: Sendable  
-   case custom
+   case commands
+   case custom

 extension ViewFactory  
-   public func makeVoiceRecordingView(options: VoiceRecordingViewOptions)-> some View
+   public func makeAttachmentCommandsPickerView(options: AttachmentCommandsPickerViewOptions)-> some View
-   public func makeCustomAttachmentView(options: CustomComposerAttachmentViewOptions)-> some View
+   public func makeVoiceRecordingView(options: VoiceRecordingViewOptions)-> some View
-   public func makeCustomAttachmentPreviewView(options: CustomAttachmentPreviewViewOptions)-> some View
+   public func makeCustomAttachmentView(options: CustomComposerAttachmentViewOptions)-> some View
-   public func makeAttachmentSourcePickerView(options: AttachmentSourcePickerViewOptions)-> some View
+   public func makeCustomAttachmentPreviewView(options: CustomAttachmentPreviewViewOptions)-> some View
-   public func makePhotoAttachmentPickerView(options: PhotoAttachmentPickerViewOptions)-> some View
+   public func makeAttachmentSourcePickerView(options: AttachmentSourcePickerViewOptions)-> some View
-   public func makeFilePickerView(options: FilePickerViewOptions)-> some View
+   public func makePhotoAttachmentPickerView(options: PhotoAttachmentPickerViewOptions)-> some View
-   public func makeCameraPickerView(options: CameraPickerViewOptions)-> some View
+   public func makeFilePickerView(options: FilePickerViewOptions)-> some View
-   public func makeAssetsAccessPermissionView(options: AssetsAccessPermissionViewOptions)-> some View
+   public func makeCameraPickerView(options: CameraPickerViewOptions)-> some View
-   public func makeSendInChannelView(options: SendInChannelViewOptions)-> some View
+   public func makeAssetsAccessPermissionView(options: AssetsAccessPermissionViewOptions)-> some View
-   public func makeMessageActionsView(options: MessageActionsViewOptions)-> some View
+   public func makeSendInChannelView(options: SendInChannelViewOptions)-> some View
-   public func makeReactionsUsersView(options: ReactionsUsersViewOptions)-> some View
+   public func makeMessageActionsView(options: MessageActionsViewOptions)-> some View
-   public func makeBottomReactionsView(options: ReactionsBottomViewOptions)-> some View
+   public func makeReactionsUsersView(options: ReactionsUsersViewOptions)-> 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 makeComposerQuotedMessageView(options: ComposerQuotedMessageViewOptions)-> some View
+   public func makeMoreReactionsView(options: MoreReactionsViewOptions)-> 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 makeQuotedMessageAttachmentPreviewView(options: QuotedMessageAttachmentPreviewViewOptions)-> some View
+   public func makeQuotedMessageView(options: QuotedMessageViewOptions)-> some View
-   public func makeCommandsContainerView(options: CommandsContainerViewOptions)-> some View
+   public func makeQuotedMessageAttachmentPreviewView(options: QuotedMessageAttachmentPreviewViewOptions)-> some View
-   public func makeMessageReadIndicatorView(options: MessageReadIndicatorViewOptions)-> some View
+   public func makeCommandsContainerView(options: CommandsContainerViewOptions)-> 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 makeThreadsListErrorBannerView(options: ThreadListErrorBannerViewOptions)-> some View
+   public func makeNoThreadsView(options: NoThreadsViewOptions)-> some View
-   public func makeThreadListLoadingView(options: ThreadListLoadingViewOptions)-> some View
+   public func makeThreadsListErrorBannerView(options: ThreadListErrorBannerViewOptions)-> 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

- @MainActor public protocol CommandHandler
+ @MainActor public protocol CommandHandler: Sendable

@sonarqubecloud
Copy link

sonarqubecloud bot commented Feb 6, 2026

Quality Gate Failed Quality Gate failed

Failed conditions
76.7% Coverage on New Code (required β‰₯ 80%)

See analysis details on SonarQube Cloud

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants