Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -4382,22 +4382,17 @@ public final class io/getstream/chat/android/compose/viewmodel/messages/Attachme
public static final field $stable I
public fun <init> (Lio/getstream/chat/android/ui/common/helper/internal/AttachmentStorageHelper;Lkotlinx/coroutines/flow/StateFlow;Landroidx/lifecycle/SavedStateHandle;)V
public synthetic fun <init> (Lio/getstream/chat/android/ui/common/helper/internal/AttachmentStorageHelper;Lkotlinx/coroutines/flow/StateFlow;Landroidx/lifecycle/SavedStateHandle;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun clearSelection ()V
public final fun deselectAttachment (Lio/getstream/chat/android/models/Attachment;)V
public final fun getAttachments ()Ljava/util/List;
public final fun getAttachmentsFromMetadata (Ljava/util/List;)Ljava/util/List;
public final fun getChannel ()Lio/getstream/chat/android/models/Channel;
public final fun getPickerMode ()Lio/getstream/chat/android/compose/state/messages/attachments/AttachmentPickerMode;
public final fun getSelectedAttachments ()Ljava/util/List;
public final fun getSubmittedAttachments ()Lkotlinx/coroutines/flow/Flow;
public final fun isPickerVisible ()Z
public final fun loadAttachments ()V
public final fun resolveAndSubmitUris (Ljava/util/List;)V
public final fun setPickerMode (Lio/getstream/chat/android/compose/state/messages/attachments/AttachmentPickerMode;)V
public final fun setPickerVisible (Z)V
public final fun togglePickerVisibility ()V
public final fun toggleSelection (Lio/getstream/chat/android/compose/state/messages/attachments/AttachmentPickerItemState;Z)V
public static synthetic fun toggleSelection$default (Lio/getstream/chat/android/compose/viewmodel/messages/AttachmentsPickerViewModel;Lio/getstream/chat/android/compose/state/messages/attachments/AttachmentPickerItemState;ZILjava/lang/Object;)V
}

public final class io/getstream/chat/android/compose/viewmodel/messages/AudioPlayerViewModel : androidx/lifecycle/ViewModel {
Expand All @@ -4414,13 +4409,15 @@ public final class io/getstream/chat/android/compose/viewmodel/messages/AudioPla

public final class io/getstream/chat/android/compose/viewmodel/messages/MessageComposerViewModel : androidx/lifecycle/ViewModel {
public static final field $stable I
public fun <init> (Lio/getstream/chat/android/ui/common/feature/messages/composer/MessageComposerController;Lio/getstream/chat/android/ui/common/helper/internal/AttachmentStorageHelper;)V
public final fun addSelectedAttachments (Ljava/util/List;)V
public fun <init> (Lio/getstream/chat/android/ui/common/feature/messages/composer/MessageComposerController;Lio/getstream/chat/android/ui/common/helper/internal/AttachmentStorageHelper;Landroidx/lifecycle/SavedStateHandle;)V
public synthetic fun <init> (Lio/getstream/chat/android/ui/common/feature/messages/composer/MessageComposerController;Lio/getstream/chat/android/ui/common/helper/internal/AttachmentStorageHelper;Landroidx/lifecycle/SavedStateHandle;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun addAttachments (Ljava/util/List;)V
public final fun buildNewMessage (Ljava/lang/String;Ljava/util/List;)Lio/getstream/chat/android/models/Message;
public static synthetic fun buildNewMessage$default (Lio/getstream/chat/android/compose/viewmodel/messages/MessageComposerViewModel;Ljava/lang/String;Ljava/util/List;ILjava/lang/Object;)Lio/getstream/chat/android/models/Message;
public final fun cancelLinkPreview ()V
public final fun cancelRecording ()V
public final fun clearActiveCommand ()V
public final fun clearAttachments ()V
public final fun clearData ()V
public final fun completeRecording ()V
public final fun createPoll (Lio/getstream/chat/android/models/PollConfig;)V
Expand All @@ -4441,7 +4438,7 @@ public final class io/getstream/chat/android/compose/viewmodel/messages/MessageC
public final fun lockRecording ()V
public final fun pauseRecording ()V
public final fun performMessageAction (Lio/getstream/chat/android/ui/common/state/messages/MessageAction;)V
public final fun removeSelectedAttachment (Lio/getstream/chat/android/models/Attachment;)V
public final fun removeAttachment (Lio/getstream/chat/android/models/Attachment;)V
public final fun seekRecordingTo (F)V
public final fun selectCommand (Lio/getstream/chat/android/models/Command;)V
public final fun selectMention (Lio/getstream/chat/android/models/User;)V
Expand All @@ -4457,7 +4454,6 @@ public final class io/getstream/chat/android/compose/viewmodel/messages/MessageC
public final fun stopRecording ()V
public final fun toggleCommandsVisibility ()V
public final fun toggleRecordingPlayback ()V
public final fun updateSelectedAttachments (Ljava/util/List;)V
}

public final class io/getstream/chat/android/compose/viewmodel/messages/MessageListViewModel : androidx/lifecycle/ViewModel {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ import io.getstream.chat.android.models.Message
import io.getstream.chat.android.models.ReactionSorting
import io.getstream.chat.android.models.ReactionSortingByFirstReactionAt
import io.getstream.chat.android.models.User
import io.getstream.chat.android.ui.common.helper.internal.AttachmentStorageHelper.Companion.EXTRA_SOURCE_URI
import io.getstream.chat.android.ui.common.state.messages.Delete
import io.getstream.chat.android.ui.common.state.messages.Edit
import io.getstream.chat.android.ui.common.state.messages.Flag
Expand Down Expand Up @@ -350,8 +351,10 @@ internal fun DefaultBottomBarContent(
isAttachmentPickerVisible = attachmentsPickerViewModel.isPickerVisible,
onAttachmentsClick = attachmentsPickerViewModel::togglePickerVisibility,
onAttachmentRemoved = { attachment ->
composerViewModel.removeSelectedAttachment(attachment)
attachmentsPickerViewModel.deselectAttachment(attachment)
attachment.extraData[EXTRA_SOURCE_URI]
?.let { it as? String }
?.let(attachmentsPickerViewModel::removeFromSelection)
composerViewModel.removeAttachment(attachment)
},
onCancelAction = {
listViewModel.dismissAllMessageActions()
Expand All @@ -367,6 +370,7 @@ internal fun DefaultBottomBarContent(
skipEnrichUrl = skipEnrichUrl,
),
)
composerViewModel.clearAttachments()
},
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,11 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.testTag
import io.getstream.chat.android.compose.R
import io.getstream.chat.android.compose.state.messages.attachments.AttachmentPickerMode
import io.getstream.chat.android.compose.state.messages.attachments.CameraPickerMode
import io.getstream.chat.android.compose.state.messages.attachments.FilePickerMode
import io.getstream.chat.android.compose.state.messages.attachments.GalleryPickerMode
import io.getstream.chat.android.compose.state.messages.attachments.PollPickerMode
import io.getstream.chat.android.compose.ui.theme.ChatTheme
import io.getstream.chat.android.compose.viewmodel.messages.AttachmentsPickerViewModel
import io.getstream.chat.android.ui.common.state.messages.MessageMode
Expand Down Expand Up @@ -70,7 +66,9 @@ public fun AttachmentPicker(
attachmentsPickerViewModel: AttachmentsPickerViewModel,
modifier: Modifier = Modifier,
messageMode: MessageMode = MessageMode.Normal,
actions: AttachmentPickerActions = AttachmentPickerActions.pickerDefaults(attachmentsPickerViewModel),
actions: AttachmentPickerActions = remember(attachmentsPickerViewModel) {
AttachmentPickerActions.pickerDefaults(attachmentsPickerViewModel)
},
) {
BackHandler(onBack = actions.onDismiss)

Expand Down Expand Up @@ -130,17 +128,3 @@ public fun AttachmentPicker(
}
}
}

/**
* A helper property to check if the current [AttachmentPickerMode] supports multiple selections.
*
* This will return:
* - `true` or `false` for [FilePickerMode] and [GalleryPickerMode], based on their `allowMultipleSelection` property.
* - `null` for other modes like [CameraPickerMode] or [PollPickerMode] that do not support this concept.
*/
internal val AttachmentPickerMode.allowMultipleSelection: Boolean?
get() = when (this) {
is FilePickerMode -> allowMultipleSelection
is GalleryPickerMode -> allowMultipleSelection
else -> null
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package io.getstream.chat.android.compose.ui.messages.attachments

import androidx.compose.runtime.Stable
import io.getstream.chat.android.compose.state.messages.attachments.AttachmentPickerItemState
import io.getstream.chat.android.compose.viewmodel.messages.AttachmentsPickerViewModel
import io.getstream.chat.android.compose.viewmodel.messages.MessageComposerViewModel
Expand All @@ -27,10 +28,11 @@ import io.getstream.chat.android.models.PollConfig
* Actions that can be performed in the attachment picker.
*
* Each property maps a user gesture or lifecycle event to a handler.
* Override individual actions via [copy] to customise behaviour while keeping the rest at their defaults.
* To customise individual actions, construct a new instance overriding only the properties you need,
* using the companion object factories as a starting point.
*
* @property onAttachmentItemSelected Called when a user taps an attachment item to select or deselect it
* inside the in-app picker grid.
* inside the in-app attachment browser.
* @property onAttachmentsSelected Called when attachments are confirmed and should be added to the composer.
* Receives the list of [Attachment] objects ready to be sent. Triggered by system pickers, camera, and
* file browser results.
Expand All @@ -42,6 +44,7 @@ import io.getstream.chat.android.models.PollConfig
* @property onCommandSelected Called when the user selects a slash command from the command picker.
* @property onDismiss Called when the attachment picker should be dismissed (back press, outside tap, etc.).
*/
@Stable
public data class AttachmentPickerActions(
val onAttachmentItemSelected: (AttachmentPickerItemState) -> Unit,
val onAttachmentsSelected: (List<Attachment>) -> Unit,
Expand Down Expand Up @@ -69,19 +72,18 @@ public data class AttachmentPickerActions(
/**
* Lightweight defaults suitable for standalone [AttachmentPicker] usage without a composer.
*
* Handles picker-level concerns only: toggling selection state and dismissing the picker.
* Poll, command, and attachment-submission actions are no-ops.
* Handles picker-level concerns only: toggling the selection index and dismissing the
* picker. Attachment submission, poll, and command actions are no-ops.
*
* @param attachmentsPickerViewModel The [AttachmentsPickerViewModel] that drives picker state.
*/
public fun pickerDefaults(
attachmentsPickerViewModel: AttachmentsPickerViewModel,
): AttachmentPickerActions = AttachmentPickerActions(
onAttachmentItemSelected = { item ->
val multiSelect = attachmentsPickerViewModel.pickerMode?.allowMultipleSelection == true
attachmentsPickerViewModel.toggleSelection(item, multiSelect)
handlePickerItemSelection(item, attachmentsPickerViewModel)
},
onAttachmentsSelected = { attachmentsPickerViewModel.setPickerVisible(visible = false) },
onAttachmentsSelected = {},
onCreatePollClick = {},
onCreatePoll = {},
onCreatePollDismissed = {},
Expand All @@ -92,36 +94,72 @@ public data class AttachmentPickerActions(
/**
* Default implementation wiring both the picker and composer view models.
*
* Handles attachment selection, poll creation, command insertion, and picker dismissal.
* Handles item selection, attachment submission, poll creation, command selection, and dismissal.
* Use this when the [AttachmentPicker] is paired with a [MessageComposerViewModel].
*
* @param attachmentsPickerViewModel The [AttachmentsPickerViewModel] that drives picker state.
* @param composerViewModel The [MessageComposerViewModel] that receives selected attachments
* and handles poll creation and command insertion.
* @param composerViewModel The [MessageComposerViewModel] that manages the attachment list for the message.
*/
public fun defaultActions(
attachmentsPickerViewModel: AttachmentsPickerViewModel,
composerViewModel: MessageComposerViewModel,
): AttachmentPickerActions = AttachmentPickerActions(
onAttachmentItemSelected = { item ->
val multiSelect = attachmentsPickerViewModel.pickerMode?.allowMultipleSelection == true
attachmentsPickerViewModel.toggleSelection(item, multiSelect)
composerViewModel.updateSelectedAttachments(attachmentsPickerViewModel.getSelectedAttachments())
},
onAttachmentsSelected = { attachments ->
attachmentsPickerViewModel.setPickerVisible(visible = false)
composerViewModel.addSelectedAttachments(attachments)
handleItemSelection(item, attachmentsPickerViewModel, composerViewModel)
},
onAttachmentsSelected = composerViewModel::addAttachments,
onCreatePollClick = {},
onCreatePoll = { pollConfig ->
attachmentsPickerViewModel.setPickerVisible(visible = false)
consumePickerSession(attachmentsPickerViewModel, composerViewModel)
composerViewModel.createPoll(pollConfig)
},
onCreatePollDismissed = {},
onCommandSelected = { command ->
attachmentsPickerViewModel.setPickerVisible(visible = false)
consumePickerSession(attachmentsPickerViewModel, composerViewModel)
composerViewModel.selectCommand(command)
},
onDismiss = { attachmentsPickerViewModel.setPickerVisible(visible = false) },
)
}
}

private fun handlePickerItemSelection(
item: AttachmentPickerItemState,
pickerViewModel: AttachmentsPickerViewModel,
) {
val uriString = item.attachmentMetaData.uri?.toString() ?: return
if (item.isSelected) {
pickerViewModel.removeFromSelection(uriString)
} else {
pickerViewModel.selectItem(uriString)
}
}

private fun handleItemSelection(
item: AttachmentPickerItemState,
pickerViewModel: AttachmentsPickerViewModel,
composerViewModel: MessageComposerViewModel,
) {
val uriString = item.attachmentMetaData.uri?.toString() ?: return
if (item.isSelected) {
pickerViewModel.removeFromSelection(uriString)
composerViewModel.removeAttachmentsByUris(setOf(uriString))
} else {
val attachment = pickerViewModel
.getAttachmentsFromMetadata(listOf(item.attachmentMetaData))
.firstOrNull() ?: return
val replaced = pickerViewModel.selectItem(uriString)
composerViewModel.removeAttachmentsByUris(replaced)
composerViewModel.addAttachments(listOf(attachment))
}
}

private fun consumePickerSession(
pickerViewModel: AttachmentsPickerViewModel,
composerViewModel: MessageComposerViewModel,
) {
// Polls and commands are mutually exclusive with file attachments — reset both.
pickerViewModel.setPickerVisible(visible = false)
pickerViewModel.clearSelection()
composerViewModel.clearAttachments()
}
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,9 @@ internal fun AttachmentTypePicker(
trailingContent()
}
LaunchedEffect(modes) {
modes.firstOrNull()?.let(onModeSelected)
if (selectedMode == null) {
modes.firstOrNull()?.let(onModeSelected)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ public fun MessageComposer(
onSendMessage: (Message) -> Unit = { viewModel.sendMessage(it) },
onAttachmentsClick: () -> Unit = {},
onValueChange: (String) -> Unit = { viewModel.setMessageInput(it) },
onAttachmentRemoved: (Attachment) -> Unit = { viewModel.removeSelectedAttachment(it) },
onAttachmentRemoved: (Attachment) -> Unit = { viewModel.removeAttachment(it) },
onCancelAction: () -> Unit = { viewModel.dismissMessageActions() },
onLinkPreviewClick: ((LinkPreview) -> Unit)? = null,
onCancelLinkPreviewClick: (() -> Unit)? = { viewModel.cancelLinkPreview() },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3874,7 +3874,7 @@ public interface ChatComponentFactory {
*
* Shows a row of buttons that launch system pickers (photo picker, file browser, camera).
* This variant does not require storage permissions since it uses system intents.
* Used when [ChatTheme.attachmentPickerConfig.useSystemPicker] is `true`.
* Used when [ChatTheme.config.attachmentPicker.useSystemPicker] is `true`.
*
* @param channel Used to check channel capabilities for filtering available modes.
* @param messageMode Used to filter modes (e.g., polls disabled in threads).
Expand Down
Loading
Loading