diff --git a/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/pages/MessageListPage.kt b/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/pages/MessageListPage.kt index eaafdc60337..f9b9134e7e5 100644 --- a/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/pages/MessageListPage.kt +++ b/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/pages/MessageListPage.kt @@ -72,12 +72,12 @@ open class MessageListPage { val attachmentsButton get() = By.res("Stream_ComposerAttachmentsButton") val quotedMessage get() = By.res("Stream_QuotedMessage") val cancelEditButton get() = By.res("Stream_ComposerCancelEditButton") - val attachmentCancelIcon get() = By.res("Stream_AttachmentCancelIcon") - val columnWithMultipleFileAttachments get() = By.res("Stream_FileAttachmentPreviewContent") - val columnWithMultipleMediaAttachments get() = By.res("Stream_MediaAttachmentPreviewContent") - val mediaAttachment get() = By.res("Stream_MediaAttachmentPreviewItem") - val fileSize get() = By.res("Stream_FileSizeInPreview") - val fileName get() = By.res("Stream_FileNameInPreview") + val attachmentCancelIcon get() = By.res("Stream_MessageComposerAttachmentCancelIcon") + val columnWithMultipleFileAttachments get() = By.res("Stream_MessageComposerAttachments") + val columnWithMultipleMediaAttachments get() = By.res("Stream_MessageComposerAttachments") + val mediaAttachment get() = By.res("Stream_MessageComposerAttachmentMediaItem") + val fileSize get() = By.res("Stream_MessageComposerAttachmentFileSize") + val fileName get() = By.res("Stream_MessageComposerAttachmentFileName") val fileImage = MessageList.Message.fileImage val linkPreviewImage get() = By.res("Stream_LinkPreviewImage") val linkPreviewTitle get() = By.res("Stream_LinkPreviewTitle") diff --git a/stream-chat-android-compose/api/stream-chat-android-compose.api b/stream-chat-android-compose/api/stream-chat-android-compose.api index 30fbe678ec9..438487113ef 100644 --- a/stream-chat-android-compose/api/stream-chat-android-compose.api +++ b/stream-chat-android-compose/api/stream-chat-android-compose.api @@ -462,60 +462,11 @@ public final class io/getstream/chat/android/compose/state/userreactions/UserRea public fun toString ()Ljava/lang/String; } -public class io/getstream/chat/android/compose/ui/attachments/AttachmentFactory { - public static final field $stable I - public fun (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function5;Lkotlin/jvm/functions/Function1;Lio/getstream/chat/android/compose/ui/attachments/AttachmentFactory$Type;)V - public synthetic fun (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function5;Lkotlin/jvm/functions/Function1;Lio/getstream/chat/android/compose/ui/attachments/AttachmentFactory$Type;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun getCanHandle ()Lkotlin/jvm/functions/Function1; - public final fun getPreviewContent ()Lkotlin/jvm/functions/Function5; - public final fun getTextFormatter ()Lkotlin/jvm/functions/Function1; - public final fun getType ()Lio/getstream/chat/android/compose/ui/attachments/AttachmentFactory$Type; -} - -public abstract interface class io/getstream/chat/android/compose/ui/attachments/AttachmentFactory$Type { -} - -public final class io/getstream/chat/android/compose/ui/attachments/AttachmentFactory$Type$BuiltIn : java/lang/Enum, io/getstream/chat/android/compose/ui/attachments/AttachmentFactory$Type { - public static final field AUDIO_RECORD Lio/getstream/chat/android/compose/ui/attachments/AttachmentFactory$Type$BuiltIn; - public static final field FILE Lio/getstream/chat/android/compose/ui/attachments/AttachmentFactory$Type$BuiltIn; - public static final field GIPHY Lio/getstream/chat/android/compose/ui/attachments/AttachmentFactory$Type$BuiltIn; - public static final field LINK Lio/getstream/chat/android/compose/ui/attachments/AttachmentFactory$Type$BuiltIn; - public static final field MEDIA Lio/getstream/chat/android/compose/ui/attachments/AttachmentFactory$Type$BuiltIn; - public static final field QUOTED Lio/getstream/chat/android/compose/ui/attachments/AttachmentFactory$Type$BuiltIn; - public static final field UNSUPPORTED Lio/getstream/chat/android/compose/ui/attachments/AttachmentFactory$Type$BuiltIn; - public static final field UPLOAD Lio/getstream/chat/android/compose/ui/attachments/AttachmentFactory$Type$BuiltIn; - public static fun getEntries ()Lkotlin/enums/EnumEntries; - public static fun valueOf (Ljava/lang/String;)Lio/getstream/chat/android/compose/ui/attachments/AttachmentFactory$Type$BuiltIn; - public static fun values ()[Lio/getstream/chat/android/compose/ui/attachments/AttachmentFactory$Type$BuiltIn; -} - -public final class io/getstream/chat/android/compose/ui/attachments/AttachmentFactory$Type$None : io/getstream/chat/android/compose/ui/attachments/AttachmentFactory$Type { - public static final field $stable I - public static final field INSTANCE Lio/getstream/chat/android/compose/ui/attachments/AttachmentFactory$Type$None; - public fun equals (Ljava/lang/Object;)Z - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class io/getstream/chat/android/compose/ui/attachments/StreamAttachmentFactories { - public static final field $stable I - public static final field INSTANCE Lio/getstream/chat/android/compose/ui/attachments/StreamAttachmentFactories; - public final fun defaultFactories (Lkotlin/jvm/functions/Function0;ILio/getstream/chat/android/ui/common/utils/GiphyInfoType;Lio/getstream/chat/android/ui/common/utils/GiphySizingMode;Landroidx/compose/ui/layout/ContentScale;ZLkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function8;Ljava/util/List;)Ljava/util/List; - public static synthetic fun defaultFactories$default (Lio/getstream/chat/android/compose/ui/attachments/StreamAttachmentFactories;Lkotlin/jvm/functions/Function0;ILio/getstream/chat/android/ui/common/utils/GiphyInfoType;Lio/getstream/chat/android/ui/common/utils/GiphySizingMode;Landroidx/compose/ui/layout/ContentScale;ZLkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function8;Ljava/util/List;ILjava/lang/Object;)Ljava/util/List; - public final fun defaults (Lkotlin/jvm/functions/Function0;ILio/getstream/chat/android/ui/common/utils/GiphyInfoType;Lio/getstream/chat/android/ui/common/utils/GiphySizingMode;Landroidx/compose/ui/layout/ContentScale;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Ljava/util/List;)Ljava/util/List; - public static synthetic fun defaults$default (Lio/getstream/chat/android/compose/ui/attachments/StreamAttachmentFactories;Lkotlin/jvm/functions/Function0;ILio/getstream/chat/android/ui/common/utils/GiphyInfoType;Lio/getstream/chat/android/ui/common/utils/GiphySizingMode;Landroidx/compose/ui/layout/ContentScale;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Ljava/util/List;ILjava/lang/Object;)Ljava/util/List; -} - public final class io/getstream/chat/android/compose/ui/attachments/content/AudioRecordAttachmentContentKt { public static final fun AudioRecordAttachmentContent (Landroidx/compose/ui/Modifier;Lio/getstream/chat/android/compose/state/messages/attachments/AttachmentState;Lio/getstream/chat/android/compose/viewmodel/messages/AudioPlayerViewModelFactory;Landroidx/compose/runtime/Composer;II)V public static final fun AudioRecordAttachmentContentItem (Landroidx/compose/ui/Modifier;Lio/getstream/chat/android/models/Attachment;Lio/getstream/chat/android/ui/common/state/messages/list/AudioPlayerState;ZLkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;II)V } -public final class io/getstream/chat/android/compose/ui/attachments/content/AudioRecordAttachmentPreviewContentKt { - public static final fun AudioRecordAttachmentPreviewContent (Ljava/util/List;Lkotlin/jvm/functions/Function1;Landroidx/compose/ui/Modifier;Lio/getstream/chat/android/compose/viewmodel/messages/AudioPlayerViewModelFactory;Landroidx/compose/runtime/Composer;II)V - public static final fun AudioRecordAttachmentPreviewContentItem (Landroidx/compose/ui/Modifier;Lio/getstream/chat/android/models/Attachment;Lio/getstream/chat/android/ui/common/state/messages/list/AudioPlayerState;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V -} - public final class io/getstream/chat/android/compose/ui/attachments/content/ComposableSingletons$AudioRecordAttachmentContentKt { public static final field INSTANCE Lio/getstream/chat/android/compose/ui/attachments/content/ComposableSingletons$AudioRecordAttachmentContentKt; public static field lambda-1 Lkotlin/jvm/functions/Function2; @@ -538,13 +489,6 @@ public final class io/getstream/chat/android/compose/ui/attachments/content/Comp public final fun getLambda-3$stream_chat_android_compose_release ()Lkotlin/jvm/functions/Function2; } -public final class io/getstream/chat/android/compose/ui/attachments/content/ComposableSingletons$FileAttachmentPreviewContentKt { - public static final field INSTANCE Lio/getstream/chat/android/compose/ui/attachments/content/ComposableSingletons$FileAttachmentPreviewContentKt; - public static field lambda-1 Lkotlin/jvm/functions/Function2; - public fun ()V - public final fun getLambda-1$stream_chat_android_compose_release ()Lkotlin/jvm/functions/Function2; -} - public final class io/getstream/chat/android/compose/ui/attachments/content/ComposableSingletons$GiphyAttachmentContentKt { public static final field INSTANCE Lio/getstream/chat/android/compose/ui/attachments/content/ComposableSingletons$GiphyAttachmentContentKt; public static field lambda-1 Lkotlin/jvm/functions/Function2; @@ -554,15 +498,6 @@ public final class io/getstream/chat/android/compose/ui/attachments/content/Comp public final fun getLambda-2$stream_chat_android_compose_release ()Lkotlin/jvm/functions/Function2; } -public final class io/getstream/chat/android/compose/ui/attachments/content/ComposableSingletons$ImageAttachmentPreviewContentKt { - public static final field INSTANCE Lio/getstream/chat/android/compose/ui/attachments/content/ComposableSingletons$ImageAttachmentPreviewContentKt; - public static field lambda-1 Lkotlin/jvm/functions/Function2; - public static field lambda-2 Lkotlin/jvm/functions/Function2; - public fun ()V - public final fun getLambda-1$stream_chat_android_compose_release ()Lkotlin/jvm/functions/Function2; - public final fun getLambda-2$stream_chat_android_compose_release ()Lkotlin/jvm/functions/Function2; -} - public final class io/getstream/chat/android/compose/ui/attachments/content/ComposableSingletons$LinkAttachmentContentKt { public static final field INSTANCE Lio/getstream/chat/android/compose/ui/attachments/content/ComposableSingletons$LinkAttachmentContentKt; public static field lambda-1 Lkotlin/jvm/functions/Function2; @@ -589,21 +524,6 @@ public final class io/getstream/chat/android/compose/ui/attachments/content/Comp public final fun getLambda-6$stream_chat_android_compose_release ()Lkotlin/jvm/functions/Function2; } -public final class io/getstream/chat/android/compose/ui/attachments/content/ComposableSingletons$MediaAttachmentPreviewContentKt { - public static final field INSTANCE Lio/getstream/chat/android/compose/ui/attachments/content/ComposableSingletons$MediaAttachmentPreviewContentKt; - public static field lambda-1 Lkotlin/jvm/functions/Function3; - public static field lambda-2 Lkotlin/jvm/functions/Function2; - public static field lambda-3 Lkotlin/jvm/functions/Function3; - public static field lambda-4 Lkotlin/jvm/functions/Function3; - public static field lambda-5 Lkotlin/jvm/functions/Function2; - public fun ()V - public final fun getLambda-1$stream_chat_android_compose_release ()Lkotlin/jvm/functions/Function3; - public final fun getLambda-2$stream_chat_android_compose_release ()Lkotlin/jvm/functions/Function2; - public final fun getLambda-3$stream_chat_android_compose_release ()Lkotlin/jvm/functions/Function3; - public final fun getLambda-4$stream_chat_android_compose_release ()Lkotlin/jvm/functions/Function3; - public final fun getLambda-5$stream_chat_android_compose_release ()Lkotlin/jvm/functions/Function2; -} - public final class io/getstream/chat/android/compose/ui/attachments/content/ComposableSingletons$UnsupportedAttachmentContentKt { public static final field INSTANCE Lio/getstream/chat/android/compose/ui/attachments/content/ComposableSingletons$UnsupportedAttachmentContentKt; public static field lambda-1 Lkotlin/jvm/functions/Function2; @@ -617,10 +537,6 @@ public final class io/getstream/chat/android/compose/ui/attachments/content/File public static final fun FileAttachmentItem (Lio/getstream/chat/android/models/Attachment;ZLkotlin/jvm/functions/Function1;Landroidx/compose/ui/Modifier;Landroidx/compose/runtime/Composer;II)V } -public final class io/getstream/chat/android/compose/ui/attachments/content/FileAttachmentPreviewContentKt { - public static final fun FileAttachmentPreviewContent (Ljava/util/List;Lkotlin/jvm/functions/Function1;Landroidx/compose/ui/Modifier;Landroidx/compose/runtime/Composer;II)V -} - public final class io/getstream/chat/android/compose/ui/attachments/content/GiphyAttachmentClickData { public static final field $stable I public final fun component1 ()Landroid/content/Context; @@ -641,10 +557,6 @@ public final class io/getstream/chat/android/compose/ui/attachments/content/Giph public static final fun GiphyAttachmentContent (Lio/getstream/chat/android/compose/state/messages/attachments/AttachmentState;Landroidx/compose/ui/Modifier;Lio/getstream/chat/android/ui/common/utils/GiphyInfoType;Lio/getstream/chat/android/ui/common/utils/GiphySizingMode;Landroidx/compose/ui/layout/ContentScale;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;II)V } -public final class io/getstream/chat/android/compose/ui/attachments/content/ImageAttachmentPreviewContentKt { - public static final fun ImageAttachmentPreviewContent (Ljava/util/List;Lkotlin/jvm/functions/Function1;Landroidx/compose/ui/Modifier;Landroidx/compose/runtime/Composer;II)V -} - public final class io/getstream/chat/android/compose/ui/attachments/content/LinkAttachmentClickData { public static final field $stable I public final fun component1 ()Landroid/content/Context; @@ -692,72 +604,10 @@ public final class io/getstream/chat/android/compose/ui/attachments/content/Medi public static final fun MediaAttachmentContent (Lio/getstream/chat/android/compose/state/messages/attachments/AttachmentState;Landroidx/compose/ui/Modifier;IZLkotlin/jvm/functions/Function8;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;II)V } -public final class io/getstream/chat/android/compose/ui/attachments/content/MediaAttachmentPreviewContentKt { - public static final fun MediaAttachmentPreviewContent (Ljava/util/List;Lkotlin/jvm/functions/Function1;Landroidx/compose/ui/Modifier;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;II)V -} - public final class io/getstream/chat/android/compose/ui/attachments/content/UnsupportedAttachmentContentKt { public static final fun UnsupportedAttachmentContent (Landroidx/compose/ui/Modifier;Landroidx/compose/runtime/Composer;II)V } -public final class io/getstream/chat/android/compose/ui/attachments/factory/AudioRecordAttachmentFactory : io/getstream/chat/android/compose/ui/attachments/AttachmentFactory { - public static final field $stable I - public fun (Lio/getstream/chat/android/compose/viewmodel/messages/AudioPlayerViewModelFactory;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;)V - public synthetic fun (Lio/getstream/chat/android/compose/viewmodel/messages/AudioPlayerViewModelFactory;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V -} - -public final class io/getstream/chat/android/compose/ui/attachments/factory/ComposableSingletons$FileAttachmentFactoryKt { - public static final field INSTANCE Lio/getstream/chat/android/compose/ui/attachments/factory/ComposableSingletons$FileAttachmentFactoryKt; - public static field lambda-1 Lkotlin/jvm/functions/Function5; - public fun ()V - public final fun getLambda-1$stream_chat_android_compose_release ()Lkotlin/jvm/functions/Function5; -} - -public final class io/getstream/chat/android/compose/ui/attachments/factory/ComposableSingletons$MediaAttachmentFactoryKt { - public static final field INSTANCE Lio/getstream/chat/android/compose/ui/attachments/factory/ComposableSingletons$MediaAttachmentFactoryKt; - public static field lambda-1 Lkotlin/jvm/functions/Function3; - public static field lambda-2 Lkotlin/jvm/functions/Function3; - public fun ()V - public final fun getLambda-1$stream_chat_android_compose_release ()Lkotlin/jvm/functions/Function3; - public final fun getLambda-2$stream_chat_android_compose_release ()Lkotlin/jvm/functions/Function3; -} - -public final class io/getstream/chat/android/compose/ui/attachments/factory/FileAttachmentFactory : io/getstream/chat/android/compose/ui/attachments/AttachmentFactory { - public static final field $stable I - public fun ()V - public fun (Lkotlin/jvm/functions/Function1;)V - public synthetic fun (Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V -} - -public final class io/getstream/chat/android/compose/ui/attachments/factory/GiphyAttachmentFactory : io/getstream/chat/android/compose/ui/attachments/AttachmentFactory { - public static final field $stable I - public fun ()V - public fun (Lio/getstream/chat/android/ui/common/utils/GiphyInfoType;Lio/getstream/chat/android/ui/common/utils/GiphySizingMode;Landroidx/compose/ui/layout/ContentScale;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)V - public synthetic fun (Lio/getstream/chat/android/ui/common/utils/GiphyInfoType;Lio/getstream/chat/android/ui/common/utils/GiphySizingMode;Landroidx/compose/ui/layout/ContentScale;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun (Lio/getstream/chat/android/ui/common/utils/GiphyInfoType;Lio/getstream/chat/android/ui/common/utils/GiphySizingMode;Landroidx/compose/ui/layout/ContentScale;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;)V - public synthetic fun (Lio/getstream/chat/android/ui/common/utils/GiphyInfoType;Lio/getstream/chat/android/ui/common/utils/GiphySizingMode;Landroidx/compose/ui/layout/ContentScale;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V -} - -public final class io/getstream/chat/android/compose/ui/attachments/factory/LinkAttachmentFactory : io/getstream/chat/android/compose/ui/attachments/AttachmentFactory { - public static final field $stable I - public fun (ILkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)V - public synthetic fun (ILkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun (ILkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;)V - public synthetic fun (ILkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V -} - -public final class io/getstream/chat/android/compose/ui/attachments/factory/MediaAttachmentFactory : io/getstream/chat/android/compose/ui/attachments/AttachmentFactory { - public static final field $stable I - public fun ()V - public fun (IZLkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;)V - public synthetic fun (IZLkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;ILkotlin/jvm/internal/DefaultConstructorMarker;)V -} - -public final class io/getstream/chat/android/compose/ui/attachments/factory/UnsupportedAttachmentFactory : io/getstream/chat/android/compose/ui/attachments/AttachmentFactory { - public static final field $stable I - public static final field INSTANCE Lio/getstream/chat/android/compose/ui/attachments/factory/UnsupportedAttachmentFactory; -} - public final class io/getstream/chat/android/compose/ui/attachments/preview/ComposableSingletons$MediaGalleryPreviewScreenKt { public static final field INSTANCE Lio/getstream/chat/android/compose/ui/attachments/preview/ComposableSingletons$MediaGalleryPreviewScreenKt; public static field lambda-1 Lkotlin/jvm/functions/Function6; @@ -1579,6 +1429,15 @@ public final class io/getstream/chat/android/compose/ui/components/common/Compos public final fun getLambda-1$stream_chat_android_compose_release ()Lkotlin/jvm/functions/Function2; } +public final class io/getstream/chat/android/compose/ui/components/common/ComposableSingletons$MediaBadgesKt { + public static final field INSTANCE Lio/getstream/chat/android/compose/ui/components/common/ComposableSingletons$MediaBadgesKt; + public static field lambda-1 Lkotlin/jvm/functions/Function2; + public static field lambda-2 Lkotlin/jvm/functions/Function2; + public fun ()V + public final fun getLambda-1$stream_chat_android_compose_release ()Lkotlin/jvm/functions/Function2; + public final fun getLambda-2$stream_chat_android_compose_release ()Lkotlin/jvm/functions/Function2; +} + public final class io/getstream/chat/android/compose/ui/components/common/ComposableSingletons$PlayButtonKt { public static final field INSTANCE Lio/getstream/chat/android/compose/ui/components/common/ComposableSingletons$PlayButtonKt; public static field lambda-1 Lkotlin/jvm/functions/Function2; @@ -1586,6 +1445,13 @@ public final class io/getstream/chat/android/compose/ui/components/common/Compos public final fun getLambda-1$stream_chat_android_compose_release ()Lkotlin/jvm/functions/Function2; } +public final class io/getstream/chat/android/compose/ui/components/common/ComposableSingletons$PlaybackSpeedToggleKt { + public static final field INSTANCE Lio/getstream/chat/android/compose/ui/components/common/ComposableSingletons$PlaybackSpeedToggleKt; + public static field lambda-1 Lkotlin/jvm/functions/Function2; + public fun ()V + public final fun getLambda-1$stream_chat_android_compose_release ()Lkotlin/jvm/functions/Function2; +} + public final class io/getstream/chat/android/compose/ui/components/common/ComposableSingletons$RadioControlsKt { public static final field INSTANCE Lio/getstream/chat/android/compose/ui/components/common/ComposableSingletons$RadioControlsKt; public static field lambda-1 Lkotlin/jvm/functions/Function2; @@ -2442,6 +2308,40 @@ public final class io/getstream/chat/android/compose/ui/messages/composer/intern public final fun getLambda-2$stream_chat_android_compose_release ()Lkotlin/jvm/functions/Function2; } +public final class io/getstream/chat/android/compose/ui/messages/composer/internal/attachments/ComposableSingletons$MessageComposerAttachmentAudioRecordItemKt { + public static final field INSTANCE Lio/getstream/chat/android/compose/ui/messages/composer/internal/attachments/ComposableSingletons$MessageComposerAttachmentAudioRecordItemKt; + public static field lambda-1 Lkotlin/jvm/functions/Function2; + public fun ()V + public final fun getLambda-1$stream_chat_android_compose_release ()Lkotlin/jvm/functions/Function2; +} + +public final class io/getstream/chat/android/compose/ui/messages/composer/internal/attachments/ComposableSingletons$MessageComposerAttachmentFileItemKt { + public static final field INSTANCE Lio/getstream/chat/android/compose/ui/messages/composer/internal/attachments/ComposableSingletons$MessageComposerAttachmentFileItemKt; + public static field lambda-1 Lkotlin/jvm/functions/Function2; + public fun ()V + public final fun getLambda-1$stream_chat_android_compose_release ()Lkotlin/jvm/functions/Function2; +} + +public final class io/getstream/chat/android/compose/ui/messages/composer/internal/attachments/ComposableSingletons$MessageComposerAttachmentMediaItemKt { + public static final field INSTANCE Lio/getstream/chat/android/compose/ui/messages/composer/internal/attachments/ComposableSingletons$MessageComposerAttachmentMediaItemKt; + public static field lambda-1 Lkotlin/jvm/functions/Function2; + public static field lambda-2 Lkotlin/jvm/functions/Function2; + public static field lambda-3 Lkotlin/jvm/functions/Function2; + public static field lambda-4 Lkotlin/jvm/functions/Function2; + public fun ()V + public final fun getLambda-1$stream_chat_android_compose_release ()Lkotlin/jvm/functions/Function2; + public final fun getLambda-2$stream_chat_android_compose_release ()Lkotlin/jvm/functions/Function2; + public final fun getLambda-3$stream_chat_android_compose_release ()Lkotlin/jvm/functions/Function2; + public final fun getLambda-4$stream_chat_android_compose_release ()Lkotlin/jvm/functions/Function2; +} + +public final class io/getstream/chat/android/compose/ui/messages/composer/internal/attachments/ComposableSingletons$MessageComposerAttachmentsKt { + public static final field INSTANCE Lio/getstream/chat/android/compose/ui/messages/composer/internal/attachments/ComposableSingletons$MessageComposerAttachmentsKt; + public static field lambda-1 Lkotlin/jvm/functions/Function2; + public fun ()V + public final fun getLambda-1$stream_chat_android_compose_release ()Lkotlin/jvm/functions/Function2; +} + public final class io/getstream/chat/android/compose/ui/messages/composer/internal/suggestions/ComposableSingletons$CommandSuggestionListKt { public static final field INSTANCE Lio/getstream/chat/android/compose/ui/messages/composer/internal/suggestions/ComposableSingletons$CommandSuggestionListKt; public static field lambda-1 Lkotlin/jvm/functions/Function3; @@ -2760,7 +2660,6 @@ public abstract interface class io/getstream/chat/android/compose/ui/theme/ChatC public abstract fun DirectChannelInfoTopBar (Lio/getstream/chat/android/ui/common/state/messages/list/ChannelHeaderViewState;Landroidx/compose/foundation/lazy/LazyListState;Lkotlin/jvm/functions/Function0;Landroidx/compose/runtime/Composer;I)V public abstract fun FileAttachmentContent (Lio/getstream/chat/android/compose/state/messages/attachments/AttachmentState;Landroidx/compose/ui/Modifier;Landroidx/compose/runtime/Composer;I)V public abstract fun FileAttachmentItem (Landroidx/compose/ui/Modifier;Lio/getstream/chat/android/models/Attachment;ZLkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;I)V - public abstract fun FileAttachmentPreviewContent (Landroidx/compose/ui/Modifier;Ljava/util/List;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;I)V public abstract fun GiphyAttachmentContent (Lio/getstream/chat/android/compose/state/messages/attachments/AttachmentState;Landroidx/compose/ui/Modifier;Landroidx/compose/runtime/Composer;I)V public abstract fun GroupChannelInfoAddMembersButton (Lkotlin/jvm/functions/Function0;Landroidx/compose/runtime/Composer;I)V public abstract fun GroupChannelInfoAvatarContainer (Lio/getstream/chat/android/models/Channel;Lio/getstream/chat/android/models/User;Lio/getstream/chat/android/ui/common/utils/ExpandableList;Landroidx/compose/runtime/Composer;I)V @@ -2780,6 +2679,10 @@ public abstract interface class io/getstream/chat/android/compose/ui/theme/ChatC public abstract fun MessageBottom (Landroidx/compose/foundation/layout/ColumnScope;Lio/getstream/chat/android/ui/common/state/messages/list/MessageItemState;Landroidx/compose/runtime/Composer;I)V public abstract fun MessageBubble-T042LqI (Landroidx/compose/ui/Modifier;Lio/getstream/chat/android/models/Message;JLandroidx/compose/ui/graphics/Shape;Landroidx/compose/foundation/BorderStroke;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;I)V public abstract fun MessageComposer (Lio/getstream/chat/android/ui/common/state/messages/composer/MessageComposerState;ZLkotlin/jvm/functions/Function2;Landroidx/compose/ui/Modifier;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lio/getstream/chat/android/compose/ui/messages/composer/actions/AudioRecordingActions;Lkotlin/jvm/functions/Function4;Landroidx/compose/runtime/Composer;II)V + public abstract fun MessageComposerAttachmentAudioRecordItem (Lio/getstream/chat/android/compose/ui/theme/MessageComposerAttachmentAudioRecordItemParams;Landroidx/compose/runtime/Composer;I)V + public abstract fun MessageComposerAttachmentFileItem (Lio/getstream/chat/android/compose/ui/theme/MessageComposerAttachmentFileItemParams;Landroidx/compose/runtime/Composer;I)V + public abstract fun MessageComposerAttachmentMediaItem (Lio/getstream/chat/android/compose/ui/theme/MessageComposerAttachmentMediaItemParams;Landroidx/compose/runtime/Composer;I)V + public abstract fun MessageComposerAttachments (Lio/getstream/chat/android/compose/ui/theme/MessageComposerAttachmentsParams;Landroidx/compose/runtime/Composer;I)V public abstract fun MessageComposerAudioRecordingButton (Lio/getstream/chat/android/ui/common/state/messages/composer/RecordingState;Lio/getstream/chat/android/compose/ui/messages/composer/actions/AudioRecordingActions;Landroidx/compose/runtime/Composer;I)V public abstract fun MessageComposerAudioRecordingFloatingLockIcon (ZILandroidx/compose/runtime/Composer;I)V public abstract fun MessageComposerAudioRecordingHint (Landroidx/compose/material3/SnackbarData;Landroidx/compose/runtime/Composer;I)V @@ -2946,7 +2849,6 @@ public final class io/getstream/chat/android/compose/ui/theme/ChatComponentFacto public static fun DirectChannelInfoTopBar (Lio/getstream/chat/android/compose/ui/theme/ChatComponentFactory;Lio/getstream/chat/android/ui/common/state/messages/list/ChannelHeaderViewState;Landroidx/compose/foundation/lazy/LazyListState;Lkotlin/jvm/functions/Function0;Landroidx/compose/runtime/Composer;I)V public static fun FileAttachmentContent (Lio/getstream/chat/android/compose/ui/theme/ChatComponentFactory;Lio/getstream/chat/android/compose/state/messages/attachments/AttachmentState;Landroidx/compose/ui/Modifier;Landroidx/compose/runtime/Composer;I)V public static fun FileAttachmentItem (Lio/getstream/chat/android/compose/ui/theme/ChatComponentFactory;Landroidx/compose/ui/Modifier;Lio/getstream/chat/android/models/Attachment;ZLkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;I)V - public static fun FileAttachmentPreviewContent (Lio/getstream/chat/android/compose/ui/theme/ChatComponentFactory;Landroidx/compose/ui/Modifier;Ljava/util/List;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;I)V public static fun GiphyAttachmentContent (Lio/getstream/chat/android/compose/ui/theme/ChatComponentFactory;Lio/getstream/chat/android/compose/state/messages/attachments/AttachmentState;Landroidx/compose/ui/Modifier;Landroidx/compose/runtime/Composer;I)V public static fun GroupChannelInfoAddMembersButton (Lio/getstream/chat/android/compose/ui/theme/ChatComponentFactory;Lkotlin/jvm/functions/Function0;Landroidx/compose/runtime/Composer;I)V public static fun GroupChannelInfoAvatarContainer (Lio/getstream/chat/android/compose/ui/theme/ChatComponentFactory;Lio/getstream/chat/android/models/Channel;Lio/getstream/chat/android/models/User;Lio/getstream/chat/android/ui/common/utils/ExpandableList;Landroidx/compose/runtime/Composer;I)V @@ -2966,6 +2868,10 @@ public final class io/getstream/chat/android/compose/ui/theme/ChatComponentFacto public static fun MessageBottom (Lio/getstream/chat/android/compose/ui/theme/ChatComponentFactory;Landroidx/compose/foundation/layout/ColumnScope;Lio/getstream/chat/android/ui/common/state/messages/list/MessageItemState;Landroidx/compose/runtime/Composer;I)V public static fun MessageBubble-T042LqI (Lio/getstream/chat/android/compose/ui/theme/ChatComponentFactory;Landroidx/compose/ui/Modifier;Lio/getstream/chat/android/models/Message;JLandroidx/compose/ui/graphics/Shape;Landroidx/compose/foundation/BorderStroke;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;I)V public static fun MessageComposer (Lio/getstream/chat/android/compose/ui/theme/ChatComponentFactory;Lio/getstream/chat/android/ui/common/state/messages/composer/MessageComposerState;ZLkotlin/jvm/functions/Function2;Landroidx/compose/ui/Modifier;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lio/getstream/chat/android/compose/ui/messages/composer/actions/AudioRecordingActions;Lkotlin/jvm/functions/Function4;Landroidx/compose/runtime/Composer;II)V + public static fun MessageComposerAttachmentAudioRecordItem (Lio/getstream/chat/android/compose/ui/theme/ChatComponentFactory;Lio/getstream/chat/android/compose/ui/theme/MessageComposerAttachmentAudioRecordItemParams;Landroidx/compose/runtime/Composer;I)V + public static fun MessageComposerAttachmentFileItem (Lio/getstream/chat/android/compose/ui/theme/ChatComponentFactory;Lio/getstream/chat/android/compose/ui/theme/MessageComposerAttachmentFileItemParams;Landroidx/compose/runtime/Composer;I)V + public static fun MessageComposerAttachmentMediaItem (Lio/getstream/chat/android/compose/ui/theme/ChatComponentFactory;Lio/getstream/chat/android/compose/ui/theme/MessageComposerAttachmentMediaItemParams;Landroidx/compose/runtime/Composer;I)V + public static fun MessageComposerAttachments (Lio/getstream/chat/android/compose/ui/theme/ChatComponentFactory;Lio/getstream/chat/android/compose/ui/theme/MessageComposerAttachmentsParams;Landroidx/compose/runtime/Composer;I)V public static fun MessageComposerAudioRecordingButton (Lio/getstream/chat/android/compose/ui/theme/ChatComponentFactory;Lio/getstream/chat/android/ui/common/state/messages/composer/RecordingState;Lio/getstream/chat/android/compose/ui/messages/composer/actions/AudioRecordingActions;Landroidx/compose/runtime/Composer;I)V public static fun MessageComposerAudioRecordingFloatingLockIcon (Lio/getstream/chat/android/compose/ui/theme/ChatComponentFactory;ZILandroidx/compose/runtime/Composer;I)V public static fun MessageComposerAudioRecordingHint (Lio/getstream/chat/android/compose/ui/theme/ChatComponentFactory;Landroidx/compose/material3/SnackbarData;Landroidx/compose/runtime/Composer;I)V @@ -3093,7 +2999,6 @@ public final class io/getstream/chat/android/compose/ui/theme/ChatConfig { public final class io/getstream/chat/android/compose/ui/theme/ChatTheme { public static final field $stable I public static final field INSTANCE Lio/getstream/chat/android/compose/ui/theme/ChatTheme; - public final fun getAttachmentFactories (Landroidx/compose/runtime/Composer;I)Ljava/util/List; public final fun getAttachmentPreviewHandlers (Landroidx/compose/runtime/Composer;I)Ljava/util/List; public final fun getChannelNameFormatter (Landroidx/compose/runtime/Composer;I)Lio/getstream/chat/android/ui/common/utils/ChannelNameFormatter; public final fun getChannelOptionsTheme (Landroidx/compose/runtime/Composer;I)Lio/getstream/chat/android/compose/ui/theme/ChannelOptionsTheme; @@ -3123,7 +3028,7 @@ public final class io/getstream/chat/android/compose/ui/theme/ChatTheme { } public final class io/getstream/chat/android/compose/ui/theme/ChatThemeKt { - public static final fun ChatTheme (ZLio/getstream/chat/android/compose/ui/theme/ChatConfig;Lio/getstream/chat/android/compose/ui/theme/StreamDesign$Colors;Lio/getstream/chat/android/compose/ui/theme/StreamDesign$Typography;Lio/getstream/chat/android/compose/ui/theme/StreamRippleConfiguration;Lio/getstream/chat/android/ui/common/model/UserPresence;Lio/getstream/chat/android/compose/ui/theme/ChatComponentFactory;Ljava/util/List;Ljava/util/List;Lio/getstream/chat/android/compose/ui/util/ReactionResolver;Lio/getstream/chat/android/compose/ui/theme/ReactionOptionsTheme;Lio/getstream/chat/android/compose/ui/util/MessagePreviewIconFactory;ZLio/getstream/chat/android/ui/common/helper/DateFormatter;Lio/getstream/chat/android/ui/common/helper/TimeProvider;Lio/getstream/chat/android/ui/common/helper/DurationFormatter;Lio/getstream/chat/android/ui/common/utils/ChannelNameFormatter;Lio/getstream/chat/android/compose/ui/util/MessagePreviewFormatter;Lio/getstream/chat/android/compose/ui/util/SearchResultNameFormatter;Lio/getstream/chat/android/compose/ui/util/StreamCoilImageLoaderFactory;Lio/getstream/chat/android/ui/common/helper/ImageHeadersProvider;Lio/getstream/chat/android/ui/common/helper/DownloadAttachmentUriGenerator;Lio/getstream/chat/android/ui/common/helper/DownloadRequestInterceptor;Lio/getstream/chat/android/ui/common/helper/ImageAssetTransformer;Lio/getstream/chat/android/compose/ui/util/MessageAlignmentProvider;Lio/getstream/chat/android/compose/ui/theme/MessageOptionsTheme;Lio/getstream/chat/android/compose/ui/theme/ChannelOptionsTheme;Lio/getstream/chat/android/ui/common/images/resizing/StreamCdnImageResizing;Lio/getstream/chat/android/compose/ui/theme/MessageComposerTheme;Lio/getstream/chat/android/compose/ui/util/MessageTextFormatter;Lio/getstream/sdk/chat/audio/recording/StreamMediaRecorder;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;IIIIII)V + public static final fun ChatTheme (ZLio/getstream/chat/android/compose/ui/theme/ChatConfig;Lio/getstream/chat/android/compose/ui/theme/StreamDesign$Colors;Lio/getstream/chat/android/compose/ui/theme/StreamDesign$Typography;Lio/getstream/chat/android/compose/ui/theme/StreamRippleConfiguration;Lio/getstream/chat/android/ui/common/model/UserPresence;Lio/getstream/chat/android/compose/ui/theme/ChatComponentFactory;Ljava/util/List;Lio/getstream/chat/android/compose/ui/util/ReactionResolver;Lio/getstream/chat/android/compose/ui/theme/ReactionOptionsTheme;Lio/getstream/chat/android/compose/ui/util/MessagePreviewIconFactory;ZLio/getstream/chat/android/ui/common/helper/DateFormatter;Lio/getstream/chat/android/ui/common/helper/TimeProvider;Lio/getstream/chat/android/ui/common/helper/DurationFormatter;Lio/getstream/chat/android/ui/common/utils/ChannelNameFormatter;Lio/getstream/chat/android/compose/ui/util/MessagePreviewFormatter;Lio/getstream/chat/android/compose/ui/util/SearchResultNameFormatter;Lio/getstream/chat/android/compose/ui/util/StreamCoilImageLoaderFactory;Lio/getstream/chat/android/ui/common/helper/ImageHeadersProvider;Lio/getstream/chat/android/ui/common/helper/DownloadAttachmentUriGenerator;Lio/getstream/chat/android/ui/common/helper/DownloadRequestInterceptor;Lio/getstream/chat/android/ui/common/helper/ImageAssetTransformer;Lio/getstream/chat/android/compose/ui/util/MessageAlignmentProvider;Lio/getstream/chat/android/compose/ui/theme/MessageOptionsTheme;Lio/getstream/chat/android/compose/ui/theme/ChannelOptionsTheme;Lio/getstream/chat/android/ui/common/images/resizing/StreamCdnImageResizing;Lio/getstream/chat/android/compose/ui/theme/MessageComposerTheme;Lio/getstream/chat/android/compose/ui/util/MessageTextFormatter;Lio/getstream/sdk/chat/audio/recording/StreamMediaRecorder;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;IIIII)V public static final fun getLocalChatConfig ()Landroidx/compose/runtime/ProvidableCompositionLocal; public static final fun getLocalComponentFactory ()Landroidx/compose/runtime/ProvidableCompositionLocal; } @@ -3377,6 +3282,84 @@ public final class io/getstream/chat/android/compose/ui/theme/MentionStyleFactor public final fun getNoStyle ()Lio/getstream/chat/android/compose/ui/theme/MentionStyleFactory; } +public final class io/getstream/chat/android/compose/ui/theme/MessageComposerAttachmentAudioRecordItemParams { + public static final field $stable I + public fun (Lio/getstream/chat/android/models/Attachment;Lio/getstream/chat/android/ui/common/state/messages/list/AudioPlayerState;Landroidx/compose/ui/Modifier;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;)V + public synthetic fun (Lio/getstream/chat/android/models/Attachment;Lio/getstream/chat/android/ui/common/state/messages/list/AudioPlayerState;Landroidx/compose/ui/Modifier;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lio/getstream/chat/android/models/Attachment; + public final fun component2 ()Lio/getstream/chat/android/ui/common/state/messages/list/AudioPlayerState; + public final fun component3 ()Landroidx/compose/ui/Modifier; + public final fun component4 ()Lkotlin/jvm/functions/Function1; + public final fun component5 ()Lkotlin/jvm/functions/Function1; + public final fun component6 ()Lkotlin/jvm/functions/Function1; + public final fun component7 ()Lkotlin/jvm/functions/Function2; + public final fun component8 ()Lkotlin/jvm/functions/Function1; + public final fun copy (Lio/getstream/chat/android/models/Attachment;Lio/getstream/chat/android/ui/common/state/messages/list/AudioPlayerState;Landroidx/compose/ui/Modifier;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;)Lio/getstream/chat/android/compose/ui/theme/MessageComposerAttachmentAudioRecordItemParams; + public static synthetic fun copy$default (Lio/getstream/chat/android/compose/ui/theme/MessageComposerAttachmentAudioRecordItemParams;Lio/getstream/chat/android/models/Attachment;Lio/getstream/chat/android/ui/common/state/messages/list/AudioPlayerState;Landroidx/compose/ui/Modifier;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lio/getstream/chat/android/compose/ui/theme/MessageComposerAttachmentAudioRecordItemParams; + public fun equals (Ljava/lang/Object;)Z + public final fun getAttachment ()Lio/getstream/chat/android/models/Attachment; + public final fun getModifier ()Landroidx/compose/ui/Modifier; + public final fun getOnAttachmentRemoved ()Lkotlin/jvm/functions/Function1; + public final fun getOnPlaySpeedClick ()Lkotlin/jvm/functions/Function1; + public final fun getOnPlayToggleClick ()Lkotlin/jvm/functions/Function1; + public final fun getOnThumbDragStart ()Lkotlin/jvm/functions/Function1; + public final fun getOnThumbDragStop ()Lkotlin/jvm/functions/Function2; + public final fun getPlayerState ()Lio/getstream/chat/android/ui/common/state/messages/list/AudioPlayerState; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/chat/android/compose/ui/theme/MessageComposerAttachmentFileItemParams { + public static final field $stable I + public fun (Lio/getstream/chat/android/models/Attachment;Lkotlin/jvm/functions/Function1;Landroidx/compose/ui/Modifier;)V + public synthetic fun (Lio/getstream/chat/android/models/Attachment;Lkotlin/jvm/functions/Function1;Landroidx/compose/ui/Modifier;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lio/getstream/chat/android/models/Attachment; + public final fun component2 ()Lkotlin/jvm/functions/Function1; + public final fun component3 ()Landroidx/compose/ui/Modifier; + public final fun copy (Lio/getstream/chat/android/models/Attachment;Lkotlin/jvm/functions/Function1;Landroidx/compose/ui/Modifier;)Lio/getstream/chat/android/compose/ui/theme/MessageComposerAttachmentFileItemParams; + public static synthetic fun copy$default (Lio/getstream/chat/android/compose/ui/theme/MessageComposerAttachmentFileItemParams;Lio/getstream/chat/android/models/Attachment;Lkotlin/jvm/functions/Function1;Landroidx/compose/ui/Modifier;ILjava/lang/Object;)Lio/getstream/chat/android/compose/ui/theme/MessageComposerAttachmentFileItemParams; + public fun equals (Ljava/lang/Object;)Z + public final fun getAttachment ()Lio/getstream/chat/android/models/Attachment; + public final fun getModifier ()Landroidx/compose/ui/Modifier; + public final fun getOnAttachmentRemoved ()Lkotlin/jvm/functions/Function1; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/chat/android/compose/ui/theme/MessageComposerAttachmentMediaItemParams { + public static final field $stable I + public fun (Lio/getstream/chat/android/models/Attachment;Lkotlin/jvm/functions/Function1;Landroidx/compose/ui/Modifier;)V + public synthetic fun (Lio/getstream/chat/android/models/Attachment;Lkotlin/jvm/functions/Function1;Landroidx/compose/ui/Modifier;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lio/getstream/chat/android/models/Attachment; + public final fun component2 ()Lkotlin/jvm/functions/Function1; + public final fun component3 ()Landroidx/compose/ui/Modifier; + public final fun copy (Lio/getstream/chat/android/models/Attachment;Lkotlin/jvm/functions/Function1;Landroidx/compose/ui/Modifier;)Lio/getstream/chat/android/compose/ui/theme/MessageComposerAttachmentMediaItemParams; + public static synthetic fun copy$default (Lio/getstream/chat/android/compose/ui/theme/MessageComposerAttachmentMediaItemParams;Lio/getstream/chat/android/models/Attachment;Lkotlin/jvm/functions/Function1;Landroidx/compose/ui/Modifier;ILjava/lang/Object;)Lio/getstream/chat/android/compose/ui/theme/MessageComposerAttachmentMediaItemParams; + public fun equals (Ljava/lang/Object;)Z + public final fun getAttachment ()Lio/getstream/chat/android/models/Attachment; + public final fun getModifier ()Landroidx/compose/ui/Modifier; + public final fun getOnAttachmentRemoved ()Lkotlin/jvm/functions/Function1; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/chat/android/compose/ui/theme/MessageComposerAttachmentsParams { + public static final field $stable I + public fun (Ljava/util/List;Lkotlin/jvm/functions/Function1;Landroidx/compose/ui/Modifier;)V + public synthetic fun (Ljava/util/List;Lkotlin/jvm/functions/Function1;Landroidx/compose/ui/Modifier;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/util/List; + public final fun component2 ()Lkotlin/jvm/functions/Function1; + public final fun component3 ()Landroidx/compose/ui/Modifier; + public final fun copy (Ljava/util/List;Lkotlin/jvm/functions/Function1;Landroidx/compose/ui/Modifier;)Lio/getstream/chat/android/compose/ui/theme/MessageComposerAttachmentsParams; + public static synthetic fun copy$default (Lio/getstream/chat/android/compose/ui/theme/MessageComposerAttachmentsParams;Ljava/util/List;Lkotlin/jvm/functions/Function1;Landroidx/compose/ui/Modifier;ILjava/lang/Object;)Lio/getstream/chat/android/compose/ui/theme/MessageComposerAttachmentsParams; + public fun equals (Ljava/lang/Object;)Z + public final fun getAttachments ()Ljava/util/List; + public final fun getModifier ()Landroidx/compose/ui/Modifier; + public final fun getOnAttachmentRemoved ()Lkotlin/jvm/functions/Function1; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + public final class io/getstream/chat/android/compose/ui/theme/MessageComposerInputLeadingContentParams { public static final field $stable I public fun (Lio/getstream/chat/android/ui/common/state/messages/composer/MessageComposerState;Lkotlin/jvm/functions/Function0;Landroidx/compose/ui/Modifier;)V @@ -4043,7 +4026,7 @@ public abstract interface class io/getstream/chat/android/compose/ui/util/Messag } public final class io/getstream/chat/android/compose/ui/util/MessagePreviewFormatter$Companion { - public final fun defaultFormatter (Landroid/content/Context;ZLio/getstream/chat/android/compose/ui/theme/StreamDesign$Typography;Ljava/util/List;Lio/getstream/chat/android/compose/ui/theme/StreamDesign$Colors;)Lio/getstream/chat/android/compose/ui/util/MessagePreviewFormatter; + public final fun defaultFormatter (Landroid/content/Context;ZLio/getstream/chat/android/compose/ui/theme/StreamDesign$Typography;Lio/getstream/chat/android/compose/ui/theme/StreamDesign$Colors;)Lio/getstream/chat/android/compose/ui/util/MessagePreviewFormatter; } public abstract interface class io/getstream/chat/android/compose/ui/util/MessagePreviewIconFactory { diff --git a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/AttachmentFactory.kt b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/AttachmentFactory.kt deleted file mode 100644 index 1b3fb4f9382..00000000000 --- a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/AttachmentFactory.kt +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. - * - * Licensed under the Stream License; - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://github.com/GetStream/stream-chat-android/blob/main/LICENSE - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.getstream.chat.android.compose.ui.attachments - -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import io.getstream.chat.android.compose.state.messages.attachments.AttachmentState -import io.getstream.chat.android.models.Attachment - -/** - * Holds the information required to build an attachment message. - * - * @param canHandle Checks the message and returns if the factory can consume it or not. - * @param previewContent Composable function that allows users to define the content the [AttachmentFactory] will build, - * using any given [AttachmentState], when the message is displayed in the message input preview, before sending. - * @param content Composable function that allows users to define the content the [AttachmentFactory] will build - * using any given [AttachmentState], when the message is displayed in the message list. - * @param textFormatter The formatter used to get a string representation for the given attachment. - */ -public open class AttachmentFactory( - public val canHandle: (attachments: List) -> Boolean, - public val previewContent: ( - @Composable ( - modifier: Modifier, - attachments: List, - onAttachmentRemoved: (Attachment) -> Unit, - ) -> Unit - )? = null, - public val textFormatter: (attachments: Attachment) -> String = { - it.title ?: it.name ?: it.fallback ?: "" - }, - public val type: Type = Type.None, -) { - - /** - * The type of the attachment factory. - */ - public interface Type { - /** - * The none type. - */ - public data object None : Type - - /** - * The SDK built-in types. - */ - public enum class BuiltIn : Type { - /** - * The attachment is a file. - */ - FILE, - - /** - * The attachment is a link. - */ - LINK, - - /** - * The attachment is a giphy. - */ - GIPHY, - - /** - * The attachment is a media, such as an image or video. - */ - MEDIA, - - /** - * The attachment is a quoted message. - */ - QUOTED, - - /** - * The attachment is an upload. - */ - UPLOAD, - - /** - * The attachment is an audio record. - */ - AUDIO_RECORD, - - /** - * The attachment is unsupported. - */ - UNSUPPORTED, - } - } -} diff --git a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/StreamAttachmentFactories.kt b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/StreamAttachmentFactories.kt deleted file mode 100644 index ae4554e6207..00000000000 --- a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/StreamAttachmentFactories.kt +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. - * - * Licensed under the Stream License; - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://github.com/GetStream/stream-chat-android/blob/main/LICENSE - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.getstream.chat.android.compose.ui.attachments - -import android.content.Context -import androidx.activity.compose.ManagedActivityResultLauncher -import androidx.compose.foundation.Image -import androidx.compose.ui.layout.ContentScale -import androidx.core.net.toUri -import io.getstream.chat.android.client.ChatClient -import io.getstream.chat.android.compose.state.mediagallerypreview.MediaGalleryPreviewResult -import io.getstream.chat.android.compose.ui.attachments.content.GiphyAttachmentClickData -import io.getstream.chat.android.compose.ui.attachments.content.LinkAttachmentClickData -import io.getstream.chat.android.compose.ui.attachments.content.onLinkAttachmentContentClick -import io.getstream.chat.android.compose.ui.attachments.content.onMediaAttachmentContentItemClick -import io.getstream.chat.android.compose.ui.attachments.factory.AudioRecordAttachmentFactory -import io.getstream.chat.android.compose.ui.attachments.factory.FileAttachmentFactory -import io.getstream.chat.android.compose.ui.attachments.factory.GiphyAttachmentFactory -import io.getstream.chat.android.compose.ui.attachments.factory.LinkAttachmentFactory -import io.getstream.chat.android.compose.ui.attachments.factory.MediaAttachmentFactory -import io.getstream.chat.android.compose.ui.attachments.factory.UnsupportedAttachmentFactory -import io.getstream.chat.android.compose.ui.attachments.preview.MediaGalleryPreviewContract -import io.getstream.chat.android.compose.viewmodel.messages.AudioPlayerViewModelFactory -import io.getstream.chat.android.models.Message -import io.getstream.chat.android.ui.common.helper.DownloadAttachmentUriGenerator -import io.getstream.chat.android.ui.common.helper.DownloadRequestInterceptor -import io.getstream.chat.android.ui.common.images.resizing.StreamCdnImageResizing -import io.getstream.chat.android.ui.common.utils.GiphyInfoType -import io.getstream.chat.android.ui.common.utils.GiphySizingMode - -/** - * Provides different attachment factories that build custom message content based on a given attachment. - */ -public object StreamAttachmentFactories { - - /** - * The default max length of the link attachments description. We limit this, because for some links the description - * can be too long. - */ - private const val DEFAULT_LINK_DESCRIPTION_MAX_LINES = 2 - - /** - * Default attachment factories we provide, which can transform image, file and link attachments. - * - * @param getChatClient - A lambda that provides the ChatClient instance. - * @param linkDescriptionMaxLines - The limit of how long the link attachment descriptions can be. - * @param giphyInfoType Used to modify the quality and dimensions of the rendered - * Giphy attachments. - * @param giphySizingMode Sets the Giphy container sizing strategy. Setting it to automatic - * makes the container capable of adaptive resizing and ignore - * the default Giphy width and height dimensions, however you can still clip maximum dimensions. - * Setting it to fixed size mode will make it respect all given dimensions. - * @param contentScale Used to determine the way Giphys are scaled inside the [Image] composable. - * @param skipEnrichUrl Used by the media gallery. If set to true will skip enriching URLs when you update the - * message by deleting an attachment contained within it. Set to false by default. - * @param onLinkContentItemClick Lambda called when a link attachment content item gets clicked. - * @param onGiphyContentItemClick Lambda called when a giphy attachment content item gets clicked (no-action by - * default). - * @param onMediaContentItemClick Lambda called when a image or video attachment content item gets clicked. - * @param skipTypes A list of [AttachmentFactory.Type] that should be skipped from the default factories. - * - * @return A [List] of various [AttachmentFactory] instances that provide different attachments support. - */ - @Deprecated( - message = "Use defaults() method instead.", - replaceWith = ReplaceWith("defaults(...)"), - level = DeprecationLevel.WARNING, - ) - public fun defaultFactories( - getChatClient: () -> ChatClient = { ChatClient.instance() }, - linkDescriptionMaxLines: Int = DEFAULT_LINK_DESCRIPTION_MAX_LINES, - giphyInfoType: GiphyInfoType = GiphyInfoType.ORIGINAL, - giphySizingMode: GiphySizingMode = GiphySizingMode.ADAPTIVE, - contentScale: ContentScale = ContentScale.Crop, - skipEnrichUrl: Boolean = false, - onLinkContentItemClick: (context: Context, previewUrl: String) -> Unit = ::onLinkAttachmentContentClick, - onGiphyContentItemClick: (context: Context, url: String) -> Unit = { _, _ -> }, - onMediaContentItemClick: ( - mediaGalleryPreviewLauncher: ManagedActivityResultLauncher, - message: Message, - attachmentPosition: Int, - videoThumbnailsEnabled: Boolean, - downloadAttachmentUriGenerator: DownloadAttachmentUriGenerator, - downloadRequestInterceptor: DownloadRequestInterceptor, - streamCdnImageResizing: StreamCdnImageResizing, - skipEnrichUrl: Boolean, - ) -> Unit = ::onMediaAttachmentContentItemClick, - skipTypes: List = emptyList(), - ): List = listOf( - AudioRecordAttachmentFactory( - viewModelFactory = AudioPlayerViewModelFactory( - getAudioPlayer = { getChatClient().audioPlayer }, - getRecordingUri = { it.assetUrl ?: it.upload?.toUri()?.toString() }, - ), - getCurrentUserId = { getChatClient().getCurrentOrStoredUserId() }, - ), - LinkAttachmentFactory( - linkDescriptionMaxLines = linkDescriptionMaxLines, - onContentItemClick = onLinkContentItemClick, - ), - GiphyAttachmentFactory( - giphyInfoType = giphyInfoType, - giphySizingMode = giphySizingMode, - contentScale = contentScale, - onContentItemClick = onGiphyContentItemClick, - ), - MediaAttachmentFactory(), - FileAttachmentFactory(), - UnsupportedAttachmentFactory, - ).filterNot { skipTypes.contains(it.type) } - - public fun defaults( - getChatClient: () -> ChatClient = { ChatClient.instance() }, - linkDescriptionMaxLines: Int = DEFAULT_LINK_DESCRIPTION_MAX_LINES, - giphyInfoType: GiphyInfoType = GiphyInfoType.ORIGINAL, - giphySizingMode: GiphySizingMode = GiphySizingMode.ADAPTIVE, - contentScale: ContentScale = ContentScale.Crop, - onLinkContentItemClick: (LinkAttachmentClickData) -> Unit = { - onLinkAttachmentContentClick(it.context, it.url) - }, - onGiphyContentItemClick: (GiphyAttachmentClickData) -> Unit = {}, - skipTypes: List = emptyList(), - ): List = listOf( - AudioRecordAttachmentFactory( - viewModelFactory = AudioPlayerViewModelFactory( - getAudioPlayer = { getChatClient().audioPlayer }, - getRecordingUri = { it.assetUrl ?: it.upload?.toUri()?.toString() }, - ), - getCurrentUserId = { getChatClient().getCurrentOrStoredUserId() }, - ), - LinkAttachmentFactory( - linkDescriptionMaxLines = linkDescriptionMaxLines, - onItemClick = onLinkContentItemClick, - ), - GiphyAttachmentFactory( - giphyInfoType = giphyInfoType, - giphySizingMode = giphySizingMode, - contentScale = contentScale, - onItemClick = onGiphyContentItemClick, - ), - MediaAttachmentFactory(), - FileAttachmentFactory(), - UnsupportedAttachmentFactory, - ).filterNot { skipTypes.contains(it.type) } -} diff --git a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/content/AudioRecordAttachmentContent.kt b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/content/AudioRecordAttachmentContent.kt index d578c8445be..17ed10b12ef 100644 --- a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/content/AudioRecordAttachmentContent.kt +++ b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/content/AudioRecordAttachmentContent.kt @@ -17,9 +17,9 @@ package io.getstream.chat.android.compose.ui.attachments.content import androidx.compose.foundation.background -import androidx.compose.foundation.border import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.defaultMinSize import androidx.compose.foundation.layout.fillMaxWidth @@ -38,7 +38,6 @@ import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview @@ -60,21 +59,20 @@ import io.getstream.chat.android.compose.ui.components.audio.PlaybackTimerText import io.getstream.chat.android.compose.ui.components.audio.StaticWaveformSlider import io.getstream.chat.android.compose.ui.components.button.StreamButton import io.getstream.chat.android.compose.ui.components.button.StreamButtonStyleDefaults +import io.getstream.chat.android.compose.ui.components.common.PlaybackSpeedToggle import io.getstream.chat.android.compose.ui.theme.ChatPreviewTheme import io.getstream.chat.android.compose.ui.theme.ChatTheme import io.getstream.chat.android.compose.ui.theme.MessageStyling import io.getstream.chat.android.compose.ui.theme.StreamTokens import io.getstream.chat.android.compose.ui.util.applyIf -import io.getstream.chat.android.compose.ui.util.clickable import io.getstream.chat.android.compose.ui.util.shouldBeDisplayedAsFullSizeAttachment import io.getstream.chat.android.compose.viewmodel.messages.AudioPlayerViewModel import io.getstream.chat.android.compose.viewmodel.messages.AudioPlayerViewModelFactory -import io.getstream.chat.android.extensions.isInt import io.getstream.chat.android.models.Attachment import io.getstream.chat.android.models.Attachment.UploadState +import io.getstream.chat.android.previewdata.PreviewAttachmentData import io.getstream.chat.android.ui.common.state.messages.list.AudioPlayerState import io.getstream.chat.android.ui.common.utils.MediaStringUtil -import kotlin.random.Random /** * Represents the audio recording attachment content. @@ -115,18 +113,10 @@ public fun AudioRecordAttachmentContent( attachment = audioRecording, playerState = playerState, isMine = attachmentState.isMine, - onPlayToggleClick = { attachment -> - viewModel.playOrPause(attachment) - }, - onPlaySpeedClick = { attachment -> - viewModel.changeSpeed(attachment) - }, - onThumbDragStart = { attachment -> - viewModel.startSeek(attachment) - }, - onThumbDragStop = { attachment, progress -> - viewModel.seekTo(attachment, progress) - }, + onPlayToggleClick = viewModel::playOrPause, + onPlaySpeedClick = viewModel::changeSpeed, + onThumbDragStart = viewModel::startSeek, + onThumbDragStop = viewModel::seekTo, ) } } @@ -181,7 +171,7 @@ public fun AudioRecordAttachmentContentItem( onThumbDragStop = onThumbDragStop, tailContent = { val speed = playerState.speeds.getOrDefault(attachment.audioHash, 1f) - SpeedButton(speed = speed, outlineColor = outlineColor, enabled = !isUploading) { + PlaybackSpeedToggle(speed = speed, outlineColor = outlineColor, enabled = !isUploading) { onPlaySpeedClick(currentAttachment) } }, @@ -197,6 +187,12 @@ internal fun AudioRecordAttachmentContentItemBase( waveformHeight: Dp, outlineColor: Color, textColor: Color, + contentPadding: PaddingValues = PaddingValues( + start = StreamTokens.spacingXs, + top = StreamTokens.spacingXs, + end = StreamTokens.spacingSm, + bottom = StreamTokens.spacingXs, + ), onPlayToggleClick: (Attachment) -> Unit = {}, onThumbDragStart: (Attachment) -> Unit = {}, onThumbDragStop: (Attachment, Float) -> Unit = { _, _ -> }, @@ -207,10 +203,7 @@ internal fun AudioRecordAttachmentContentItemBase( val trackProgress = playerState.current.playingProgress.takeIf { isCurrentAttachment } ?: attachmentUrl?.let { playerState.seekTo.getOrDefault(attachment.audioHash, 0f) } ?: 0f val playing = isCurrentAttachment && playerState.current.isPlaying - val waveform = when (playing) { - true -> playerState.current.waveform - else -> attachment.waveformData - } ?: emptyList() + val waveform = (if (playing) playerState.current.waveform else attachment.waveformData) ?: emptyList() val uploadProgress = attachment.uploadState as? UploadState.InProgress val currentAttachment by rememberUpdatedState(attachment) @@ -218,12 +211,7 @@ internal fun AudioRecordAttachmentContentItemBase( modifier = modifier .defaultMinSize(minHeight = 64.dp) .fillMaxWidth() - .padding( - top = StreamTokens.spacingXs, - bottom = StreamTokens.spacingXs, - start = StreamTokens.spacingXs, - end = StreamTokens.spacingSm, - ), + .padding(contentPadding), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(StreamTokens.spacingXs), ) { @@ -240,6 +228,14 @@ internal fun AudioRecordAttachmentContentItemBase( var currentProgress by remember { mutableFloatStateOf(trackProgress) } LaunchedEffect(attachmentUrl, playing, trackProgress) { currentProgress = trackProgress } + val timerTextColor = if (playing) ChatTheme.colors.accentPrimary else textColor + PlaybackTimerText( + progress = currentProgress, + durationInMs = currentAttachment.durationInMs, + color = timerTextColor, + countdown = true, + ) + StaticWaveformSlider( modifier = Modifier .height(waveformHeight) @@ -260,14 +256,6 @@ internal fun AudioRecordAttachmentContentItemBase( onThumbDragStop(currentAttachment, it) }, ) - - val timerTextColor = if (playing) ChatTheme.colors.accentPrimary else textColor - PlaybackTimerText( - progress = currentProgress, - durationInMs = currentAttachment.durationInMs, - color = timerTextColor, - countdown = true, - ) } tailContent() @@ -307,36 +295,6 @@ internal fun PlaybackToggleButton( } } -private val speedButtonShape = RoundedCornerShape(StreamTokens.radiusLg) - -/** - * Represents the speed button. - */ -@Composable -private fun SpeedButton( - speed: Float, - outlineColor: Color, - enabled: Boolean = true, - onClick: () -> Unit, -) { - val colors = ChatTheme.colors - val textColor = if (enabled) colors.controlPlaybackToggleText else colors.textDisabled - val borderColor = if (enabled) outlineColor else colors.borderUtilityDisabled - Text( - text = when (speed.isInt()) { - true -> "x${speed.toInt()}" - else -> "x$speed" - }, - style = ChatTheme.typography.metadataEmphasis, - color = textColor, - modifier = Modifier - .border(1.dp, borderColor, speedButtonShape) - .clip(speedButtonShape) - .applyIf(enabled) { clickable(onClick = onClick) } - .padding(horizontal = StreamTokens.spacingXs, vertical = StreamTokens.spacing2xs), - ) -} - @Composable private fun UploadProgressIndicator( uploadState: UploadState.InProgress, @@ -366,15 +324,14 @@ private fun progressFraction(state: UploadState.InProgress): Float = @Composable internal fun AudioRecordAttachmentContentItemPlayback() { - val rand = Random(1) val previewUri = "preview://audio" AudioRecordAttachmentContentItem( - attachment = Attachment(type = "audio_recording", assetUrl = previewUri), + attachment = PreviewAttachmentData.attachmentAudioRecording1.copy(assetUrl = previewUri), playerState = AudioPlayerState( current = AudioPlayerState.CurrentAudioState( isPlaying = true, audioUri = previewUri, - waveform = List(size = 100) { rand.nextFloat() }, + waveform = PreviewAttachmentData.attachmentAudioRecording1.waveformData!!, ), getRecordingUri = Attachment::assetUrl, ), @@ -384,8 +341,7 @@ internal fun AudioRecordAttachmentContentItemPlayback() { @Composable internal fun AudioRecordAttachmentContentItemUploading() { AudioRecordAttachmentContentItem( - attachment = Attachment( - type = "audio_recording", + attachment = PreviewAttachmentData.attachmentAudioRecording1.copy( assetUrl = "preview://audio", uploadState = UploadState.InProgress( bytesUploaded = 2_400_000, diff --git a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/content/AudioRecordAttachmentPreviewContent.kt b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/content/AudioRecordAttachmentPreviewContent.kt deleted file mode 100644 index f237fdeb86d..00000000000 --- a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/content/AudioRecordAttachmentPreviewContent.kt +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. - * - * Licensed under the Stream License; - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://github.com/GetStream/stream-chat-android/blob/main/LICENSE - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.getstream.chat.android.compose.ui.attachments.content - -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.lazy.LazyRow -import androidx.compose.foundation.lazy.items -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.rememberUpdatedState -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.compose.LifecycleEventEffect -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.lifecycle.viewmodel.compose.viewModel -import io.getstream.chat.android.compose.ui.components.ComposerCancelIcon -import io.getstream.chat.android.compose.ui.theme.ChatPreviewTheme -import io.getstream.chat.android.compose.ui.theme.ChatTheme -import io.getstream.chat.android.compose.viewmodel.messages.AudioPlayerViewModel -import io.getstream.chat.android.compose.viewmodel.messages.AudioPlayerViewModelFactory -import io.getstream.chat.android.models.Attachment -import io.getstream.chat.android.ui.common.state.messages.list.AudioPlayerState - -@Composable -public fun AudioRecordAttachmentPreviewContent( - attachments: List, - onAttachmentRemoved: (Attachment) -> Unit, - modifier: Modifier = Modifier, - viewModelFactory: AudioPlayerViewModelFactory, -) { - val viewModel = viewModel(AudioPlayerViewModel::class.java, factory = viewModelFactory) - - val playerState by viewModel.state.collectAsStateWithLifecycle() - - LazyRow( - modifier = modifier - .fillMaxWidth(), - ) { - items(attachments) { audioRecording -> - AudioRecordAttachmentPreviewContentItem( - attachment = audioRecording, - playerState = playerState, - onPlayToggleClick = { attachment -> - viewModel.playOrPause(attachment) - }, - onThumbDragStart = { attachment -> - viewModel.startSeek(attachment) - }, - onThumbDragStop = { attachment, progress -> - viewModel.seekTo(attachment, progress) - }, - onAttachmentRemoved = { - viewModel.reset(it) - onAttachmentRemoved(it) - }, - ) - } - } - - // Cleanup: Pause any playing tracks in onPause. - LifecycleEventEffect(event = Lifecycle.Event.ON_PAUSE) { - viewModel.pause() - } -} - -/** - * Represents fallback content for unsupported attachments. - * - * @param modifier Modifier for styling. - */ -@Composable -public fun AudioRecordAttachmentPreviewContentItem( - modifier: Modifier = Modifier, - attachment: Attachment, - playerState: AudioPlayerState, - onPlayToggleClick: (Attachment) -> Unit = {}, - onThumbDragStart: (Attachment) -> Unit = {}, - onThumbDragStop: (Attachment, Float) -> Unit = { _, _ -> }, - onAttachmentRemoved: (Attachment) -> Unit = {}, -) { - val currentAttachment by rememberUpdatedState(attachment) - AudioRecordAttachmentContentItemBase( - modifier = modifier, - attachment = attachment, - outlineColor = ChatTheme.colors.borderUtilitySelected, - textColor = ChatTheme.colors.chatTextOutgoing, - playerState = playerState, - waveformHeight = 36.dp, - onPlayToggleClick = onPlayToggleClick, - onThumbDragStart = onThumbDragStart, - onThumbDragStop = onThumbDragStop, - tailContent = { - ComposerCancelIcon( - modifier = Modifier - .padding(4.dp), - onClick = { onAttachmentRemoved(currentAttachment) }, - ) - }, - ) -} - -@Preview(showBackground = true) -@Composable -internal fun AudioRecordAttachmentPreviewContentItemPreview() { - val waveformData = (0..100).map { it.toFloat() } - val attachment = Attachment( - type = "audio_recording", - extraData = mutableMapOf( - "waveform" to waveformData, - "duration" to 1000, - ), - ) - - ChatPreviewTheme { - AudioRecordAttachmentPreviewContentItem( - modifier = Modifier - .background(Color.Yellow) - .width(250.dp) - .height(60.dp), - attachment = attachment, - playerState = AudioPlayerState( - current = AudioPlayerState.CurrentAudioState( - audioUri = attachment.assetUrl.orEmpty(), - waveform = waveformData, - isPlaying = false, - ), - getRecordingUri = Attachment::assetUrl, - ), - ) - } -} diff --git a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/content/FileAttachmentPreviewContent.kt b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/content/FileAttachmentPreviewContent.kt deleted file mode 100644 index 3d67621d9d9..00000000000 --- a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/content/FileAttachmentPreviewContent.kt +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. - * - * Licensed under the Stream License; - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://github.com/GetStream/stream-chat-android/blob/main/LICENSE - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.getstream.chat.android.compose.ui.attachments.content - -import androidx.compose.foundation.BorderStroke -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.lazy.LazyRow -import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.platform.testTag -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import io.getstream.chat.android.compose.ui.components.ComposerCancelIcon -import io.getstream.chat.android.compose.ui.components.composer.MessageInput -import io.getstream.chat.android.compose.ui.theme.ChatTheme -import io.getstream.chat.android.compose.ui.util.extensions.internal.stableKey -import io.getstream.chat.android.compose.ui.util.rememberAutoScrollLazyListState -import io.getstream.chat.android.models.Attachment -import io.getstream.chat.android.ui.common.utils.MediaStringUtil - -/** - * UI for currently selected file attachments, within the [MessageInput]. - * - * @param attachments Selected attachments. - * @param onAttachmentRemoved Handler when the user removes an attachment from the list. - * @param modifier Modifier for styling. - */ -@Suppress("LongMethod") -@Composable -public fun FileAttachmentPreviewContent( - attachments: List, - onAttachmentRemoved: (Attachment) -> Unit, - modifier: Modifier = Modifier, -) { - LazyRow( - state = rememberAutoScrollLazyListState(attachments.size), - modifier = modifier - .clip(RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp)) - .testTag("Stream_FileAttachmentPreviewContent"), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(4.dp, Alignment.Start), - contentPadding = PaddingValues(12.dp), - ) { - items( - items = attachments, - key = Attachment::stableKey, - ) { attachment -> - Surface( - modifier = Modifier - .animateItem() - .padding(1.dp), - color = ChatTheme.colors.backgroundCoreApp, - shape = RoundedCornerShape(16.dp), - border = BorderStroke(1.dp, ChatTheme.colors.borderCoreDefault), - ) { - Row( - modifier = Modifier - .width(200.dp) - .padding(vertical = 8.dp, horizontal = 8.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - FileAttachmentImage( - attachment = attachment, - true, - ) - - Column( - modifier = Modifier - .weight(1f) - .padding(horizontal = 8.dp), - horizontalAlignment = Alignment.Start, - verticalArrangement = Arrangement.Center, - ) { - Text( - modifier = Modifier.testTag("Stream_FileNameInPreview"), - text = attachment.title ?: attachment.name ?: "", - style = ChatTheme.typography.bodyEmphasis, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - color = ChatTheme.colors.textPrimary, - ) - - val fileSize = attachment.fileSize - .takeIf { it > 0 } - ?.let { MediaStringUtil.convertFileSizeByteCount(it.toLong()) } - if (fileSize != null) { - Text( - modifier = Modifier.testTag("Stream_FileSizeInPreview"), - text = fileSize, - style = ChatTheme.typography.metadataDefault, - color = ChatTheme.colors.textSecondary, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) - } - } - - ComposerCancelIcon( - modifier = Modifier.padding(4.dp).testTag("Stream_AttachmentCancelIcon"), - onClick = { onAttachmentRemoved(attachment) }, - ) - } - } - } - } -} - -@Composable -@Preview(showBackground = true) -private fun FileAttachmentPreviewContentPreview() { - ChatTheme { - FileAttachmentPreviewContent() - } -} - -@Composable -internal fun FileAttachmentPreviewContent() { - val attachment = Attachment( - type = "file", - name = "test_document.pdf", - fileSize = 1024 * 1024, - mimeType = "application/pdf", - ) - FileAttachmentPreviewContent( - attachments = listOf(attachment), - onAttachmentRemoved = {}, - ) -} diff --git a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/content/ImageAttachmentPreviewContent.kt b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/content/ImageAttachmentPreviewContent.kt deleted file mode 100644 index d0d6cadfb64..00000000000 --- a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/content/ImageAttachmentPreviewContent.kt +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. - * - * Licensed under the Stream License; - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://github.com/GetStream/stream-chat-android/blob/main/LICENSE - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.getstream.chat.android.compose.ui.attachments.content - -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.lazy.LazyRow -import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.toArgb -import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import coil3.ColorImage -import coil3.compose.LocalAsyncImagePreviewHandler -import io.getstream.chat.android.compose.ui.components.ComposerCancelIcon -import io.getstream.chat.android.compose.ui.components.composer.MessageInput -import io.getstream.chat.android.compose.ui.theme.ChatTheme -import io.getstream.chat.android.compose.ui.util.AsyncImagePreviewHandler -import io.getstream.chat.android.compose.ui.util.StreamAsyncImage -import io.getstream.chat.android.models.Attachment -import io.getstream.chat.android.ui.common.utils.extensions.imagePreviewUrl - -/** - * UI for currently selected image attachments, within the [MessageInput]. - * - * @param attachments Selected attachments. - * @param onAttachmentRemoved Handler when the user removes an attachment from the list. - * @param modifier Modifier for styling. - */ -@Composable -public fun ImageAttachmentPreviewContent( - attachments: List, - onAttachmentRemoved: (Attachment) -> Unit, - modifier: Modifier = Modifier, -) { - LazyRow( - modifier = modifier.clip(RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp)), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(4.dp, Alignment.Start), - contentPadding = PaddingValues(12.dp), - ) { - items(attachments) { attachment -> - ImageAttachmentPreviewContentItem( - attachment = attachment, - onAttachmentRemoved = onAttachmentRemoved, - ) - } - } -} - -@Composable -private fun ImageAttachmentPreviewContentItem( - attachment: Attachment, - onAttachmentRemoved: (Attachment) -> Unit, -) { - val data = attachment.upload ?: attachment.imagePreviewUrl - - Box( - modifier = Modifier - .size(95.dp) - .clip(RoundedCornerShape(16.dp)), - ) { - StreamAsyncImage( - modifier = Modifier.fillMaxSize(), - data = data, - contentDescription = null, - contentScale = ContentScale.Crop, - ) - - ComposerCancelIcon( - modifier = Modifier - .align(Alignment.TopEnd) - .padding(4.dp), - onClick = { onAttachmentRemoved(attachment) }, - ) - } -} - -@Preview(showBackground = true) -@Composable -private fun ImageAttachmentContentPreview() { - ChatTheme { - ImageAttachmentPreviewContent() - } -} - -@Composable -internal fun ImageAttachmentPreviewContent() { - val previewHandler = AsyncImagePreviewHandler { - ColorImage(color = Color.Red.toArgb(), width = 200, height = 150) - } - CompositionLocalProvider(LocalAsyncImagePreviewHandler provides previewHandler) { - ImageAttachmentPreviewContentItem( - attachment = Attachment(imageUrl = "Image"), - onAttachmentRemoved = {}, - ) - } -} diff --git a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/content/MediaAttachmentPreviewContent.kt b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/content/MediaAttachmentPreviewContent.kt deleted file mode 100644 index c411d55841b..00000000000 --- a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/content/MediaAttachmentPreviewContent.kt +++ /dev/null @@ -1,168 +0,0 @@ -/* - * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. - * - * Licensed under the Stream License; - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://github.com/GetStream/stream-chat-android/blob/main/LICENSE - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.getstream.chat.android.compose.ui.attachments.content - -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.lazy.LazyRow -import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.toArgb -import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.platform.testTag -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import coil3.ColorImage -import coil3.compose.LocalAsyncImagePreviewHandler -import io.getstream.chat.android.compose.ui.attachments.factory.DefaultPreviewItemOverlayContent -import io.getstream.chat.android.compose.ui.components.ComposerCancelIcon -import io.getstream.chat.android.compose.ui.components.composer.MessageInput -import io.getstream.chat.android.compose.ui.theme.ChatTheme -import io.getstream.chat.android.compose.ui.theme.StreamTokens -import io.getstream.chat.android.compose.ui.util.AsyncImagePreviewHandler -import io.getstream.chat.android.compose.ui.util.StreamAsyncImage -import io.getstream.chat.android.compose.ui.util.extensions.internal.localPreviewData -import io.getstream.chat.android.compose.ui.util.extensions.internal.stableKey -import io.getstream.chat.android.compose.ui.util.rememberAutoScrollLazyListState -import io.getstream.chat.android.models.Attachment -import io.getstream.chat.android.models.AttachmentType - -/** - * UI for currently selected image and video attachments, within the [MessageInput]. - * - * @param attachments Selected attachments. - * @param onAttachmentRemoved Handler when the user removes an attachment from the list. - * @param modifier Modifier for styling. - * @param previewItemOverlayContent Represents the content overlaid above individual preview items. - * By default it is used to display a play button over video previews. - */ -@Composable -public fun MediaAttachmentPreviewContent( - attachments: List, - onAttachmentRemoved: (Attachment) -> Unit, - modifier: Modifier = Modifier, - previewItemOverlayContent: @Composable (attachmentType: String?) -> Unit = { attachmentType -> - if (attachmentType == AttachmentType.VIDEO) { - DefaultPreviewItemOverlayContent() - } - }, -) { - LazyRow( - state = rememberAutoScrollLazyListState(attachments.size), - modifier = modifier - .clip(RoundedCornerShape(StreamTokens.radiusLg)) - .testTag("Stream_MediaAttachmentPreviewContent"), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(StreamTokens.spacing2xs, Alignment.Start), - contentPadding = PaddingValues(horizontal = StreamTokens.spacingSm), - ) { - items( - items = attachments, - key = Attachment::stableKey, - ) { attachment -> - MediaAttachmentPreviewItem( - modifier = Modifier.animateItem(), - mediaAttachment = attachment, - onAttachmentRemoved = onAttachmentRemoved, - overlayContent = previewItemOverlayContent, - ) - } - } -} - -/** - * A preview of an individual selected image or video attachment. - * - * @param mediaAttachment The selected attachment. - * @param onAttachmentRemoved Handler when the user removes an attachment from the list. - * @param overlayContent Represents the content overlaid above the item. - * Usually used to display an icon above video previews. - */ -@Composable -private fun MediaAttachmentPreviewItem( - mediaAttachment: Attachment, - onAttachmentRemoved: (Attachment) -> Unit, - overlayContent: @Composable (attachmentType: String?) -> Unit, - modifier: Modifier = Modifier, -) { - val data = mediaAttachment.localPreviewData - - Box( - modifier = modifier - .size(95.dp) - .clip(RoundedCornerShape(StreamTokens.radiusLg)) - .testTag("Stream_MediaAttachmentPreviewItem"), - contentAlignment = Alignment.Center, - ) { - StreamAsyncImage( - modifier = Modifier.fillMaxSize(), - data = data, - contentDescription = null, - contentScale = ContentScale.Crop, - ) - - overlayContent(mediaAttachment.type) - - ComposerCancelIcon( - modifier = Modifier - .align(Alignment.TopEnd) - .padding(4.dp) - .testTag("Stream_AttachmentCancelIcon"), - onClick = { onAttachmentRemoved(mediaAttachment) }, - ) - } -} - -@Preview(showBackground = true) -@Composable -private fun MediaAttachmentItemsPreview() { - ChatTheme { - MediaAttachmentPreviewItems() - } -} - -@Composable -internal fun MediaAttachmentPreviewItems() { - val previewHandler = AsyncImagePreviewHandler { - ColorImage(color = Color.Green.toArgb(), width = 200, height = 150) - } - CompositionLocalProvider(LocalAsyncImagePreviewHandler provides previewHandler) { - Row(horizontalArrangement = Arrangement.spacedBy(4.dp)) { - MediaAttachmentPreviewItem( - mediaAttachment = Attachment(imageUrl = "Image"), - onAttachmentRemoved = {}, - overlayContent = {}, - ) - MediaAttachmentPreviewItem( - mediaAttachment = Attachment(imageUrl = "Image"), - onAttachmentRemoved = {}, - overlayContent = { DefaultPreviewItemOverlayContent() }, - ) - } - } -} diff --git a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/factory/AudioRecordAttachmentFactory.kt b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/factory/AudioRecordAttachmentFactory.kt deleted file mode 100644 index 38b4ddb6366..00000000000 --- a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/factory/AudioRecordAttachmentFactory.kt +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. - * - * Licensed under the Stream License; - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://github.com/GetStream/stream-chat-android/blob/main/LICENSE - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.getstream.chat.android.compose.ui.attachments.factory - -import androidx.compose.runtime.Composable -import io.getstream.chat.android.client.utils.attachment.isAudioRecording -import io.getstream.chat.android.compose.ui.attachments.AttachmentFactory -import io.getstream.chat.android.compose.ui.attachments.content.AudioRecordAttachmentPreviewContent -import io.getstream.chat.android.compose.viewmodel.messages.AudioPlayerViewModelFactory -import io.getstream.chat.android.models.Attachment -import io.getstream.log.StreamLog - -/** - * An [AttachmentFactory] that will be used if no other [AttachmentFactory] can handle the attachments. - */ -public class AudioRecordAttachmentFactory( - private val viewModelFactory: AudioPlayerViewModelFactory, - private val getCurrentUserId: () -> String?, - canHandle: (attachments: List) -> Boolean = { attachments -> - attachments.all(Attachment::isAudioRecording) - }, -) : AttachmentFactory( - type = Type.BuiltIn.AUDIO_RECORD, - canHandle = canHandle, - previewContent = @Composable { modifier, attachments, onAttachmentRemoved -> - AudioRecordAttachmentPreviewContent( - modifier = modifier, - attachments = attachments, - onAttachmentRemoved = onAttachmentRemoved, - viewModelFactory = viewModelFactory, - ) - }, -) { - init { - StreamLog.i("AudioRecordAttachmentFactoryImpl") { - " no args" - } - } - - protected fun finalize() { - StreamLog.i("AudioRecordAttachmentFactoryImpl") { - " no args" - } - } -} diff --git a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/factory/FileAttachmentFactory.kt b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/factory/FileAttachmentFactory.kt deleted file mode 100644 index 2f80614d03e..00000000000 --- a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/factory/FileAttachmentFactory.kt +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. - * - * Licensed under the Stream License; - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://github.com/GetStream/stream-chat-android/blob/main/LICENSE - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.getstream.chat.android.compose.ui.attachments.factory - -import androidx.compose.runtime.Composable -import io.getstream.chat.android.compose.ui.attachments.AttachmentFactory -import io.getstream.chat.android.compose.ui.attachments.content.FileAttachmentContent -import io.getstream.chat.android.compose.ui.theme.ChatTheme -import io.getstream.chat.android.models.Attachment -import io.getstream.chat.android.ui.common.utils.extensions.isAnyFileType - -/** - * An [AttachmentFactory] that validates attachments as files and uses [FileAttachmentContent] to - * build the UI for the message. - * - * @param canHandle Lambda that checks if the factory can handle the given attachments. - */ -public class FileAttachmentFactory( - canHandle: (attachments: List) -> Boolean = { attachments -> - attachments.any(Attachment::isAnyFileType) - }, -) : AttachmentFactory( - type = Type.BuiltIn.FILE, - canHandle = canHandle, - previewContent = @Composable { modifier, attachments, onAttachmentRemoved -> - ChatTheme.componentFactory.FileAttachmentPreviewContent( - modifier = modifier, - attachments = attachments, - onAttachmentRemoved = onAttachmentRemoved, - ) - }, -) diff --git a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/factory/GiphyAttachmentFactory.kt b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/factory/GiphyAttachmentFactory.kt deleted file mode 100644 index 2882728494e..00000000000 --- a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/factory/GiphyAttachmentFactory.kt +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. - * - * Licensed under the Stream License; - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://github.com/GetStream/stream-chat-android/blob/main/LICENSE - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.getstream.chat.android.compose.ui.attachments.factory - -import android.content.Context -import androidx.compose.foundation.Image -import androidx.compose.ui.layout.ContentScale -import io.getstream.chat.android.client.utils.attachment.isGiphy -import io.getstream.chat.android.compose.ui.attachments.AttachmentFactory -import io.getstream.chat.android.compose.ui.attachments.content.GiphyAttachmentClickData -import io.getstream.chat.android.compose.ui.attachments.content.GiphyAttachmentContent -import io.getstream.chat.android.models.Attachment -import io.getstream.chat.android.ui.common.utils.GiphyInfoType -import io.getstream.chat.android.ui.common.utils.GiphySizingMode - -/** - * An [AttachmentFactory] that validates and shows Giphy attachments using [GiphyAttachmentContent]. - * - * Has no "preview content", given that this attachment only exists after being sent. - * - * @param giphyInfoType Used to modify the quality and dimensions of the rendered - * Giphy attachments. - * @param giphySizingMode Sets the Giphy container sizing strategy. Setting it to automatic - * makes the container capable of adaptive resizing and ignore - * the default Giphy width and height dimensions, however you can still clip maximum dimensions. - * Setting it to fixed size mode will make it respect all given dimensions. - * @param contentScale Used to determine the way Giphys are scaled inside the [Image] composable. - * @param onItemClick Lambda called when an item gets clicked. - * @param canHandle Lambda that checks if the factory can handle the given attachments. - * - * @return Returns an instance of [AttachmentFactory] that is used to handle Giphys. - */ -public class GiphyAttachmentFactory( - giphyInfoType: GiphyInfoType = GiphyInfoType.FIXED_HEIGHT_DOWNSAMPLED, - giphySizingMode: GiphySizingMode = GiphySizingMode.ADAPTIVE, - contentScale: ContentScale = ContentScale.Crop, - onItemClick: (GiphyAttachmentClickData) -> Unit = {}, - canHandle: (attachments: List) -> Boolean = { attachments -> attachments.any(Attachment::isGiphy) }, -) : AttachmentFactory( - type = Type.BuiltIn.GIPHY, - canHandle = canHandle, -) { - /** - * Creates a new instance of [GiphyAttachmentFactory] with the default parameters. - */ - @Deprecated( - message = "Use the constructor that does not take onContentItemClick parameter.", - replaceWith = ReplaceWith( - "GiphyAttachmentFactory(" + - "giphyInfoType, " + - "giphySizingMode, " + - "contentScale, " + - "onContentItemClick, " + - "canHandle" + - ")", - ), - level = DeprecationLevel.WARNING, - ) - public constructor( - giphyInfoType: GiphyInfoType = GiphyInfoType.FIXED_HEIGHT_DOWNSAMPLED, - giphySizingMode: GiphySizingMode = GiphySizingMode.ADAPTIVE, - contentScale: ContentScale = ContentScale.Crop, - onContentItemClick: (context: Context, url: String) -> Unit, - canHandle: (attachments: List) -> Boolean = { attachments -> attachments.any(Attachment::isGiphy) }, - ) : this( - giphyInfoType = giphyInfoType, - giphySizingMode = giphySizingMode, - contentScale = contentScale, - onItemClick = { - onContentItemClick(it.context, it.url) - }, - canHandle = canHandle, - ) -} diff --git a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/factory/LinkAttachmentFactory.kt b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/factory/LinkAttachmentFactory.kt deleted file mode 100644 index ae3134a19b8..00000000000 --- a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/factory/LinkAttachmentFactory.kt +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. - * - * Licensed under the Stream License; - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://github.com/GetStream/stream-chat-android/blob/main/LICENSE - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.getstream.chat.android.compose.ui.attachments.factory - -import android.content.Context -import io.getstream.chat.android.client.utils.attachment.isGiphy -import io.getstream.chat.android.compose.ui.attachments.AttachmentFactory -import io.getstream.chat.android.compose.ui.attachments.content.LinkAttachmentClickData -import io.getstream.chat.android.compose.ui.attachments.content.LinkAttachmentContent -import io.getstream.chat.android.compose.ui.attachments.content.onLinkAttachmentContentClick -import io.getstream.chat.android.models.Attachment -import io.getstream.chat.android.ui.common.utils.extensions.hasLink - -/** - * An [AttachmentFactory] that validates attachments as images and uses [LinkAttachmentContent] to - * build the UI for the message. - * - * Has no "preview content", given that this attachment only exists after being sent. - * - * @param linkDescriptionMaxLines - The limit of how many lines we show for the link description. - * @param onLinkAttachmentContentClick Lambda called when an item gets clicked. - * @param canHandle Lambda that checks if the factory can handle the given attachments. - */ -public class LinkAttachmentFactory( - linkDescriptionMaxLines: Int, - onItemClick: (LinkAttachmentClickData) -> Unit = { - onLinkAttachmentContentClick(it.context, it.url) - }, - canHandle: (attachments: List) -> Boolean = { links -> links.any { it.hasLink() && !it.isGiphy() } }, -) : AttachmentFactory( - type = Type.BuiltIn.LINK, - canHandle = canHandle, -) { - - /** - * Creates a new instance of [LinkAttachmentFactory] with the default parameters. - */ - @Deprecated( - message = "Use the constructor that does not take onContentItemClick parameter.", - replaceWith = ReplaceWith("LinkAttachmentFactory(linkDescriptionMaxLines, onItemClick, canHandle)"), - level = DeprecationLevel.WARNING, - ) - public constructor( - linkDescriptionMaxLines: Int, - onContentItemClick: (context: Context, previewUrl: String) -> Unit, - canHandle: (attachments: List) -> Boolean = { links -> - links.any { it.hasLink() && !it.isGiphy() } - }, - ) : this( - linkDescriptionMaxLines = linkDescriptionMaxLines, - onItemClick = { - onContentItemClick(it.context, it.url) - }, - canHandle = canHandle, - ) -} diff --git a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/factory/MediaAttachmentFactory.kt b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/factory/MediaAttachmentFactory.kt deleted file mode 100644 index 7188e91ef89..00000000000 --- a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/factory/MediaAttachmentFactory.kt +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. - * - * Licensed under the Stream License; - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://github.com/GetStream/stream-chat-android/blob/main/LICENSE - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.getstream.chat.android.compose.ui.attachments.factory - -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.testTag -import androidx.compose.ui.tooling.preview.Preview -import io.getstream.chat.android.client.utils.attachment.isImage -import io.getstream.chat.android.client.utils.attachment.isVideo -import io.getstream.chat.android.compose.ui.attachments.AttachmentFactory -import io.getstream.chat.android.compose.ui.attachments.content.MediaAttachmentClickData -import io.getstream.chat.android.compose.ui.attachments.content.MediaAttachmentPreviewContent -import io.getstream.chat.android.compose.ui.attachments.content.onMediaAttachmentContentItemClick -import io.getstream.chat.android.compose.ui.components.common.PlayButton -import io.getstream.chat.android.compose.ui.components.common.PlayButtonSize -import io.getstream.chat.android.models.Attachment -import io.getstream.chat.android.models.AttachmentType - -/** - * An [AttachmentFactory] that is able to handle Image and Video attachments. - * - * @param maximumNumberOfPreviewedItems The maximum number of thumbnails that can be displayed - * in a group when previewing Media attachments in the message list. Values between 4 and 8 are optimal. - * @param skipEnrichUrl Used by the media gallery. If set to true will skip enriching URLs when you update the message - * by deleting an attachment contained within it. Set to false by default. - * @param onContentItemClick Lambda called when an item gets clicked. - * @param canHandle Lambda that checks if the factory can handle the given attachments. - * @param itemOverlayContent Represents the content overlaid above individual items. - * By default it is used to display a play button over video previews. - * @param previewItemOverlayContent Represents the content overlaid above individual preview items. - * By default it is used to display a play button over video previews. - */ -public class MediaAttachmentFactory( - maximumNumberOfPreviewedItems: Int = 4, - skipEnrichUrl: Boolean = false, - onContentItemClick: (MediaAttachmentClickData) -> Unit = { - onMediaAttachmentContentItemClick( - it.mediaGalleryPreviewLauncher, - it.message, - it.attachmentPosition, - it.videoThumbnailsEnabled, - it.downloadAttachmentUriGenerator, - it.downloadRequestInterceptor, - it.streamCdnImageResizing, - it.skipEnrichUrl, - ) - }, - canHandle: (attachments: List) -> Boolean = { attachments -> - attachments.all { it.isImage() || it.isVideo() } - }, - itemOverlayContent: @Composable (attachmentType: String?) -> Unit = { attachmentType -> - if (attachmentType == AttachmentType.VIDEO) { - DefaultItemOverlayContent() - } - }, - previewItemOverlayContent: @Composable (attachmentType: String?) -> Unit = { attachmentType -> - if (attachmentType == AttachmentType.VIDEO) { - DefaultPreviewItemOverlayContent() - } - }, -) : AttachmentFactory( - type = Type.BuiltIn.MEDIA, - canHandle = canHandle, - previewContent = { modifier, attachments, onAttachmentRemoved -> - MediaAttachmentPreviewContent( - attachments = attachments, - onAttachmentRemoved = onAttachmentRemoved, - modifier = modifier, - previewItemOverlayContent = previewItemOverlayContent, - ) - }, -) - -/** - * Represents the default play button that is - * overlaid above video attachment previews inside - * the messages list. - */ -@Preview(name = "DefaultItemOverlayContent Preview") -@Composable -private fun DefaultItemOverlayContent() { - PlayButton( - size = PlayButtonSize.Small, - modifier = Modifier.testTag("Stream_PlayButton"), - ) -} - -/** - * Represents the default play button that is - * overlaid above video attachment previews inside - * the message input. - */ -@Preview(name = "DefaultPreviewItemOverlayContent Preview") -@Composable -internal fun DefaultPreviewItemOverlayContent() { - PlayButton(size = PlayButtonSize.Small) -} diff --git a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/factory/UnsupportedAttachmentFactory.kt b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/factory/UnsupportedAttachmentFactory.kt deleted file mode 100644 index 102ea38c25b..00000000000 --- a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/factory/UnsupportedAttachmentFactory.kt +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. - * - * Licensed under the Stream License; - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://github.com/GetStream/stream-chat-android/blob/main/LICENSE - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.getstream.chat.android.compose.ui.attachments.factory - -import io.getstream.chat.android.compose.ui.attachments.AttachmentFactory - -/** - * An [AttachmentFactory] that will be used if no other [AttachmentFactory] can handle the attachments. - */ -public object UnsupportedAttachmentFactory : AttachmentFactory( - type = Type.BuiltIn.UNSUPPORTED, - canHandle = { true }, -) diff --git a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/attachments/images/ImagesPicker.kt b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/attachments/images/ImagesPicker.kt index 658d25b6ebb..bfd56f77b28 100644 --- a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/attachments/images/ImagesPicker.kt +++ b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/attachments/images/ImagesPicker.kt @@ -21,7 +21,6 @@ import androidx.compose.foundation.border import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding @@ -49,13 +48,13 @@ import coil3.video.VideoFrameDecoder import coil3.video.videoFrameMillis import io.getstream.chat.android.compose.R import io.getstream.chat.android.compose.state.messages.attachments.AttachmentPickerItemState +import io.getstream.chat.android.compose.ui.components.common.VideoBadge import io.getstream.chat.android.compose.ui.theme.ChatTheme import io.getstream.chat.android.compose.ui.theme.StreamTokens import io.getstream.chat.android.compose.ui.util.StreamAsyncImage import io.getstream.chat.android.compose.ui.util.clickable import io.getstream.chat.android.models.AttachmentType import io.getstream.chat.android.ui.common.state.messages.composer.AttachmentMetaData -import io.getstream.chat.android.ui.common.utils.MediaStringUtil private const val DefaultNumberOfPicturesPerRow = 3 private val ItemShape = RoundedCornerShape(2.dp) @@ -156,16 +155,21 @@ internal fun DefaultImagesPickerItem( } if (isVideo) { - VideoThumbnailOverlay( + VideoBadge( modifier = Modifier .align(Alignment.BottomStart) .padding(StreamTokens.spacingXs), - videoLength = attachmentMetaData.videoLength, + durationInSeconds = attachmentMetaData.videoLength, ) } } } +/** + * The time code of the frame to extract from a video. + */ +private const val VideoFrameMillis = 1000L + @Composable private fun SelectedIndicator( modifier: Modifier = Modifier, @@ -207,39 +211,6 @@ private fun UnselectedIndicator( ) } -@Composable -private fun VideoThumbnailOverlay( - videoLength: Long, - modifier: Modifier = Modifier, -) { - val overlayShape = RoundedCornerShape(9.dp) - - Row( - modifier = modifier - .background( - shape = overlayShape, - color = ChatTheme.colors.accentBlack, - ) - .padding( - horizontal = StreamTokens.spacingXs, - vertical = StreamTokens.spacing2xs, - ), - horizontalArrangement = Arrangement.spacedBy(StreamTokens.spacing2xs), - verticalAlignment = Alignment.CenterVertically, - ) { - Icon( - painter = painterResource(id = R.drawable.stream_compose_ic_video), - contentDescription = null, - tint = ChatTheme.colors.textOnAccent, - ) - Text( - text = MediaStringUtil.convertVideoLength(videoLength), - style = ChatTheme.typography.numericMedium, - color = ChatTheme.colors.textOnAccent, - ) - } -} - /** * Default 'pick more' tile to be shown if the user can pick more images. * @@ -273,10 +244,7 @@ private fun DefaultAddMoreItem(onPickMoreClick: () -> Unit) { } } -/** - * The time code of the frame to extract from a video. - */ -private const val VideoFrameMillis: Long = 1000 +private const val VideoLengthInSeconds = 60L @Preview(showBackground = true) @Composable @@ -299,7 +267,7 @@ internal fun ImagesPickerSelection() { ), AttachmentPickerItemState( attachmentMetaData = AttachmentMetaData(type = AttachmentType.VIDEO).apply { - videoLength = VideoFrameMillis + videoLength = VideoLengthInSeconds }, ), ), @@ -328,7 +296,7 @@ internal fun ImagesPickerAddMore() { ), AttachmentPickerItemState( attachmentMetaData = AttachmentMetaData(type = AttachmentType.VIDEO).apply { - videoLength = VideoFrameMillis + videoLength = VideoLengthInSeconds }, ), ), diff --git a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/common/MediaBadges.kt b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/common/MediaBadges.kt new file mode 100644 index 00000000000..caf99c9da01 --- /dev/null +++ b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/common/MediaBadges.kt @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-chat-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.chat.android.compose.ui.components.common + +import androidx.annotation.DrawableRes +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import io.getstream.chat.android.compose.R +import io.getstream.chat.android.compose.ui.theme.ChatTheme +import io.getstream.chat.android.compose.ui.theme.StreamTokens +import java.util.Locale +import kotlin.time.Duration.Companion.seconds + +@Composable +internal fun VideoBadge( + durationInSeconds: Long, + modifier: Modifier = Modifier, + compact: Boolean = false, +) { + MediaBadge( + durationInSeconds = durationInSeconds, + iconRes = R.drawable.stream_compose_ic_video, + modifier = modifier, + compact = compact, + ) +} + +@Composable +internal fun AudioBadge( + durationInSeconds: Long, + modifier: Modifier = Modifier, + compact: Boolean = false, +) { + MediaBadge( + durationInSeconds = durationInSeconds, + iconRes = R.drawable.stream_compose_ic_mic_solid, + modifier = modifier, + compact = compact, + ) +} + +@Composable +private fun MediaBadge( + durationInSeconds: Long, + @DrawableRes iconRes: Int, + modifier: Modifier = Modifier, + compact: Boolean = false, +) { + Row( + modifier = modifier + .background( + shape = MediaBadgeShape, + color = ChatTheme.colors.accentBlack, + ) + .padding( + horizontal = StreamTokens.spacingXs, + vertical = StreamTokens.spacing2xs, + ), + horizontalArrangement = Arrangement.spacedBy(StreamTokens.spacing2xs), + verticalAlignment = Alignment.CenterVertically, + ) { + Icon( + painter = painterResource(iconRes), + contentDescription = null, + tint = ChatTheme.colors.textOnAccent, + ) + Text( + text = if (compact) { + durationInSeconds.toCompactDuration() + } else { + durationInSeconds.toPreciseDuration() + }, + style = ChatTheme.typography.numericMedium, + color = ChatTheme.colors.textOnAccent, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + } +} + +private val MediaBadgeShape = RoundedCornerShape(StreamTokens.radiusFull) + +private fun Long.toCompactDuration(): String = seconds.toComponents { hours, minutes, secs, _ -> + when { + hours > 0 -> "${hours}h" + minutes > 0 -> "${minutes}m" + else -> "${secs}s" + } +} + +private fun Long.toPreciseDuration(): String = seconds.toComponents { hours, minutes, secs, _ -> + if (hours > 0) { + String.format(Locale.ROOT, "%d:%02d:%02d", hours, minutes, secs) + } else { + String.format(Locale.ROOT, "%d:%02d", minutes, secs) + } +} + +@Preview +@Composable +private fun MediaBadgeCompactPreview() { + ChatTheme { + Row { + VideoBadge(durationInSeconds = 8, compact = true) + AudioBadge(durationInSeconds = 8, compact = true) + } + } +} + +@Preview +@Composable +private fun MediaBadgePrecisePreview() { + ChatTheme { + Row { + VideoBadge(durationInSeconds = 8) + AudioBadge(durationInSeconds = 8) + } + } +} diff --git a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/common/PlaybackSpeedToggle.kt b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/common/PlaybackSpeedToggle.kt new file mode 100644 index 00000000000..faf19eec57f --- /dev/null +++ b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/common/PlaybackSpeedToggle.kt @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-chat-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.chat.android.compose.ui.components.common + +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import io.getstream.chat.android.compose.ui.theme.ChatTheme +import io.getstream.chat.android.compose.ui.theme.StreamTokens +import io.getstream.chat.android.compose.ui.util.applyIf +import io.getstream.chat.android.compose.ui.util.clickable +import io.getstream.chat.android.extensions.isInt + +@Composable +internal fun PlaybackSpeedToggle( + speed: Float, + outlineColor: Color = ChatTheme.colors.controlPlaybackToggleBorder, + enabled: Boolean = true, + onClick: () -> Unit = {}, +) { + val colors = ChatTheme.colors + val textColor = if (enabled) colors.controlPlaybackToggleText else colors.textDisabled + val borderColor = if (enabled) outlineColor else colors.borderUtilityDisabled + Text( + text = if (speed.isInt()) "x${speed.toInt()}" else "x$speed", + style = ChatTheme.typography.metadataEmphasis, + color = textColor, + modifier = Modifier + .border(1.dp, borderColor, SpeedToggleShape) + .clip(SpeedToggleShape) + .applyIf(enabled) { clickable(onClick = onClick) } + .padding(horizontal = StreamTokens.spacingXs, vertical = StreamTokens.spacing2xs), + ) +} + +private val SpeedToggleShape = RoundedCornerShape(StreamTokens.radiusLg) + +@Preview +@Composable +private fun PlaybackSpeedTogglePreview() { + ChatTheme { + Row(modifier = Modifier.padding(16.dp)) { + PlaybackSpeedToggle( + speed = 1.5f, + ) + PlaybackSpeedToggle( + speed = 1.5f, + enabled = false, + ) + } + } +} diff --git a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/composer/MessageInput.kt b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/composer/MessageInput.kt index bdae8030f94..ca117e673d7 100644 --- a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/composer/MessageInput.kt +++ b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/composer/MessageInput.kt @@ -27,7 +27,6 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.minimumInteractiveComponentSize import androidx.compose.runtime.Composable @@ -41,6 +40,7 @@ import io.getstream.chat.android.compose.ui.messages.composer.actions.AudioRecor import io.getstream.chat.android.compose.ui.theme.ChatTheme import io.getstream.chat.android.compose.ui.theme.ComposerConfig import io.getstream.chat.android.compose.ui.theme.LocalChatConfig +import io.getstream.chat.android.compose.ui.theme.MessageComposerAttachmentsParams import io.getstream.chat.android.compose.ui.theme.MessageComposerInputLeadingContentParams import io.getstream.chat.android.compose.ui.theme.StreamTokens import io.getstream.chat.android.compose.util.extensions.toSet @@ -205,14 +205,11 @@ private fun MessageInputTop( } if (showAttachments) { - val previewFactory = ChatTheme.attachmentFactories.firstOrNull { it.canHandle(attachments) } - - previewFactory?.previewContent?.invoke( - Modifier - .fillMaxWidth() - .wrapContentHeight(), - attachments, - onAttachmentRemoved, + ChatTheme.componentFactory.MessageComposerAttachments( + params = MessageComposerAttachmentsParams( + attachments = attachments, + onAttachmentRemoved = onAttachmentRemoved, + ), ) } diff --git a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/MessagesScreen.kt b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/MessagesScreen.kt index 357befa0dee..3204be00cde 100644 --- a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/MessagesScreen.kt +++ b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/MessagesScreen.kt @@ -55,7 +55,6 @@ import io.getstream.chat.android.compose.ui.components.moderatedmessage.Moderate import io.getstream.chat.android.compose.ui.components.poll.PollAnswersDialog import io.getstream.chat.android.compose.ui.components.poll.PollMoreOptionsDialog import io.getstream.chat.android.compose.ui.components.poll.PollViewResultDialog -import io.getstream.chat.android.compose.ui.messages.attachments.AttachmentPickerMenu import io.getstream.chat.android.compose.ui.messages.composer.MessageComposer import io.getstream.chat.android.compose.ui.messages.list.LocalSelectedMessageBounds import io.getstream.chat.android.compose.ui.messages.list.MessageList @@ -373,7 +372,7 @@ internal fun DefaultBottomBarContent( }, ) - AttachmentPickerMenu( + ChatTheme.componentFactory.AttachmentPickerMenu( attachmentsPickerViewModel = attachmentsPickerViewModel, composerViewModel = composerViewModel, ) diff --git a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/composer/internal/attachments/MessageComposerAttachmentAudioRecordItem.kt b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/composer/internal/attachments/MessageComposerAttachmentAudioRecordItem.kt new file mode 100644 index 00000000000..7f281e2c809 --- /dev/null +++ b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/composer/internal/attachments/MessageComposerAttachmentAudioRecordItem.kt @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-chat-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.chat.android.compose.ui.messages.composer.internal.attachments + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import io.getstream.chat.android.client.audio.audioHash +import io.getstream.chat.android.compose.ui.attachments.content.AudioRecordAttachmentContentItemBase +import io.getstream.chat.android.compose.ui.components.ComposerCancelIcon +import io.getstream.chat.android.compose.ui.components.common.PlaybackSpeedToggle +import io.getstream.chat.android.compose.ui.theme.ChatPreviewTheme +import io.getstream.chat.android.compose.ui.theme.ChatTheme +import io.getstream.chat.android.compose.ui.theme.StreamTokens +import io.getstream.chat.android.models.Attachment +import io.getstream.chat.android.previewdata.PreviewAttachmentData +import io.getstream.chat.android.ui.common.state.messages.list.AudioPlayerState + +@Composable +internal fun MessageComposerAttachmentAudioRecordItem( + attachment: Attachment, + playerState: AudioPlayerState, + modifier: Modifier = Modifier, + onPlayToggleClick: (Attachment) -> Unit = {}, + onPlaySpeedClick: (Attachment) -> Unit = {}, + onThumbDragStart: (Attachment) -> Unit = {}, + onThumbDragStop: (Attachment, Float) -> Unit = { _, _ -> }, + onAttachmentRemoved: (Attachment) -> Unit = {}, +) { + val currentAttachment by rememberUpdatedState(attachment) + Box(modifier = modifier) { + AudioRecordAttachmentContentItemBase( + modifier = Modifier + .padding(StreamTokens.spacing2xs) + .border(1.dp, ChatTheme.colors.borderCoreDefault, AudioRecordItemShape) + .clip(AudioRecordItemShape) + .background(ChatTheme.colors.backgroundCoreApp) + .size(width = 290.dp, height = 72.dp), + contentPadding = PaddingValues(StreamTokens.spacingSm), + attachment = attachment, + playerState = playerState, + outlineColor = ChatTheme.colors.borderCoreDefault, + textColor = ChatTheme.colors.textSecondary, + waveformHeight = 36.dp, + onPlayToggleClick = onPlayToggleClick, + onThumbDragStart = onThumbDragStart, + onThumbDragStop = onThumbDragStop, + tailContent = { + val speed = playerState.speeds.getOrDefault(attachment.audioHash, 1f) + PlaybackSpeedToggle( + speed = speed, + onClick = { onPlaySpeedClick(currentAttachment) }, + ) + }, + ) + + ComposerCancelIcon( + modifier = Modifier + .align(Alignment.TopEnd) + .testTag("Stream_MessageComposerAttachmentCancelIcon"), + onClick = { onAttachmentRemoved(currentAttachment) }, + ) + } +} + +private val AudioRecordItemShape = RoundedCornerShape(StreamTokens.radiusLg) + +@Preview +@Composable +private fun MessageComposerAttachmentAudioRecordItemPreview() { + ChatPreviewTheme { + MessageComposerAttachmentAudioRecordItem() + } +} + +@Composable +internal fun MessageComposerAttachmentAudioRecordItem() { + MessageComposerAttachmentAudioRecordItem( + attachment = PreviewAttachmentData.attachmentAudioRecording1, + playerState = AudioPlayerState(getRecordingUri = Attachment::assetUrl), + ) +} diff --git a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/composer/internal/attachments/MessageComposerAttachmentFileItem.kt b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/composer/internal/attachments/MessageComposerAttachmentFileItem.kt new file mode 100644 index 00000000000..c934987adfd --- /dev/null +++ b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/composer/internal/attachments/MessageComposerAttachmentFileItem.kt @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-chat-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.chat.android.compose.ui.messages.composer.internal.attachments + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import io.getstream.chat.android.compose.ui.attachments.content.FileAttachmentImage +import io.getstream.chat.android.compose.ui.components.ComposerCancelIcon +import io.getstream.chat.android.compose.ui.theme.ChatTheme +import io.getstream.chat.android.compose.ui.theme.StreamTokens +import io.getstream.chat.android.models.Attachment +import io.getstream.chat.android.previewdata.PreviewAttachmentData +import io.getstream.chat.android.ui.common.utils.MediaStringUtil + +@Composable +internal fun MessageComposerAttachmentFileItem( + attachment: Attachment, + modifier: Modifier = Modifier, + onAttachmentRemoved: (Attachment) -> Unit = {}, +) { + Box(modifier = modifier) { + Row( + modifier = Modifier + .padding(StreamTokens.spacing2xs) + .border(1.dp, ChatTheme.colors.borderCoreDefault, FileItemShape) + .clip(FileItemShape) + .background(ChatTheme.colors.backgroundCoreApp) + .size(width = 260.dp, height = 72.dp) + .padding( + start = StreamTokens.spacingMd, + end = StreamTokens.spacingSm, + top = StreamTokens.spacingMd, + bottom = StreamTokens.spacingMd, + ), + horizontalArrangement = Arrangement.spacedBy(StreamTokens.spacingSm), + verticalAlignment = Alignment.CenterVertically, + ) { + FileAttachmentImage(attachment = attachment, isMine = true) + + Column( + modifier = Modifier.weight(1f), + verticalArrangement = Arrangement.spacedBy(StreamTokens.spacing2xs), + ) { + Text( + modifier = Modifier.testTag("Stream_MessageComposerAttachmentFileName"), + text = attachment.title ?: attachment.name ?: "", + style = ChatTheme.typography.bodyEmphasis, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + color = ChatTheme.colors.textPrimary, + ) + + val fileSize = remember(attachment.fileSize) { + attachment.fileSize.takeIf { it > 0 }?.let { + MediaStringUtil.convertFileSizeByteCount(it.toLong()) + } + } + if (fileSize != null) { + Text( + modifier = Modifier.testTag("Stream_MessageComposerAttachmentFileSize"), + text = fileSize, + style = ChatTheme.typography.metadataDefault, + color = ChatTheme.colors.textSecondary, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + } + } + } + + ComposerCancelIcon( + modifier = Modifier + .align(Alignment.TopEnd) + .testTag("Stream_MessageComposerAttachmentCancelIcon"), + onClick = { onAttachmentRemoved(attachment) }, + ) + } +} + +private val FileItemShape = RoundedCornerShape(StreamTokens.radiusLg) + +@Preview +@Composable +private fun MessageComposerAttachmentFileItemPreview() { + ChatTheme { + MessageComposerAttachmentFileItem() + } +} + +@Composable +internal fun MessageComposerAttachmentFileItem() { + MessageComposerAttachmentFileItem( + attachment = PreviewAttachmentData.attachmentFile1, + ) +} diff --git a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/composer/internal/attachments/MessageComposerAttachmentMediaItem.kt b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/composer/internal/attachments/MessageComposerAttachmentMediaItem.kt new file mode 100644 index 00000000000..9fe1f08f924 --- /dev/null +++ b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/composer/internal/attachments/MessageComposerAttachmentMediaItem.kt @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-chat-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.chat.android.compose.ui.messages.composer.internal.attachments + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import coil3.ColorImage +import coil3.compose.LocalAsyncImagePreviewHandler +import io.getstream.chat.android.client.extensions.duration +import io.getstream.chat.android.compose.ui.components.ComposerCancelIcon +import io.getstream.chat.android.compose.ui.components.common.VideoBadge +import io.getstream.chat.android.compose.ui.theme.ChatTheme +import io.getstream.chat.android.compose.ui.theme.StreamTokens +import io.getstream.chat.android.compose.ui.util.AsyncImagePreviewHandler +import io.getstream.chat.android.compose.ui.util.StreamAsyncImage +import io.getstream.chat.android.compose.ui.util.extensions.internal.localPreviewData +import io.getstream.chat.android.models.Attachment +import io.getstream.chat.android.models.AttachmentType +import io.getstream.chat.android.previewdata.PreviewAttachmentData + +@Composable +internal fun MessageComposerAttachmentMediaItem( + attachment: Attachment, + modifier: Modifier = Modifier, + onAttachmentRemoved: (Attachment) -> Unit = {}, +) { + val data = attachment.localPreviewData + + Box( + modifier = modifier.testTag("Stream_MessageComposerAttachmentMediaItem"), + ) { + Box( + modifier = Modifier + .padding(StreamTokens.spacing2xs) + .size(72.dp) + .clip(MediaItemImageShape), + ) { + StreamAsyncImage( + modifier = Modifier.fillMaxSize(), + data = data, + contentDescription = null, + contentScale = ContentScale.Crop, + ) + + if (attachment.type == AttachmentType.VIDEO) { + VideoBadge( + modifier = Modifier + .align(Alignment.BottomStart) + .padding(StreamTokens.spacing2xs), + durationInSeconds = attachment.duration?.toLong() ?: 0, + compact = true, + ) + } + } + + ComposerCancelIcon( + modifier = Modifier + .align(Alignment.TopEnd) + .testTag("Stream_MessageComposerAttachmentCancelIcon"), + onClick = { onAttachmentRemoved(attachment) }, + ) + } +} + +private val MediaItemImageShape = RoundedCornerShape(StreamTokens.radiusLg) + +@Preview +@Composable +private fun MessageComposerAttachmentImageItemPreview() { + ChatTheme { + MessageComposerAttachmentImageItem() + } +} + +@Composable +internal fun MessageComposerAttachmentImageItem() { + val previewHandler = AsyncImagePreviewHandler { + ColorImage(color = Color.Green.toArgb(), width = 200, height = 150) + } + CompositionLocalProvider(LocalAsyncImagePreviewHandler provides previewHandler) { + MessageComposerAttachmentMediaItem( + attachment = PreviewAttachmentData.attachmentImage1, + ) + } +} + +@Preview +@Composable +private fun MessageComposerAttachmentVideoItemPreview() { + ChatTheme { + MessageComposerAttachmentVideoItem() + } +} + +@Composable +internal fun MessageComposerAttachmentVideoItem() { + val previewHandler = AsyncImagePreviewHandler { + ColorImage(color = Color.Green.toArgb(), width = 200, height = 150) + } + CompositionLocalProvider(LocalAsyncImagePreviewHandler provides previewHandler) { + MessageComposerAttachmentMediaItem( + attachment = PreviewAttachmentData.attachmentVideo1, + ) + } +} diff --git a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/composer/internal/attachments/MessageComposerAttachments.kt b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/composer/internal/attachments/MessageComposerAttachments.kt new file mode 100644 index 00000000000..df0875ecc3a --- /dev/null +++ b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/composer/internal/attachments/MessageComposerAttachments.kt @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-chat-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.chat.android.compose.ui.messages.composer.internal.attachments + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.items +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalInspectionMode +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.tooling.preview.Preview +import androidx.core.net.toUri +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.compose.LifecycleEventEffect +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.lifecycle.viewmodel.compose.viewModel +import io.getstream.chat.android.client.ChatClient +import io.getstream.chat.android.client.utils.attachment.isAudioRecording +import io.getstream.chat.android.client.utils.attachment.isImage +import io.getstream.chat.android.client.utils.attachment.isVideo +import io.getstream.chat.android.compose.ui.theme.ChatTheme +import io.getstream.chat.android.compose.ui.theme.MessageComposerAttachmentAudioRecordItemParams +import io.getstream.chat.android.compose.ui.theme.MessageComposerAttachmentFileItemParams +import io.getstream.chat.android.compose.ui.theme.MessageComposerAttachmentMediaItemParams +import io.getstream.chat.android.compose.ui.theme.StreamTokens +import io.getstream.chat.android.compose.ui.util.extensions.internal.stableKey +import io.getstream.chat.android.compose.ui.util.rememberAutoScrollLazyListState +import io.getstream.chat.android.compose.viewmodel.messages.AudioPlayerViewModel +import io.getstream.chat.android.compose.viewmodel.messages.AudioPlayerViewModelFactory +import io.getstream.chat.android.models.Attachment +import io.getstream.chat.android.previewdata.PreviewAttachmentData +import io.getstream.chat.android.ui.common.state.messages.list.AudioPlayerState + +/** + * Renders all selected attachments in the message composer as a single horizontal scrolling row. + */ +@Composable +internal fun MessageComposerAttachments( + attachments: List, + modifier: Modifier = Modifier, + onAttachmentRemoved: (Attachment) -> Unit = {}, +) { + if (LocalInspectionMode.current) { + MessageComposerAttachmentsContent( + modifier = modifier, + attachments = attachments, + playerState = AudioPlayerState(getRecordingUri = Attachment::assetUrl), + onAttachmentRemoved = onAttachmentRemoved, + ) + } else { + val viewModelFactory = remember { + AudioPlayerViewModelFactory( + getAudioPlayer = { ChatClient.instance().audioPlayer }, + getRecordingUri = { it.assetUrl ?: it.upload?.toUri()?.toString() }, + ) + } + val audioPlayerViewModel = viewModel(AudioPlayerViewModel::class.java, factory = viewModelFactory) + val playerState by audioPlayerViewModel.state.collectAsStateWithLifecycle() + + LifecycleEventEffect(event = Lifecycle.Event.ON_PAUSE) { + audioPlayerViewModel.pause() + } + + MessageComposerAttachmentsContent( + modifier = modifier, + attachments = attachments, + playerState = playerState, + onPlayToggleClick = audioPlayerViewModel::playOrPause, + onPlaySpeedClick = audioPlayerViewModel::changeSpeed, + onThumbDragStart = audioPlayerViewModel::startSeek, + onThumbDragStop = audioPlayerViewModel::seekTo, + onAttachmentRemoved = { + audioPlayerViewModel.reset(it) + onAttachmentRemoved(it) + }, + ) + } +} + +@Composable +private fun MessageComposerAttachmentsContent( + attachments: List, + playerState: AudioPlayerState, + modifier: Modifier = Modifier, + onPlayToggleClick: (Attachment) -> Unit = {}, + onPlaySpeedClick: (Attachment) -> Unit = {}, + onThumbDragStart: (Attachment) -> Unit = {}, + onThumbDragStop: (Attachment, Float) -> Unit = { _, _ -> }, + onAttachmentRemoved: (Attachment) -> Unit = {}, +) { + LazyRow( + state = rememberAutoScrollLazyListState(attachments.size), + modifier = modifier.testTag("Stream_MessageComposerAttachments"), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(StreamTokens.spacing2xs, Alignment.Start), + contentPadding = PaddingValues(horizontal = StreamTokens.spacingSm), + ) { + items( + items = attachments, + key = Attachment::stableKey, + ) { attachment -> + when { + attachment.isAudioRecording() -> + ChatTheme.componentFactory.MessageComposerAttachmentAudioRecordItem( + params = MessageComposerAttachmentAudioRecordItemParams( + modifier = Modifier.animateItem(), + attachment = attachment, + playerState = playerState, + onPlayToggleClick = onPlayToggleClick, + onPlaySpeedClick = onPlaySpeedClick, + onThumbDragStart = onThumbDragStart, + onThumbDragStop = onThumbDragStop, + onAttachmentRemoved = onAttachmentRemoved, + ), + ) + + attachment.isImage() || attachment.isVideo() -> + ChatTheme.componentFactory.MessageComposerAttachmentMediaItem( + params = MessageComposerAttachmentMediaItemParams( + modifier = Modifier.animateItem(), + attachment = attachment, + onAttachmentRemoved = onAttachmentRemoved, + ), + ) + + else -> ChatTheme.componentFactory.MessageComposerAttachmentFileItem( + params = MessageComposerAttachmentFileItemParams( + modifier = Modifier.animateItem(), + attachment = attachment, + onAttachmentRemoved = onAttachmentRemoved, + ), + ) + } + } + } +} + +@Preview(widthDp = 640) +@Composable +private fun MessageComposerAttachmentsPreview() { + ChatTheme { + MessageComposerAttachments() + } +} + +@Composable +internal fun MessageComposerAttachments() { + MessageComposerAttachments( + attachments = listOf( + PreviewAttachmentData.attachmentImage1, + PreviewAttachmentData.attachmentVideo1, + PreviewAttachmentData.attachmentFile1, + PreviewAttachmentData.attachmentAudioRecording1, + ), + ) +} diff --git a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/ChatComponentFactory.kt b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/ChatComponentFactory.kt index e2715bdf6d0..f2f94a95244 100644 --- a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/ChatComponentFactory.kt +++ b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/ChatComponentFactory.kt @@ -143,6 +143,10 @@ import io.getstream.chat.android.compose.ui.messages.attachments.AttachmentPicke import io.getstream.chat.android.compose.ui.messages.composer.actions.AudioRecordingActions import io.getstream.chat.android.compose.ui.messages.composer.internal.AudioRecordingButton import io.getstream.chat.android.compose.ui.messages.composer.internal.MessageComposerEditIndicator +import io.getstream.chat.android.compose.ui.messages.composer.internal.attachments.MessageComposerAttachmentAudioRecordItem +import io.getstream.chat.android.compose.ui.messages.composer.internal.attachments.MessageComposerAttachmentFileItem +import io.getstream.chat.android.compose.ui.messages.composer.internal.attachments.MessageComposerAttachmentMediaItem +import io.getstream.chat.android.compose.ui.messages.composer.internal.attachments.MessageComposerAttachments import io.getstream.chat.android.compose.ui.messages.composer.internal.suggestions.CommandSuggestionItem import io.getstream.chat.android.compose.ui.messages.composer.internal.suggestions.DefaultCommandSuggestionItemCenterContent import io.getstream.chat.android.compose.ui.messages.composer.internal.suggestions.DefaultCommandSuggestionItemLeadingContent @@ -2796,22 +2800,69 @@ public interface ChatComponentFactory { } /** - * Factory method for creating the preview content of file attachments. + * Renders all selected attachments in the message composer as a single horizontal scrolling row. * - * @param modifier Modifier for styling. - * @param attachments List of file attachments to preview. - * @param onAttachmentRemoved Lambda invoked when an attachment is removed. + * @param params Parameters for this component. */ @Composable - public fun FileAttachmentPreviewContent( - modifier: Modifier, - attachments: List, - onAttachmentRemoved: (Attachment) -> Unit, - ) { - io.getstream.chat.android.compose.ui.attachments.content.FileAttachmentPreviewContent( - modifier = modifier, - attachments = attachments, - onAttachmentRemoved = onAttachmentRemoved, + public fun MessageComposerAttachments(params: MessageComposerAttachmentsParams) { + MessageComposerAttachments( + modifier = params.modifier, + attachments = params.attachments, + onAttachmentRemoved = params.onAttachmentRemoved, + ) + } + + /** + * Renders a single audio recording attachment item in the message composer tray. + * + * Used as part of [MessageComposerAttachments]. + * + * @param params Parameters for this component. + */ + @Composable + public fun MessageComposerAttachmentAudioRecordItem(params: MessageComposerAttachmentAudioRecordItemParams) { + MessageComposerAttachmentAudioRecordItem( + modifier = params.modifier, + attachment = params.attachment, + playerState = params.playerState, + onPlayToggleClick = params.onPlayToggleClick, + onPlaySpeedClick = params.onPlaySpeedClick, + onThumbDragStart = params.onThumbDragStart, + onThumbDragStop = params.onThumbDragStop, + onAttachmentRemoved = params.onAttachmentRemoved, + ) + } + + /** + * Renders a single media (image or video) attachment item in the message composer tray. + * + * Used as part of [MessageComposerAttachments]. + * + * @param params Parameters for this component. + */ + @Composable + public fun MessageComposerAttachmentMediaItem(params: MessageComposerAttachmentMediaItemParams) { + MessageComposerAttachmentMediaItem( + modifier = params.modifier, + attachment = params.attachment, + onAttachmentRemoved = params.onAttachmentRemoved, + ) + } + + /** + * Renders a single generic file attachment item in the message composer tray. + * + * Used as part of [MessageComposerAttachments]. + * + * @param params Parameters for this component. + */ + @Composable + public fun MessageComposerAttachmentFileItem(params: MessageComposerAttachmentFileItemParams) { + MessageComposerAttachmentFileItem( + modifier = params.modifier, + attachment = params.attachment, + onAttachmentRemoved = params.onAttachmentRemoved, ) } diff --git a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/ChatComponentFactoryParams.kt b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/ChatComponentFactoryParams.kt index 064153cb284..789e8309386 100644 --- a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/ChatComponentFactoryParams.kt +++ b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/ChatComponentFactoryParams.kt @@ -19,8 +19,10 @@ package io.getstream.chat.android.compose.ui.theme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import io.getstream.chat.android.compose.state.messages.MessageReactionItemState +import io.getstream.chat.android.models.Attachment import io.getstream.chat.android.models.Message import io.getstream.chat.android.ui.common.state.messages.composer.MessageComposerState +import io.getstream.chat.android.ui.common.state.messages.list.AudioPlayerState import io.getstream.chat.android.ui.common.state.messages.list.MessageItemState /** @@ -74,3 +76,65 @@ public data class MessageComposerInputLeadingContentParams( val onActiveCommandDismiss: () -> Unit, val modifier: Modifier = Modifier, ) + +/** + * Parameters for the [ChatComponentFactory.MessageComposerAttachments] component. + * + * @param attachments The attachments currently selected in the composer. + * @param onAttachmentRemoved Lambda invoked when an attachment is removed by the user. + * @param modifier Modifier for styling. + */ +public data class MessageComposerAttachmentsParams( + val attachments: List, + val onAttachmentRemoved: (Attachment) -> Unit, + val modifier: Modifier = Modifier, +) + +/** + * Parameters for the [ChatComponentFactory.MessageComposerAttachmentAudioRecordItem] component. + * + * @param attachment The audio recording attachment to render. + * @param playerState Current state of the audio player. + * @param modifier Modifier for styling. + * @param onPlayToggleClick Called when the play/pause button is tapped. + * @param onPlaySpeedClick Called when the playback speed button is tapped. + * @param onThumbDragStart Called when the user starts dragging the waveform thumb. + * @param onThumbDragStop Called when the user stops dragging, with the target seek fraction. + * @param onAttachmentRemoved Called when the attachment is removed by the user. + */ +public data class MessageComposerAttachmentAudioRecordItemParams( + val attachment: Attachment, + val playerState: AudioPlayerState, + val modifier: Modifier = Modifier, + val onPlayToggleClick: (Attachment) -> Unit = {}, + val onPlaySpeedClick: (Attachment) -> Unit = {}, + val onThumbDragStart: (Attachment) -> Unit = {}, + val onThumbDragStop: (Attachment, Float) -> Unit = { _, _ -> }, + val onAttachmentRemoved: (Attachment) -> Unit = {}, +) + +/** + * Parameters for the [ChatComponentFactory.MessageComposerAttachmentMediaItem] component. + * + * @param attachment The image or video attachment to render. + * @param onAttachmentRemoved Called when the attachment is removed by the user. + * @param modifier Modifier for styling. + */ +public data class MessageComposerAttachmentMediaItemParams( + val attachment: Attachment, + val onAttachmentRemoved: (Attachment) -> Unit, + val modifier: Modifier = Modifier, +) + +/** + * Parameters for the [ChatComponentFactory.MessageComposerAttachmentFileItem] component. + * + * @param attachment The file attachment to render. + * @param onAttachmentRemoved Called when the attachment is removed by the user. + * @param modifier Modifier for styling. + */ +public data class MessageComposerAttachmentFileItemParams( + val attachment: Attachment, + val onAttachmentRemoved: (Attachment) -> Unit, + val modifier: Modifier = Modifier, +) diff --git a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/ChatTheme.kt b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/ChatTheme.kt index 78cb07fe4bb..1fa5c6c548f 100644 --- a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/ChatTheme.kt +++ b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/ChatTheme.kt @@ -38,8 +38,6 @@ import coil3.ImageLoader import com.valentinilk.shimmer.LocalShimmerTheme import io.getstream.chat.android.client.ChatClient import io.getstream.chat.android.client.header.VersionPrefixHeader -import io.getstream.chat.android.compose.ui.attachments.AttachmentFactory -import io.getstream.chat.android.compose.ui.attachments.StreamAttachmentFactories import io.getstream.chat.android.compose.ui.attachments.preview.handler.AttachmentPreviewHandler import io.getstream.chat.android.compose.ui.util.LocalStreamImageLoader import io.getstream.chat.android.compose.ui.util.MessageAlignmentProvider @@ -91,10 +89,6 @@ private val LocalUserPresence = compositionLocalOf { public val LocalComponentFactory: ProvidableCompositionLocal = compositionLocalOf { error("No ComponentFactory provided! Make sure to wrap all usages of Stream components in a ChatTheme.") } -private val LocalAttachmentFactories = compositionLocalOf> { - error("No attachment factories provided! Make sure to wrap all usages of Stream components in a ChatTheme.") -} - private val LocalAttachmentPreviewHandlers = compositionLocalOf> { error("No attachment preview handlers provided! Make sure to wrap all usages of Stream components in a ChatTheme.") } @@ -176,7 +170,6 @@ private val LocalStreamMediaRecorder = compositionLocalOf { * @param rippleConfiguration Defines the appearance for ripples. * @param userPresence The user presence display configuration. * @param componentFactory Provide to customize the stateless components that are used throughout the UI - * @param attachmentFactories Attachment factories that we provide. * @param attachmentPreviewHandlers Attachment preview handlers we provide. * @param reactionResolver Provides available reactions and resolves reaction types to emoji codes. * @param reactionOptionsTheme [ReactionOptionsTheme] Theme for the reaction option list in the selected message menu. @@ -221,7 +214,6 @@ public fun ChatTheme( ), userPresence: UserPresence = UserPresence(), componentFactory: ChatComponentFactory = DefaultChatComponentFactory(), - attachmentFactories: List = StreamAttachmentFactories.defaults(), attachmentPreviewHandlers: List = AttachmentPreviewHandler.defaultAttachmentHandlers(LocalContext.current), reactionResolver: ReactionResolver = ReactionResolver.defaultResolver(), @@ -235,7 +227,6 @@ public fun ChatTheme( messagePreviewFormatter: MessagePreviewFormatter = MessagePreviewFormatter.defaultFormatter( context = LocalContext.current, typography = typography, - attachmentFactories = attachmentFactories, autoTranslationEnabled = config.translation.enabled, colors = colors, ), @@ -274,7 +265,6 @@ public fun ChatTheme( LocalShimmerTheme provides StreamShimmerTheme, LocalUserPresence provides userPresence, LocalComponentFactory provides componentFactory, - LocalAttachmentFactories provides attachmentFactories, LocalAttachmentPreviewHandlers provides attachmentPreviewHandlers, LocalReactionResolver provides reactionResolver, LocalMessagePreviewIconFactory provides messagePreviewIconFactory, @@ -355,14 +345,6 @@ public object ChatTheme { @ReadOnlyComposable get() = LocalComponentFactory.current - /** - * Retrieves the current list of [AttachmentFactory] at the call site's position in the hierarchy. - */ - public val attachmentFactories: List - @Composable - @ReadOnlyComposable - get() = LocalAttachmentFactories.current - /** * Retrieves the current list of [AttachmentPreviewHandler] at the call site's position in the hierarchy. */ diff --git a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/util/MessagePreviewFormatter.kt b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/util/MessagePreviewFormatter.kt index 881aa0b3312..f007c63e6fd 100644 --- a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/util/MessagePreviewFormatter.kt +++ b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/util/MessagePreviewFormatter.kt @@ -32,7 +32,6 @@ import io.getstream.chat.android.client.utils.message.isPoll import io.getstream.chat.android.client.utils.message.isPollClosed import io.getstream.chat.android.client.utils.message.isSystem import io.getstream.chat.android.compose.R -import io.getstream.chat.android.compose.ui.attachments.AttachmentFactory import io.getstream.chat.android.compose.ui.theme.StreamDesign import io.getstream.chat.android.models.Attachment import io.getstream.chat.android.models.AttachmentType @@ -84,7 +83,7 @@ public interface MessagePreviewFormatter { * @param context The context to load string resources. * @param autoTranslationEnabled Whether the auto-translation is enabled. * @param typography The typography to use for styling. - * @param attachmentFactories The list of [AttachmentFactory] to use for formatting attachments. + * @param colors The color palette used for styling. * @return The default implementation of [MessagePreviewFormatter]. * * @see [DefaultMessagePreviewFormatter] @@ -94,7 +93,6 @@ public interface MessagePreviewFormatter { context: Context, autoTranslationEnabled: Boolean, typography: StreamDesign.Typography, - attachmentFactories: List, colors: StreamDesign.Colors, ): MessagePreviewFormatter { return DefaultMessagePreviewFormatter( @@ -105,7 +103,6 @@ public interface MessagePreviewFormatter { senderNameTextStyle = typography.bodyEmphasis, // TODO: replace with a dedicated italic token once the design system provides one attachmentTextFontStyle = typography.bodyDefault.copy(fontStyle = FontStyle.Italic), - attachmentFactories = attachmentFactories, ) } } @@ -117,7 +114,6 @@ public interface MessagePreviewFormatter { * * @param context The context to load string resources. */ -@Suppress("LongParameterList") private class DefaultMessagePreviewFormatter( private val context: Context, private val autoTranslationEnabled: Boolean, @@ -125,7 +121,6 @@ private class DefaultMessagePreviewFormatter( private val messageTextStyle: TextStyle, private val senderNameTextStyle: TextStyle, private val attachmentTextFontStyle: TextStyle, - private val attachmentFactories: List, ) : MessagePreviewFormatter { private companion object { @@ -295,7 +290,7 @@ private class DefaultMessagePreviewFormatter( else -> { // No recognizable typed attachment — use default text + attachment formatting appendMessageText(displayedText, messageTextStyle) - appendAttachmentText(attachments, attachmentFactories, attachmentTextFontStyle) + appendAttachmentText(attachments, attachmentTextFontStyle) } } } @@ -399,37 +394,27 @@ private class DefaultMessagePreviewFormatter( } /** - * Appends a string representations of [attachments] to the [AnnotatedString]. + * Appends a string representation of [attachments] to the [AnnotatedString]. + * Falls back to the attachment's title, name, or fallback field for each item. */ private fun AnnotatedString.Builder.appendAttachmentText( attachments: List, - attachmentFactories: List, attachmentTextStyle: TextStyle, ) { - if (attachments.isNotEmpty()) { - attachmentFactories - .firstOrNull { it.canHandle(attachments) } - ?.textFormatter - ?.let { textFormatter -> - attachments.mapNotNull { attachment -> - textFormatter.invoke(attachment) - .let { previewText -> - previewText.ifEmpty { null } - } - }.joinToString() - }?.let { attachmentText -> - val startIndex = this.length - append(attachmentText) - - addStyle( - SpanStyle( - fontStyle = attachmentTextStyle.fontStyle, - fontFamily = attachmentTextStyle.fontFamily, - ), - start = startIndex, - end = startIndex + attachmentText.length, - ) - } - } + if (attachments.isEmpty()) return + val attachmentText = attachments.mapNotNull { attachment -> + (attachment.title ?: attachment.name ?: attachment.fallback ?: "").ifEmpty { null } + }.joinToString() + if (attachmentText.isEmpty()) return + val startIndex = length + append(attachmentText) + addStyle( + SpanStyle( + fontStyle = attachmentTextStyle.fontStyle, + fontFamily = attachmentTextStyle.fontFamily, + ), + start = startIndex, + end = startIndex + attachmentText.length, + ) } } diff --git a/stream-chat-android-compose/src/main/res/drawable/stream_compose_ic_mic_solid.xml b/stream-chat-android-compose/src/main/res/drawable/stream_compose_ic_mic_solid.xml new file mode 100644 index 00000000000..c97a8624a6e --- /dev/null +++ b/stream-chat-android-compose/src/main/res/drawable/stream_compose_ic_mic_solid.xml @@ -0,0 +1,35 @@ + + + + + + + + + diff --git a/stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/ui/PaparazziComposeTest.kt b/stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/ui/PaparazziComposeTest.kt index c7daabdf5c7..c9419027133 100644 --- a/stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/ui/PaparazziComposeTest.kt +++ b/stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/ui/PaparazziComposeTest.kt @@ -36,6 +36,10 @@ import androidx.compose.ui.platform.LocalInspectionMode import androidx.core.app.ActivityOptionsCompat import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleRegistry +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelStore +import androidx.lifecycle.ViewModelStoreOwner +import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner import app.cash.paparazzi.Paparazzi import io.getstream.chat.android.client.test.MockedChatClientTest import io.getstream.chat.android.compose.ui.theme.ChatTheme @@ -51,14 +55,10 @@ internal interface PaparazziComposeTest : MockedChatClientTest { composable: @Composable () -> Unit, ) { paparazzi.snapshot { - CompositionLocalProvider( - LocalInspectionMode provides true, - LocalOnBackPressedDispatcherOwner provides FakeBackDispatcherOwner, - LocalActivityResultRegistryOwner provides NoOpRegistryOwner, - ) { + TestEnvironment { ChatTheme(isInDarkMode = isInDarkMode) { Box(modifier = Modifier.background(ChatTheme.colors.backgroundCoreApp)) { - composable.invoke() + composable() } } } @@ -70,11 +70,7 @@ internal interface PaparazziComposeTest : MockedChatClientTest { composable: @Composable () -> Unit, ) { paparazzi.snapshot { - CompositionLocalProvider( - LocalInspectionMode provides true, - LocalOnBackPressedDispatcherOwner provides FakeBackDispatcherOwner, - LocalActivityResultRegistryOwner provides NoOpRegistryOwner, - ) { + TestEnvironment { Column { ChatTheme(isInDarkMode = true) { Box( @@ -106,11 +102,7 @@ internal interface PaparazziComposeTest : MockedChatClientTest { composable: @Composable () -> Unit, ) { paparazzi.snapshot { - CompositionLocalProvider( - LocalInspectionMode provides true, - LocalOnBackPressedDispatcherOwner provides FakeBackDispatcherOwner, - LocalActivityResultRegistryOwner provides NoOpRegistryOwner, - ) { + TestEnvironment { Row { ChatTheme(isInDarkMode = true) { Box( @@ -138,6 +130,24 @@ internal interface PaparazziComposeTest : MockedChatClientTest { } } +@Composable +private fun TestEnvironment(content: @Composable () -> Unit) { + CompositionLocalProvider( + LocalInspectionMode provides true, + LocalViewModelStoreOwner provides FakeViewModelStoreOwner, + LocalOnBackPressedDispatcherOwner provides FakeBackDispatcherOwner, + LocalActivityResultRegistryOwner provides NoOpRegistryOwner, + content = content, + ) +} + +/** + * A fake [ViewModelStoreOwner] necessary for composable components that use [ViewModel]. + */ +private val FakeViewModelStoreOwner = object : ViewModelStoreOwner { + override val viewModelStore: ViewModelStore = ViewModelStore() +} + /** * A fake [OnBackPressedDispatcherOwner] necessary for composable components that use [BackHandler]. */ diff --git a/stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/ui/attachments/content/AttachmentsContentTest.kt b/stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/ui/attachments/content/AttachmentsContentTest.kt index bffb63dd630..2f76755987c 100644 --- a/stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/ui/attachments/content/AttachmentsContentTest.kt +++ b/stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/ui/attachments/content/AttachmentsContentTest.kt @@ -32,13 +32,6 @@ internal class AttachmentsContentTest : PaparazziComposeTest { renderingMode = SessionParams.RenderingMode.SHRINK, ) - @Test - fun `file attachment preview content`() { - snapshotWithDarkModeRow { - FileAttachmentPreviewContent() - } - } - @Test fun `file attachment content`() { snapshotWithDarkMode { @@ -63,20 +56,6 @@ internal class AttachmentsContentTest : PaparazziComposeTest { } } - @Test - fun `image attachment preview content`() { - snapshotWithDarkModeRow { - ImageAttachmentPreviewContent() - } - } - - @Test - fun `media attachment preview items`() { - snapshotWithDarkModeRow { - MediaAttachmentPreviewItems() - } - } - @Test fun `file upload content`() { snapshotWithDarkModeRow { diff --git a/stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/ui/messages/attachments/AttachmentFilePickerTest.kt b/stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/ui/messages/attachments/AttachmentFilePickerTest.kt index 11e91e30765..f57a205cfc0 100644 --- a/stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/ui/messages/attachments/AttachmentFilePickerTest.kt +++ b/stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/ui/messages/attachments/AttachmentFilePickerTest.kt @@ -19,7 +19,6 @@ package io.getstream.chat.android.compose.ui.messages.attachments import app.cash.paparazzi.DeviceConfig import app.cash.paparazzi.Paparazzi import io.getstream.chat.android.compose.ui.PaparazziComposeTest -import io.getstream.chat.android.compose.ui.util.ViewModelStore import io.getstream.chat.android.test.TestCoroutineRule import org.junit.Rule import org.junit.Test @@ -35,18 +34,14 @@ internal class AttachmentFilePickerTest : PaparazziComposeTest { @Test fun `single selection`() { snapshotWithDarkMode { - ViewModelStore { - AttachmentFilePickerSingleSelection() - } + AttachmentFilePickerSingleSelection() } } @Test fun `multiple selection`() { snapshotWithDarkMode { - ViewModelStore { - AttachmentFilePickerMultipleSelection() - } + AttachmentFilePickerMultipleSelection() } } } diff --git a/stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/ui/messages/attachments/AttachmentMediaPickerTest.kt b/stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/ui/messages/attachments/AttachmentMediaPickerTest.kt index cad4a967c0b..9054c2e5616 100644 --- a/stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/ui/messages/attachments/AttachmentMediaPickerTest.kt +++ b/stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/ui/messages/attachments/AttachmentMediaPickerTest.kt @@ -19,7 +19,6 @@ package io.getstream.chat.android.compose.ui.messages.attachments import app.cash.paparazzi.DeviceConfig import app.cash.paparazzi.Paparazzi import io.getstream.chat.android.compose.ui.PaparazziComposeTest -import io.getstream.chat.android.compose.ui.util.ViewModelStore import io.getstream.chat.android.test.TestCoroutineRule import org.junit.Rule import org.junit.Test @@ -35,9 +34,7 @@ internal class AttachmentMediaPickerTest : PaparazziComposeTest { @Test fun `selection`() { snapshotWithDarkMode { - ViewModelStore { - AttachmentMediaPickerSelection() - } + AttachmentMediaPickerSelection() } } } diff --git a/stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/ui/messages/attachments/AttachmentSystemPickerTest.kt b/stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/ui/messages/attachments/AttachmentSystemPickerTest.kt index 08ff4f780e4..ebc5a63b647 100644 --- a/stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/ui/messages/attachments/AttachmentSystemPickerTest.kt +++ b/stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/ui/messages/attachments/AttachmentSystemPickerTest.kt @@ -19,7 +19,6 @@ package io.getstream.chat.android.compose.ui.messages.attachments import app.cash.paparazzi.DeviceConfig import app.cash.paparazzi.Paparazzi import io.getstream.chat.android.compose.ui.PaparazziComposeTest -import io.getstream.chat.android.compose.ui.util.ViewModelStore import io.getstream.chat.android.test.TestCoroutineRule import org.junit.Rule import org.junit.Test @@ -35,27 +34,21 @@ internal class AttachmentSystemPickerTest : PaparazziComposeTest { @Test fun `system picker`() { snapshotWithDarkMode { - ViewModelStore { - AttachmentSystemPicker() - } + AttachmentSystemPicker() } } @Test fun `system picker with polls`() { snapshotWithDarkMode { - ViewModelStore { - AttachmentSystemPickerWithPolls() - } + AttachmentSystemPickerWithPolls() } } @Test fun `system picker with commands`() { snapshotWithDarkMode { - ViewModelStore { - AttachmentSystemPickerWithCommands() - } + AttachmentSystemPickerWithCommands() } } } diff --git a/stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/ui/messages/attachments/poll/CreatePollScreenTest.kt b/stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/ui/messages/attachments/poll/CreatePollScreenTest.kt index 6187d1e880d..900abba5d81 100644 --- a/stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/ui/messages/attachments/poll/CreatePollScreenTest.kt +++ b/stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/ui/messages/attachments/poll/CreatePollScreenTest.kt @@ -19,7 +19,6 @@ package io.getstream.chat.android.compose.ui.messages.attachments.poll import app.cash.paparazzi.DeviceConfig import app.cash.paparazzi.Paparazzi import io.getstream.chat.android.compose.ui.PaparazziComposeTest -import io.getstream.chat.android.compose.ui.util.ViewModelStore import io.getstream.chat.android.test.TestCoroutineRule import org.junit.Rule import org.junit.Test @@ -35,18 +34,14 @@ internal class CreatePollScreenTest : PaparazziComposeTest { @Test fun `light mode`() { snapshot(isInDarkMode = false) { - ViewModelStore { - CreatePollScreen() - } + CreatePollScreen() } } @Test fun `dark mode`() { snapshot(isInDarkMode = true) { - ViewModelStore { - CreatePollScreen() - } + CreatePollScreen() } } } diff --git a/stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/ui/messages/composer/internal/attachments/MessageComposerAttachmentsTest.kt b/stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/ui/messages/composer/internal/attachments/MessageComposerAttachmentsTest.kt new file mode 100644 index 00000000000..669d45a63ce --- /dev/null +++ b/stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/ui/messages/composer/internal/attachments/MessageComposerAttachmentsTest.kt @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-chat-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.chat.android.compose.ui.messages.composer.internal.attachments + +import app.cash.paparazzi.DeviceConfig +import app.cash.paparazzi.Paparazzi +import com.android.ide.common.rendering.api.SessionParams +import com.android.resources.ScreenOrientation +import io.getstream.chat.android.compose.ui.PaparazziComposeTest +import org.junit.Rule +import org.junit.Test + +internal class MessageComposerAttachmentsTest : PaparazziComposeTest { + + @get:Rule + override val paparazzi = Paparazzi( + deviceConfig = DeviceConfig.PIXEL_2.copy(orientation = ScreenOrientation.LANDSCAPE), + renderingMode = SessionParams.RenderingMode.SHRINK, + ) + + @Test + fun `message composer attachments`() { + snapshotWithDarkMode { + MessageComposerAttachments() + } + } + + @Test + fun `audio record attachment item`() { + snapshotWithDarkModeRow { + MessageComposerAttachmentAudioRecordItem() + } + } + + @Test + fun `image attachment item`() { + snapshotWithDarkModeRow { + MessageComposerAttachmentImageItem() + } + } + + @Test + fun `video attachment item`() { + snapshotWithDarkModeRow { + MessageComposerAttachmentVideoItem() + } + } + + @Test + fun `file attachment item`() { + snapshotWithDarkModeRow { + MessageComposerAttachmentFileItem() + } + } +} diff --git a/stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.attachments.content_AttachmentsContentTest_audio_record_attachment_content.png b/stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.attachments.content_AttachmentsContentTest_audio_record_attachment_content.png index 7dccbb8d266..5e1772dd61b 100644 Binary files a/stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.attachments.content_AttachmentsContentTest_audio_record_attachment_content.png and b/stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.attachments.content_AttachmentsContentTest_audio_record_attachment_content.png differ diff --git a/stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.attachments.content_AttachmentsContentTest_file_attachment_preview_content.png b/stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.attachments.content_AttachmentsContentTest_file_attachment_preview_content.png deleted file mode 100644 index a2d39ca7605..00000000000 Binary files a/stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.attachments.content_AttachmentsContentTest_file_attachment_preview_content.png and /dev/null differ diff --git a/stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.attachments.content_AttachmentsContentTest_image_attachment_preview_content.png b/stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.attachments.content_AttachmentsContentTest_image_attachment_preview_content.png deleted file mode 100644 index a7cf8a3a265..00000000000 Binary files a/stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.attachments.content_AttachmentsContentTest_image_attachment_preview_content.png and /dev/null differ diff --git a/stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.attachments.content_AttachmentsContentTest_media_attachment_preview_items.png b/stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.attachments.content_AttachmentsContentTest_media_attachment_preview_items.png deleted file mode 100644 index 3fe2756fe95..00000000000 Binary files a/stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.attachments.content_AttachmentsContentTest_media_attachment_preview_items.png and /dev/null differ diff --git a/stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.components.attachments.images_ImagesPickerTest_add_more.png b/stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.components.attachments.images_ImagesPickerTest_add_more.png index 7b808232cf0..a21633105b5 100644 Binary files a/stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.components.attachments.images_ImagesPickerTest_add_more.png and b/stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.components.attachments.images_ImagesPickerTest_add_more.png differ diff --git a/stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.components.attachments.images_ImagesPickerTest_selection.png b/stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.components.attachments.images_ImagesPickerTest_selection.png index b00ebaf0b97..dde8d0601e3 100644 Binary files a/stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.components.attachments.images_ImagesPickerTest_selection.png and b/stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.components.attachments.images_ImagesPickerTest_selection.png differ diff --git a/stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.messages.attachments_AttachmentMediaPickerTest_selection.png b/stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.messages.attachments_AttachmentMediaPickerTest_selection.png index 58e56352526..eea43e6cf0e 100644 Binary files a/stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.messages.attachments_AttachmentMediaPickerTest_selection.png and b/stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.messages.attachments_AttachmentMediaPickerTest_selection.png differ diff --git a/stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.messages.composer.internal.attachments_MessageComposerAttachmentsTest_audio_record_attachment_item.png b/stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.messages.composer.internal.attachments_MessageComposerAttachmentsTest_audio_record_attachment_item.png new file mode 100644 index 00000000000..d5d2a1a944a Binary files /dev/null and b/stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.messages.composer.internal.attachments_MessageComposerAttachmentsTest_audio_record_attachment_item.png differ diff --git a/stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.messages.composer.internal.attachments_MessageComposerAttachmentsTest_file_attachment_item.png b/stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.messages.composer.internal.attachments_MessageComposerAttachmentsTest_file_attachment_item.png new file mode 100644 index 00000000000..d5f36be237e Binary files /dev/null and b/stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.messages.composer.internal.attachments_MessageComposerAttachmentsTest_file_attachment_item.png differ diff --git a/stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.messages.composer.internal.attachments_MessageComposerAttachmentsTest_image_attachment_item.png b/stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.messages.composer.internal.attachments_MessageComposerAttachmentsTest_image_attachment_item.png new file mode 100644 index 00000000000..851c64307a8 Binary files /dev/null and b/stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.messages.composer.internal.attachments_MessageComposerAttachmentsTest_image_attachment_item.png differ diff --git a/stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.messages.composer.internal.attachments_MessageComposerAttachmentsTest_message_composer_attachments.png b/stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.messages.composer.internal.attachments_MessageComposerAttachmentsTest_message_composer_attachments.png new file mode 100644 index 00000000000..9b06e38d029 Binary files /dev/null and b/stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.messages.composer.internal.attachments_MessageComposerAttachmentsTest_message_composer_attachments.png differ diff --git a/stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.messages.composer.internal.attachments_MessageComposerAttachmentsTest_video_attachment_item.png b/stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.messages.composer.internal.attachments_MessageComposerAttachmentsTest_video_attachment_item.png new file mode 100644 index 00000000000..1d881c44e3f Binary files /dev/null and b/stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.messages.composer.internal.attachments_MessageComposerAttachmentsTest_video_attachment_item.png differ diff --git a/stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.messages_MessageComposerInputTest_attachments.png b/stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.messages_MessageComposerInputTest_attachments.png index 9082f53df91..f3d0f6dfcb2 100644 Binary files a/stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.messages_MessageComposerInputTest_attachments.png and b/stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.messages_MessageComposerInputTest_attachments.png differ diff --git a/stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.messages_MessageComposerInputTest_attachments_and_link.png b/stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.messages_MessageComposerInputTest_attachments_and_link.png index b7ad4aee6a8..0f68b76cf4c 100644 Binary files a/stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.messages_MessageComposerInputTest_attachments_and_link.png and b/stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.messages_MessageComposerInputTest_attachments_and_link.png differ diff --git a/stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.messages_MessageComposerInputTest_edit,_attachments,_and_link.png b/stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.messages_MessageComposerInputTest_edit,_attachments,_and_link.png index 05917bbfc4c..c42cb2119ce 100644 Binary files a/stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.messages_MessageComposerInputTest_edit,_attachments,_and_link.png and b/stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.messages_MessageComposerInputTest_edit,_attachments,_and_link.png differ diff --git a/stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.messages_MessageComposerInputTest_reply,_attachments,_and_link.png b/stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.messages_MessageComposerInputTest_reply,_attachments,_and_link.png index f350ec83a4f..3915ced9ee1 100644 Binary files a/stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.messages_MessageComposerInputTest_reply,_attachments,_and_link.png and b/stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.messages_MessageComposerInputTest_reply,_attachments,_and_link.png differ diff --git a/stream-chat-android-core/src/main/java/io/getstream/chat/android/models/Attachment.kt b/stream-chat-android-core/src/main/java/io/getstream/chat/android/models/Attachment.kt index 5aecf73c896..42f818a482b 100644 --- a/stream-chat-android-core/src/main/java/io/getstream/chat/android/models/Attachment.kt +++ b/stream-chat-android-core/src/main/java/io/getstream/chat/android/models/Attachment.kt @@ -45,8 +45,9 @@ import java.io.File * @param type The type of the attachment. e.g "file", "image, "audio". * @param image The image attachment. * @param name The attachment name. - * @param fallback Alternative description in the case of an image attachment - * (img alt in HTML). + * @param fallback Fallback text used when [title] and [name] are absent. + * Displayed in channel-list previews and other summary surfaces. + * For image attachments this corresponds to the HTML img alt attribute. * @param originalHeight The original height of the attachment. * Provided if the attachment is of type "image". * @param originalWidth The original width of the attachment. diff --git a/stream-chat-android-docs/src/main/kotlin/io/getstream/chat/docs/kotlin/compose/general/CustomAttachments.kt b/stream-chat-android-docs/src/main/kotlin/io/getstream/chat/docs/kotlin/compose/general/CustomAttachments.kt index 6a5e973b8d9..5346e527953 100644 --- a/stream-chat-android-docs/src/main/kotlin/io/getstream/chat/docs/kotlin/compose/general/CustomAttachments.kt +++ b/stream-chat-android-docs/src/main/kotlin/io/getstream/chat/docs/kotlin/compose/general/CustomAttachments.kt @@ -5,10 +5,7 @@ package io.getstream.chat.docs.kotlin.compose.general import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent -import androidx.appcompat.app.AppCompatActivity -import com.google.firebase.components.Component -import io.getstream.chat.android.compose.ui.attachments.AttachmentFactory -import io.getstream.chat.android.compose.ui.attachments.StreamAttachmentFactories +import io.getstream.chat.android.compose.ui.theme.ChatComponentFactory import io.getstream.chat.android.compose.ui.theme.ChatTheme /** @@ -20,12 +17,12 @@ private object CustomAttachmentsSnippet { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - val myAttachmentFactories = listOf() - val defaultFactories = StreamAttachmentFactories.defaults() setContent { - // override the default factories by adding your own - ChatTheme(attachmentFactories = myAttachmentFactories + defaultFactories) { + // Override ChatComponentFactory to customise how attachments are rendered + // in the message list and composer. Extend ChatComponentFactory and override + // the relevant methods (e.g. CustomAttachmentContent, MessageComposerAttachments). + ChatTheme(componentFactory = object : ChatComponentFactory {}) { // Chat components } } diff --git a/stream-chat-android-docs/src/main/kotlin/io/getstream/chat/docs/kotlin/compose/guides/AddingCustomAttachments.kt b/stream-chat-android-docs/src/main/kotlin/io/getstream/chat/docs/kotlin/compose/guides/AddingCustomAttachments.kt index 11f1af30087..3b86beefe6a 100644 --- a/stream-chat-android-docs/src/main/kotlin/io/getstream/chat/docs/kotlin/compose/guides/AddingCustomAttachments.kt +++ b/stream-chat-android-docs/src/main/kotlin/io/getstream/chat/docs/kotlin/compose/guides/AddingCustomAttachments.kt @@ -32,12 +32,11 @@ import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel import com.google.android.material.datepicker.MaterialDatePicker import io.getstream.chat.android.compose.state.messages.attachments.AttachmentState -import io.getstream.chat.android.compose.ui.attachments.AttachmentFactory -import io.getstream.chat.android.compose.ui.attachments.StreamAttachmentFactories import io.getstream.chat.android.compose.ui.components.ComposerCancelIcon import io.getstream.chat.android.compose.ui.messages.composer.MessageComposer import io.getstream.chat.android.compose.ui.theme.ChatComponentFactory import io.getstream.chat.android.compose.ui.theme.ChatTheme +import io.getstream.chat.android.compose.ui.theme.MessageComposerAttachmentsParams import io.getstream.chat.android.compose.viewmodel.messages.MessageComposerViewModel import io.getstream.chat.android.compose.viewmodel.messages.MessagesViewModelFactory import io.getstream.chat.android.models.Attachment @@ -57,15 +56,9 @@ private object AddingCustomAttachmentsSnippet { super.onCreate(savedInstanceState) val channelId = requireNotNull(intent.getStringExtra(KEY_CHANNEL_ID)) - val customFactories = listOf(dateAttachmentFactory) - val defaultFactories = StreamAttachmentFactories.defaults() - setContent { - // Pass in custom factories or combine them with the default ones - ChatTheme( - componentFactory = CustomComponentFactory(), - attachmentFactories = customFactories + defaultFactories, - ) { + // Pass a custom ChatComponentFactory to handle custom attachment types + ChatTheme(componentFactory = CustomComponentFactory()) { CustomMessagesScreen( channelId = channelId, onBackPressed = { finish() }, @@ -114,14 +107,15 @@ private object AddingCustomAttachmentsSnippet { val payload = SimpleDateFormat("MMMM dd, yyyy").format(Date(date)) val attachment = Attachment( type = "date", - extraData = mutableMapOf("payload" to payload) + fallback = payload, + extraData = mutableMapOf("payload" to payload), ) // 3 composerViewModel.addAttachments(listOf(attachment)) }, ) - } + }, ) { _ -> // Message list } @@ -163,6 +157,30 @@ class CustomComponentFactory : ChatComponentFactory { } } + @Composable + override fun MessageComposerAttachments(params: MessageComposerAttachmentsParams) { + val dateAttachments = params.attachments.filter { it.type == "date" } + val otherAttachments = params.attachments.filter { it.type != "date" } + + Column(modifier = params.modifier) { + if (dateAttachments.isNotEmpty()) { + DateAttachmentPreviewContent( + modifier = Modifier.fillMaxWidth().wrapContentHeight(), + attachments = dateAttachments, + onAttachmentRemoved = params.onAttachmentRemoved, + ) + } + if (otherAttachments.isNotEmpty()) { + super.MessageComposerAttachments( + params = params.copy( + modifier = Modifier.fillMaxWidth().wrapContentHeight(), + attachments = otherAttachments, + ), + ) + } + } + } + @Composable override fun MessageComposerLeadingContent( modifier: Modifier, @@ -178,7 +196,7 @@ class CustomComponentFactory : ChatComponentFactory { Icon( painter = painterResource(id = R.drawable.ic_calendar), contentDescription = null, - tint = ChatTheme.colors.textSecondary + tint = ChatTheme.colors.textSecondary, ) }, onClick = onAttachmentsClick, @@ -186,20 +204,6 @@ class CustomComponentFactory : ChatComponentFactory { } } -val dateAttachmentFactory: AttachmentFactory = AttachmentFactory( - canHandle = { attachments -> attachments.any { it.type == "date" } }, - previewContent = { modifier, attachments, onAttachmentRemoved -> - DateAttachmentPreviewContent( - modifier = modifier, - attachments = attachments, - onAttachmentRemoved = onAttachmentRemoved - ) - }, - textFormatter = { attachment -> - attachment.extraData["payload"].toString() - }, -) - @Composable fun DateAttachmentContent( attachmentState: AttachmentState, @@ -214,11 +218,11 @@ fun DateAttachmentContent( .padding(4.dp) .clip(RoundedCornerShape(12.dp)) .background(ChatTheme.colors.accentSuccess) - .padding(8.dp) + .padding(8.dp), ) { Row( horizontalArrangement = Arrangement.spacedBy(8.dp), - verticalAlignment = Alignment.CenterVertically + verticalAlignment = Alignment.CenterVertically, ) { Icon( modifier = Modifier.size(16.dp), @@ -231,7 +235,7 @@ fun DateAttachmentContent( text = formattedDate, style = ChatTheme.typography.bodyDefault, maxLines = 1, - color = ChatTheme.colors.textPrimary + color = ChatTheme.colors.textPrimary, ) } } @@ -250,7 +254,7 @@ fun DateAttachmentPreviewContent( modifier = modifier .wrapContentHeight() .clip(RoundedCornerShape(16.dp)) - .background(color = ChatTheme.colors.backgroundElevationElevation1) + .background(color = ChatTheme.colors.backgroundElevationElevation1), ) { Text( modifier = Modifier @@ -260,14 +264,14 @@ fun DateAttachmentPreviewContent( text = formattedDate, style = ChatTheme.typography.bodyDefault, maxLines = 1, - color = ChatTheme.colors.textPrimary + color = ChatTheme.colors.textPrimary, ) ComposerCancelIcon( modifier = Modifier .align(Alignment.TopEnd) .padding(4.dp), - onClick = { onAttachmentRemoved(attachment) } + onClick = { onAttachmentRemoved(attachment) }, ) } } diff --git a/stream-chat-android-docs/src/main/kotlin/io/getstream/chat/docs/kotlin/compose/guides/CustomizingImageAndVideoPreviews.kt b/stream-chat-android-docs/src/main/kotlin/io/getstream/chat/docs/kotlin/compose/guides/CustomizingImageAndVideoPreviews.kt index 021e9f21c6b..7ffcb479a9f 100644 --- a/stream-chat-android-docs/src/main/kotlin/io/getstream/chat/docs/kotlin/compose/guides/CustomizingImageAndVideoPreviews.kt +++ b/stream-chat-android-docs/src/main/kotlin/io/getstream/chat/docs/kotlin/compose/guides/CustomizingImageAndVideoPreviews.kt @@ -1,7 +1,6 @@ package io.getstream.chat.docs.kotlin.compose.guides import android.os.Bundle -import android.os.PersistableBundle import androidx.activity.compose.setContent import androidx.appcompat.app.AppCompatActivity import androidx.compose.foundation.background @@ -10,7 +9,6 @@ import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Icon import androidx.compose.runtime.Composable @@ -19,13 +17,10 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp -import io.getstream.chat.android.compose.ui.attachments.factory.FileAttachmentFactory -import io.getstream.chat.android.compose.ui.attachments.factory.GiphyAttachmentFactory -import io.getstream.chat.android.compose.ui.attachments.factory.LinkAttachmentFactory -import io.getstream.chat.android.compose.ui.attachments.factory.MediaAttachmentFactory -import io.getstream.chat.android.compose.ui.attachments.factory.UnsupportedAttachmentFactory import io.getstream.chat.android.compose.ui.messages.MessagesScreen +import io.getstream.chat.android.compose.ui.theme.ChatComponentFactory import io.getstream.chat.android.compose.ui.theme.ChatTheme +import io.getstream.chat.android.compose.ui.theme.MessageComposerAttachmentMediaItemParams import io.getstream.chat.android.compose.viewmodel.messages.MessagesViewModelFactory import io.getstream.chat.android.models.AttachmentType import io.getstream.chat.docs.R @@ -43,60 +38,15 @@ private object CustomizingImageAndVideoPreviewsSnippet { ) } - val customMediaAttachmentFactory = MediaAttachmentFactory( - // Increase the maximum number of previewed items to 5 - maximumNumberOfPreviewedItems = 5, - // Render a custom item above attachments inside the message list - itemOverlayContent = { attachmentType -> - // Apply it only to video attachments - if (attachmentType == AttachmentType.VIDEO) { - CustomPlayButton( - modifier = Modifier - .widthIn(10.dp) - .padding(2.dp) - .background( - color = Color(red = 255, blue = 255, green = 255, alpha = 220), - shape = RoundedCornerShape(8.dp) - ) - .fillMaxWidth(0.3f) - .aspectRatio(1.20f), - ) - } - }, - // Render a custom item above attachments inside the message composer - previewItemOverlayContent = { attachmentType -> - // Apply it only to video attachments - if (attachmentType == AttachmentType.VIDEO) { - CustomPlayButton( - modifier = Modifier - .padding(2.dp) - .background( - color = Color(red = 255, blue = 255, green = 255, alpha = 220), - shape = RoundedCornerShape(8.dp) - ) - .fillMaxWidth(0.35f) - .aspectRatio(1.20f), - ) - } - }) - - val attachmentFactories = listOf( - LinkAttachmentFactory(linkDescriptionMaxLines = 5), - GiphyAttachmentFactory(), - customMediaAttachmentFactory, - FileAttachmentFactory(), - UnsupportedAttachmentFactory - ) - - override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) { - super.onCreate(savedInstanceState, persistentState) + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) setContent { - // Replace the default attachment factories - ChatTheme(attachmentFactories = attachmentFactories) { + // Override the default component factory to customize attachment previews + ChatTheme(componentFactory = CustomMediaComponentFactory) { MessagesScreen( viewModelFactory = messageListViewModelFactory, - onBackPressed = { finish() } + onBackPressed = { finish() }, ) } } @@ -105,12 +55,45 @@ private object CustomizingImageAndVideoPreviewsSnippet { @Composable private fun CustomPlayButton(modifier: Modifier) { Box(modifier = modifier, contentAlignment = Alignment.Center) { - Icon(modifier = Modifier - .padding(2.dp) - .fillMaxSize(0.8f), + Icon( + modifier = Modifier + .padding(2.dp) + .fillMaxSize(0.8f), painter = painterResource(id = R.drawable.stream_compose_ic_play), tint = Color.White, - contentDescription = null) + contentDescription = null, + ) + } + } + + /** + * A [ChatComponentFactory] that renders a custom play button overlay above video + * attachments in the message composer preview. + * + * To customize the overlay in the message composer, override MessageComposerAttachmentMediaItemOverlay. + */ + val CustomMediaComponentFactory = object : ChatComponentFactory { + + @Composable + override fun MessageComposerAttachmentMediaItem( + params: MessageComposerAttachmentMediaItemParams, + ) { + Box(contentAlignment = Alignment.Center) { + super.MessageComposerAttachmentMediaItem(params) + if (params.attachment.type == AttachmentType.VIDEO) { + // Render a custom play button above video items in the composer + CustomPlayButton( + modifier = Modifier + .padding(2.dp) + .background( + color = Color(red = 255, blue = 255, green = 255, alpha = 220), + shape = RoundedCornerShape(8.dp), + ) + .fillMaxWidth(0.35f) + .aspectRatio(1.20f), + ) + } + } } } } diff --git a/stream-chat-android-docs/src/main/kotlin/io/getstream/chat/docs/kotlin/ui/guides/AddingCustomAttachments.kt b/stream-chat-android-docs/src/main/kotlin/io/getstream/chat/docs/kotlin/ui/guides/AddingCustomAttachments.kt index c87c4072d5d..a9f4e021740 100644 --- a/stream-chat-android-docs/src/main/kotlin/io/getstream/chat/docs/kotlin/ui/guides/AddingCustomAttachments.kt +++ b/stream-chat-android-docs/src/main/kotlin/io/getstream/chat/docs/kotlin/ui/guides/AddingCustomAttachments.kt @@ -123,7 +123,8 @@ class AddingCustomAttachments { val payload = SimpleDateFormat("MMMM dd, yyyy").format(Date(date)) val attachment = Attachment( type = "date", - extraData = mutableMapOf("payload" to payload) + fallback = payload, + extraData = mutableMapOf("payload" to payload), ) messageComposerViewModel.addAttachments(listOf(attachment)) } diff --git a/stream-chat-android-previewdata/src/main/kotlin/io/getstream/chat/android/previewdata/PreviewAttachmentData.kt b/stream-chat-android-previewdata/src/main/kotlin/io/getstream/chat/android/previewdata/PreviewAttachmentData.kt index ecfda802cb0..1edf0b385c5 100644 --- a/stream-chat-android-previewdata/src/main/kotlin/io/getstream/chat/android/previewdata/PreviewAttachmentData.kt +++ b/stream-chat-android-previewdata/src/main/kotlin/io/getstream/chat/android/previewdata/PreviewAttachmentData.kt @@ -16,16 +16,20 @@ package io.getstream.chat.android.previewdata +import io.getstream.chat.android.client.extensions.EXTRA_DURATION +import io.getstream.chat.android.client.extensions.EXTRA_WAVEFORM_DATA import io.getstream.chat.android.core.internal.InternalStreamChatApi import io.getstream.chat.android.models.Attachment +import io.getstream.chat.android.models.AttachmentType +@Suppress("MagicNumber") @InternalStreamChatApi public object PreviewAttachmentData { public val attachmentImage1: Attachment = Attachment( name = "image1.jpg", fileSize = 2000000, - type = "image", + type = AttachmentType.IMAGE, mimeType = "image/jpeg", imageUrl = "https://example.com/image1.jpg", ) @@ -33,32 +37,53 @@ public object PreviewAttachmentData { public val attachmentImage2: Attachment = Attachment( name = "image2.jpg", fileSize = 3000000, - type = "image", + type = AttachmentType.IMAGE, mimeType = "image/jpeg", imageUrl = "https://example.com/image2.jpg", ) public val attachmentImage3: Attachment = Attachment( - name = "image3.jpg", + name = "image3.png", fileSize = 4000000, - type = "image", - mimeType = "image/jpeg", - imageUrl = "https://example.com/image3.jpg", + type = AttachmentType.IMAGE, + mimeType = "image/png", + imageUrl = "https://example.com/image3.png", + extraData = mapOf(EXTRA_DURATION to 8), ) public val attachmentVideo1: Attachment = Attachment( name = "video1.mp4", fileSize = 10000000, - type = "video", + type = AttachmentType.VIDEO, mimeType = "video/mp4", thumbUrl = "https://example.com/thumb1.jpg", + extraData = mapOf(EXTRA_DURATION to 8), ) public val attachmentVideo2: Attachment = Attachment( name = "video2.mp4", fileSize = 20000000, - type = "video", + type = AttachmentType.VIDEO, mimeType = "video/mp4", thumbUrl = "https://example.com/thumb2.jpg", ) + + public val attachmentFile1: Attachment = Attachment( + name = "document.pdf", + fileSize = 1500000, + type = AttachmentType.FILE, + mimeType = "application/pdf", + ) + + public val attachmentAudioRecording1: Attachment = Attachment( + type = AttachmentType.AUDIO_RECORDING, + extraData = mutableMapOf( + EXTRA_WAVEFORM_DATA to listOf( + 0.1f, 0.3f, 0.6f, 0.8f, 1.0f, 0.9f, 0.7f, 0.5f, 0.4f, 0.6f, + 0.8f, 0.7f, 0.5f, 0.3f, 0.4f, 0.6f, 0.9f, 1.0f, 0.8f, 0.6f, + 0.4f, 0.3f, 0.5f, 0.7f, 0.9f, 0.8f, 0.6f, 0.4f, 0.2f, 0.3f, + ), + EXTRA_DURATION to 1_500, + ), + ) } diff --git a/stream-chat-android-ui-guides/src/main/java/io/getstream/chat/android/guides/catalog/compose/customattachments/ChannelsActivity.kt b/stream-chat-android-ui-guides/src/main/java/io/getstream/chat/android/guides/catalog/compose/customattachments/ChannelsActivity.kt index 1b243303bce..cb340d362c3 100644 --- a/stream-chat-android-ui-guides/src/main/java/io/getstream/chat/android/guides/catalog/compose/customattachments/ChannelsActivity.kt +++ b/stream-chat-android-ui-guides/src/main/java/io/getstream/chat/android/guides/catalog/compose/customattachments/ChannelsActivity.kt @@ -21,7 +21,6 @@ import android.content.Intent import android.os.Bundle import androidx.activity.compose.setContent import androidx.appcompat.app.AppCompatActivity -import io.getstream.chat.android.compose.ui.attachments.StreamAttachmentFactories import io.getstream.chat.android.compose.ui.channels.ChannelsScreen import io.getstream.chat.android.compose.ui.theme.ChatTheme @@ -33,14 +32,8 @@ class ChannelsActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - val customFactories = listOf(dateAttachmentFactory) - val defaultFactories = StreamAttachmentFactories.defaults() - setContent { - ChatTheme( - componentFactory = CustomChatComponentFactory, - attachmentFactories = customFactories + defaultFactories, - ) { + ChatTheme(componentFactory = CustomChatComponentFactory) { ChannelsScreen( onChannelClick = { channel -> startActivity(MessagesActivity.createIntent(this, channel.cid)) diff --git a/stream-chat-android-ui-guides/src/main/java/io/getstream/chat/android/guides/catalog/compose/customattachments/CustomChatComponentFactory.kt b/stream-chat-android-ui-guides/src/main/java/io/getstream/chat/android/guides/catalog/compose/customattachments/CustomChatComponentFactory.kt index d4fc1e6b932..cf88b093a2c 100644 --- a/stream-chat-android-ui-guides/src/main/java/io/getstream/chat/android/guides/catalog/compose/customattachments/CustomChatComponentFactory.kt +++ b/stream-chat-android-ui-guides/src/main/java/io/getstream/chat/android/guides/catalog/compose/customattachments/CustomChatComponentFactory.kt @@ -36,10 +36,10 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp import io.getstream.chat.android.compose.state.messages.attachments.AttachmentState -import io.getstream.chat.android.compose.ui.attachments.AttachmentFactory import io.getstream.chat.android.compose.ui.components.ComposerCancelIcon import io.getstream.chat.android.compose.ui.theme.ChatComponentFactory import io.getstream.chat.android.compose.ui.theme.ChatTheme +import io.getstream.chat.android.compose.ui.theme.MessageComposerAttachmentsParams import io.getstream.chat.android.guides.R import io.getstream.chat.android.models.Attachment import io.getstream.chat.android.ui.common.state.messages.composer.MessageComposerState @@ -55,6 +55,30 @@ object CustomChatComponentFactory : ChatComponentFactory { } } + @Composable + override fun MessageComposerAttachments(params: MessageComposerAttachmentsParams) { + val dateAttachments = params.attachments.filter { it.type == "date" } + val otherAttachments = params.attachments.filter { it.type != "date" } + + Column(modifier = params.modifier) { + if (dateAttachments.isNotEmpty()) { + DateAttachmentPreviewContent( + modifier = Modifier.fillMaxWidth().wrapContentHeight(), + attachments = dateAttachments, + onAttachmentRemoved = params.onAttachmentRemoved, + ) + } + if (otherAttachments.isNotEmpty()) { + super.MessageComposerAttachments( + params = params.copy( + modifier = Modifier.fillMaxWidth().wrapContentHeight(), + attachments = otherAttachments, + ), + ) + } + } + } + @Composable override fun MessageComposerLeadingContent( modifier: Modifier, @@ -78,23 +102,6 @@ object CustomChatComponentFactory : ChatComponentFactory { } } -/** - * A custom [AttachmentFactory] that adds support for date attachments. - */ -val dateAttachmentFactory: AttachmentFactory = AttachmentFactory( - canHandle = { attachments -> attachments.any { it.type == "date" } }, - previewContent = { modifier, attachments, onAttachmentRemoved -> - DateAttachmentPreviewContent( - modifier = modifier, - attachments = attachments, - onAttachmentRemoved = onAttachmentRemoved, - ) - }, - textFormatter = { attachment -> - attachment.extraData["payload"].toString() - }, -) - /** * Represents the UI shown in the message input preview before sending. * diff --git a/stream-chat-android-ui-guides/src/main/java/io/getstream/chat/android/guides/catalog/compose/customattachments/MessagesActivity.kt b/stream-chat-android-ui-guides/src/main/java/io/getstream/chat/android/guides/catalog/compose/customattachments/MessagesActivity.kt index 584ce39755c..dd81af9dd7d 100644 --- a/stream-chat-android-ui-guides/src/main/java/io/getstream/chat/android/guides/catalog/compose/customattachments/MessagesActivity.kt +++ b/stream-chat-android-ui-guides/src/main/java/io/getstream/chat/android/guides/catalog/compose/customattachments/MessagesActivity.kt @@ -38,7 +38,6 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel import com.google.android.material.datepicker.MaterialDatePicker -import io.getstream.chat.android.compose.ui.attachments.StreamAttachmentFactories import io.getstream.chat.android.compose.ui.messages.MessagesScreen import io.getstream.chat.android.compose.ui.messages.composer.MessageComposer import io.getstream.chat.android.compose.ui.messages.header.MessageListHeader @@ -63,14 +62,8 @@ class MessagesActivity : AppCompatActivity() { super.onCreate(savedInstanceState) val channelId = requireNotNull(intent.getStringExtra(KEY_CHANNEL_ID)) - val customFactories = listOf(dateAttachmentFactory) - val defaultFactories = StreamAttachmentFactories.defaults() - setContent { - ChatTheme( - componentFactory = CustomChatComponentFactory, - attachmentFactories = customFactories + defaultFactories, - ) { + ChatTheme(componentFactory = CustomChatComponentFactory) { CustomMessagesScreen( channelId = channelId, onBackPressed = { finish() }, @@ -130,6 +123,7 @@ class MessagesActivity : AppCompatActivity() { val payload = SimpleDateFormat("MMMM dd, yyyy").format(Date(date)) val attachment = Attachment( type = "date", + fallback = payload, extraData = mutableMapOf("payload" to payload), ) diff --git a/stream-chat-android-ui-guides/src/main/java/io/getstream/chat/android/guides/catalog/compose/customizingimageandvideoattachments/MessagesActivity.kt b/stream-chat-android-ui-guides/src/main/java/io/getstream/chat/android/guides/catalog/compose/customizingimageandvideoattachments/MessagesActivity.kt index 0faae610041..8d0062c2d2b 100644 --- a/stream-chat-android-ui-guides/src/main/java/io/getstream/chat/android/guides/catalog/compose/customizingimageandvideoattachments/MessagesActivity.kt +++ b/stream-chat-android-ui-guides/src/main/java/io/getstream/chat/android/guides/catalog/compose/customizingimageandvideoattachments/MessagesActivity.kt @@ -21,19 +21,31 @@ import android.content.Intent import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent -import io.getstream.chat.android.compose.ui.attachments.StreamAttachmentFactories -import io.getstream.chat.android.compose.ui.attachments.factory.FileAttachmentFactory -import io.getstream.chat.android.compose.ui.attachments.factory.GiphyAttachmentFactory -import io.getstream.chat.android.compose.ui.attachments.factory.LinkAttachmentFactory -import io.getstream.chat.android.compose.ui.attachments.factory.UnsupportedAttachmentFactory +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp import io.getstream.chat.android.compose.ui.messages.MessagesScreen +import io.getstream.chat.android.compose.ui.theme.ChatComponentFactory import io.getstream.chat.android.compose.ui.theme.ChatTheme +import io.getstream.chat.android.compose.ui.theme.MessageComposerAttachmentMediaItemParams import io.getstream.chat.android.compose.viewmodel.messages.MessagesViewModelFactory -import io.getstream.chat.android.guides.catalog.compose.customizingimageandvideoattachments.factory.customMediaAttachmentFactory +import io.getstream.chat.android.guides.catalog.compose.customizingimageandvideoattachments.ui.CustomPlayButton +import io.getstream.chat.android.models.AttachmentType + +private const val PlayButtonWidthFraction = 0.35f +private const val PlayButtonAspectRatio = 1.20f /** * An activity featuring a fully functional message list screen with a custom - * media attachment factory. + * media attachment preview in the composer. */ class MessagesActivity : ComponentActivity() { @@ -45,25 +57,11 @@ class MessagesActivity : ComponentActivity() { ) } - /** - * A list of attachment factories that mimics the order of those - * found in [StreamAttachmentFactories.defaults] while - * replacing the default media attachment factory with a - * custom one. - */ - private val attachmentFactories = listOf( - LinkAttachmentFactory(linkDescriptionMaxLines = 5), - GiphyAttachmentFactory(), - customMediaAttachmentFactory, - FileAttachmentFactory(), - UnsupportedAttachmentFactory, - ) - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { - ChatTheme(attachmentFactories = attachmentFactories) { + ChatTheme(componentFactory = CustomMediaChatComponentFactory) { MessagesScreen( viewModelFactory = messagesViewModelFactory, onBackPressed = { finish() }, @@ -89,3 +87,33 @@ class MessagesActivity : ComponentActivity() { } } } + +/** + * A custom [ChatComponentFactory] that renders a custom play button overlay + * above video attachments in the message composer preview. + * + * To customize the overlay in the message list, override [MediaAttachmentContent]. + */ +object CustomMediaChatComponentFactory : ChatComponentFactory { + + @Composable + override fun MessageComposerAttachmentMediaItem( + params: MessageComposerAttachmentMediaItemParams, + ) { + Box(contentAlignment = Alignment.Center) { + super.MessageComposerAttachmentMediaItem(params) + if (params.attachment.type == AttachmentType.VIDEO) { + CustomPlayButton( + modifier = Modifier + .padding(2.dp) + .background( + color = Color(red = 255, blue = 255, green = 255, alpha = 220), + shape = RoundedCornerShape(8.dp), + ) + .fillMaxWidth(PlayButtonWidthFraction) + .aspectRatio(PlayButtonAspectRatio), + ) + } + } + } +} diff --git a/stream-chat-android-ui-guides/src/main/java/io/getstream/chat/android/guides/catalog/compose/customizingimageandvideoattachments/factory/MultimediaAttachmentFactory.kt b/stream-chat-android-ui-guides/src/main/java/io/getstream/chat/android/guides/catalog/compose/customizingimageandvideoattachments/factory/MultimediaAttachmentFactory.kt deleted file mode 100644 index b7f6c4efc23..00000000000 --- a/stream-chat-android-ui-guides/src/main/java/io/getstream/chat/android/guides/catalog/compose/customizingimageandvideoattachments/factory/MultimediaAttachmentFactory.kt +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. - * - * Licensed under the Stream License; - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://github.com/GetStream/stream-chat-android/blob/main/LICENSE - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.getstream.chat.android.guides.catalog.compose.customizingimageandvideoattachments.factory - -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.aspectRatio -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.widthIn -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.unit.dp -import io.getstream.chat.android.compose.ui.attachments.factory.MediaAttachmentFactory -import io.getstream.chat.android.guides.catalog.compose.customizingimageandvideoattachments.ui.CustomPlayButton -import io.getstream.chat.android.models.AttachmentType - -val customMediaAttachmentFactory = MediaAttachmentFactory( - // Increase the maximum number of previewed items to 5 - maximumNumberOfPreviewedItems = 5, - // Render a custom item above attachments inside the message list - itemOverlayContent = { attachmentType -> - if (attachmentType == AttachmentType.VIDEO) { - CustomPlayButton( - modifier = Modifier - .widthIn(10.dp) - .padding(2.dp) - .background( - color = Color(red = 255, blue = 255, green = 255, alpha = 220), - shape = RoundedCornerShape(8.dp), - ) - .fillMaxWidth(0.3f) - .aspectRatio(1.20f), - ) - } - }, - // Render a custom item above attachments inside the message composer - previewItemOverlayContent = { attachmentType -> - if (attachmentType == AttachmentType.VIDEO) { - CustomPlayButton( - modifier = Modifier - .padding(2.dp) - .background( - color = Color(red = 255, blue = 255, green = 255, alpha = 220), - shape = RoundedCornerShape(8.dp), - ) - .fillMaxWidth(0.35f) - .aspectRatio(1.20f), - ) - } - }, -) diff --git a/stream-chat-android-ui-guides/src/main/java/io/getstream/chat/android/guides/catalog/uicomponents/customattachments/MessagesActivity.kt b/stream-chat-android-ui-guides/src/main/java/io/getstream/chat/android/guides/catalog/uicomponents/customattachments/MessagesActivity.kt index a2ccd5b9410..e19900b34c1 100644 --- a/stream-chat-android-ui-guides/src/main/java/io/getstream/chat/android/guides/catalog/uicomponents/customattachments/MessagesActivity.kt +++ b/stream-chat-android-ui-guides/src/main/java/io/getstream/chat/android/guides/catalog/uicomponents/customattachments/MessagesActivity.kt @@ -123,6 +123,7 @@ class MessagesActivity : AppCompatActivity() { val payload = SimpleDateFormat("MMMM dd, yyyy").format(Date(date)) val attachment = Attachment( type = "date", + fallback = payload, extraData = mutableMapOf("payload" to payload), ) messageComposerViewModel.addAttachments(listOf(attachment))