diff --git a/CHANGELOG.md b/CHANGELOG.md index fe3d173d38f..cb4867d121e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -74,9 +74,13 @@ - Improved asking for `WRITE_EXTERNAL_STORAGE` permission. The permission won't be requested starting from Android Q unless legacy external storage is requested. [#4219](https://github.com/GetStream/stream-chat-android/pull/4219) - Improved the stability of cooldown timer in slow mode. [#4251](https://github.com/GetStream/stream-chat-android/pull/4251) - Improved how system bar colors are handled on the attachment gallery screen. [#4269](https://github.com/GetStream/stream-chat-android/pull/4269) +- The default attachment gallery is now able to handle videos as well as images. [#4283](https://github.com/GetStream/stream-chat-android/pull/4283) ### ✅ Added - Added `UserAvatarView` and `ChannelAvatarView` to replace `AvatarView` to keep consistency with the Compose UI SDK. [#4165](https://github.com/GetStream/stream-chat-android/pull/4165) +- Added the ability to turn off video previews (thumbnails) via `ChatUI.videoThumbnailsEnabled`. Video previews are a paid feature and as such you can turn them off. They are on by default and the pricing can be found [here](https://getstream.io/chat/pricing/). [#4158](https://github.com/GetStream/stream-chat-android/pull/4158) +- Added a new function `MessageListItemViewHolderFactory.createMediaAttachmentsViewHolder()` which returns a `ViewHolder` capable of previewing both images and videos. [#4158](https://github.com/GetStream/stream-chat-android/pull/4158) +- Added a style class called `MediaAttachmentViewStyle`. The new style controls how previews of both image and video attachments are displayed inside the message list. [#4158](https://github.com/GetStream/stream-chat-android/pull/4158) - Added `UnsupportedAttachmentFactory` for unsupported attachments. [#4271](https://github.com/GetStream/stream-chat-android/pull/4271) - Added attrs to `UnsupportedAttachmentsView` that allow to customize the UI of unsupported attachments in [#4271](https://github.com/GetStream/stream-chat-android/pull/4271): * `streamUiUnsupportedAttachmentBackgroundColor` @@ -93,10 +97,15 @@ - Added a new function `MessageListItemViewHolderFactory.createMediaAttachmentsViewHolder()` which returns a `ViewHolder` capable of previewing both images and videos. [#4158](https://github.com/GetStream/stream-chat-android/pull/4158) - Added a style class called `MediaAttachmentViewStyle`. The new style controls how previews of both image and video attachments are displayed inside the message list. [#4158](https://github.com/GetStream/stream-chat-android/pull/4158) - Added `OnScrollToBottomHandler` to `MessageListView`. [#3849](https://github.com/GetStream/stream-chat-android/pull/3849) +- Added the ability to style the play button inside the attachment gallery. The necessary attributes along with their description can be found [here](https://github.com/GetStream/stream-chat-android/blob/main/stream-chat-android-ui-components/src/main/res/values/attrs_attachment_gallery_activity.xml). These attributes can be parsed using the newly created `AttachmentGalleryViewMediaStyle` class. [#4283](https://github.com/GetStream/stream-chat-android/pull/4283) +- Added new styled attributes for `MediaAttachmentGridView`. The new attributes along with their description can be found [here](https://github.com/GetStream/stream-chat-android/blob/main/src/main/res/values/attrs_media_attachment_grid_view.xml). These attributes can be parsed using the newly created `MediaAttachmentGridViewStyle` class. [#4283](https://github.com/GetStream/stream-chat-android/pull/4283) ### ⚠️ Changed - 🚨 Breaking change: The function `MessageListItemViewHolderFactory.createImageAttachmentsViewHolder()` has been removed in favor of the function `MessageListItemViewHolderFactory.createMediaAttachmentsViewHolder()` which returns a `ViewHolder` capable of previewing both images and videos. [#4158](https://github.com/GetStream/stream-chat-android/pull/4158) - 🚨 Breaking change: `ImageAttachmentViewStyle` has been removed and replaced by `MediaAttachmentViewStyle`. The new style controls how previews of both image and video attachments are displayed inside the message list. [#4158](https://github.com/GetStream/stream-chat-android/pull/4158) +- 🚨 Breaking change: Attribute `streamUiSaveImageEnabled` has been renamed to `streamUiSaveMediaEnabled`. [#4283](https://github.com/GetStream/stream-chat-android/pull/4283) +- 🚨 Breaking change: Attribute `streamUiSaveImageIcon` has been renamed to `streamUiSaveMediaIcon`. [#4283](https://github.com/GetStream/stream-chat-android/pull/4283) +- 🚨 Breaking change: String resource `stream_ui_attachment_gallery_save_image` has been renamed to `stream_ui_attachment_gallery_save_media`. [#4283](https://github.com/GetStream/stream-chat-android/pull/4283) ### ❌ Removed - Removed `AvatarView` in favor of `UserAvatarView` and `ChannelAvatarView` to keep consistency with the Compose UI SDK. [#4165](https://github.com/GetStream/stream-chat-android/pull/4165) diff --git a/stream-chat-android-ui-components/api/stream-chat-android-ui-components.api b/stream-chat-android-ui-components/api/stream-chat-android-ui-components.api index ba3b8df6da1..a7377a03a09 100644 --- a/stream-chat-android-ui-components/api/stream-chat-android-ui-components.api +++ b/stream-chat-android-ui-components/api/stream-chat-android-ui-components.api @@ -724,6 +724,7 @@ public final class io/getstream/chat/android/ui/SupportedReactions$ReactionDrawa public final class io/getstream/chat/android/ui/TransformStyle { public static final field INSTANCE Lio/getstream/chat/android/ui/TransformStyle; public final fun getAttachmentGalleryOptionsStyleTransformer ()Lio/getstream/chat/android/ui/StyleTransformer; + public final fun getAttachmentGalleryViewMediaStyle ()Lio/getstream/chat/android/ui/StyleTransformer; public final fun getAttachmentsPickerStyleTransformer ()Lio/getstream/chat/android/ui/StyleTransformer; public final fun getAvatarStyleTransformer ()Lio/getstream/chat/android/ui/StyleTransformer; public final fun getChannelActionsDialogStyleTransformer ()Lio/getstream/chat/android/ui/StyleTransformer; @@ -732,7 +733,8 @@ public final class io/getstream/chat/android/ui/TransformStyle { public final fun getEditReactionsStyleTransformer ()Lio/getstream/chat/android/ui/StyleTransformer; public final fun getFileAttachmentStyleTransformer ()Lio/getstream/chat/android/ui/StyleTransformer; public final fun getGiphyViewHolderStyleTransformer ()Lio/getstream/chat/android/ui/StyleTransformer; - public final fun getImageAttachmentStyleTransformer ()Lio/getstream/chat/android/ui/StyleTransformer; + public final fun getMediaAttachmentGridViewStyle ()Lio/getstream/chat/android/ui/StyleTransformer; + public final fun getMediaAttachmentStyleTransformer ()Lio/getstream/chat/android/ui/StyleTransformer; public final fun getMentionListViewStyleTransformer ()Lio/getstream/chat/android/ui/StyleTransformer; public final fun getMessageComposerStyleTransformer ()Lio/getstream/chat/android/ui/StyleTransformer; public final fun getMessageInputStyleTransformer ()Lio/getstream/chat/android/ui/StyleTransformer; @@ -750,6 +752,7 @@ public final class io/getstream/chat/android/ui/TransformStyle { public final fun getUnsupportedAttachmentStyleTransformer ()Lio/getstream/chat/android/ui/StyleTransformer; public final fun getViewReactionsStyleTransformer ()Lio/getstream/chat/android/ui/StyleTransformer; public final fun setAttachmentGalleryOptionsStyleTransformer (Lio/getstream/chat/android/ui/StyleTransformer;)V + public final fun setAttachmentGalleryViewMediaStyle (Lio/getstream/chat/android/ui/StyleTransformer;)V public final fun setAttachmentsPickerStyleTransformer (Lio/getstream/chat/android/ui/StyleTransformer;)V public final fun setAvatarStyleTransformer (Lio/getstream/chat/android/ui/StyleTransformer;)V public final fun setChannelActionsDialogStyleTransformer (Lio/getstream/chat/android/ui/StyleTransformer;)V @@ -758,7 +761,8 @@ public final class io/getstream/chat/android/ui/TransformStyle { public final fun setEditReactionsStyleTransformer (Lio/getstream/chat/android/ui/StyleTransformer;)V public final fun setFileAttachmentStyleTransformer (Lio/getstream/chat/android/ui/StyleTransformer;)V public final fun setGiphyViewHolderStyleTransformer (Lio/getstream/chat/android/ui/StyleTransformer;)V - public final fun setImageAttachmentStyleTransformer (Lio/getstream/chat/android/ui/StyleTransformer;)V + public final fun setMediaAttachmentGridViewStyle (Lio/getstream/chat/android/ui/StyleTransformer;)V + public final fun setMediaAttachmentStyleTransformer (Lio/getstream/chat/android/ui/StyleTransformer;)V public final fun setMentionListViewStyleTransformer (Lio/getstream/chat/android/ui/StyleTransformer;)V public final fun setMessageComposerStyleTransformer (Lio/getstream/chat/android/ui/StyleTransformer;)V public final fun setMessageInputStyleTransformer (Lio/getstream/chat/android/ui/StyleTransformer;)V @@ -1651,6 +1655,39 @@ public final class io/getstream/chat/android/ui/gallery/AttachmentGalleryResultI public static final fun toAttachmentGalleryResultItem (Lio/getstream/chat/android/client/models/Attachment;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Z)Lio/getstream/chat/android/ui/gallery/AttachmentGalleryResultItem; } +public final class io/getstream/chat/android/ui/gallery/AttachmentGalleryViewMediaStyle { + public fun (Landroid/graphics/drawable/Drawable;Ljava/lang/Integer;IFFIIIIIILandroid/graphics/drawable/Drawable;)V + public final fun component1 ()Landroid/graphics/drawable/Drawable; + public final fun component10 ()I + public final fun component11 ()I + public final fun component12 ()Landroid/graphics/drawable/Drawable; + public final fun component2 ()Ljava/lang/Integer; + public final fun component3 ()I + public final fun component4 ()F + public final fun component5 ()F + public final fun component6 ()I + public final fun component7 ()I + public final fun component8 ()I + public final fun component9 ()I + public final fun copy (Landroid/graphics/drawable/Drawable;Ljava/lang/Integer;IFFIIIIIILandroid/graphics/drawable/Drawable;)Lio/getstream/chat/android/ui/gallery/AttachmentGalleryViewMediaStyle; + public static synthetic fun copy$default (Lio/getstream/chat/android/ui/gallery/AttachmentGalleryViewMediaStyle;Landroid/graphics/drawable/Drawable;Ljava/lang/Integer;IFFIIIIIILandroid/graphics/drawable/Drawable;ILjava/lang/Object;)Lio/getstream/chat/android/ui/gallery/AttachmentGalleryViewMediaStyle; + public fun equals (Ljava/lang/Object;)Z + public final fun getImagePlaceholder ()Landroid/graphics/drawable/Drawable; + public final fun getViewMediaPlayVideoButtonIcon ()Landroid/graphics/drawable/Drawable; + public final fun getViewMediaPlayVideoIconBackgroundColor ()I + public final fun getViewMediaPlayVideoIconCornerRadius ()F + public final fun getViewMediaPlayVideoIconElevation ()F + public final fun getViewMediaPlayVideoIconHeight ()I + public final fun getViewMediaPlayVideoIconPaddingBottom ()I + public final fun getViewMediaPlayVideoIconPaddingEnd ()I + public final fun getViewMediaPlayVideoIconPaddingStart ()I + public final fun getViewMediaPlayVideoIconPaddingTop ()I + public final fun getViewMediaPlayVideoIconTint ()Ljava/lang/Integer; + public final fun getViewMediaPlayVideoIconWidth ()I + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + public final class io/getstream/chat/android/ui/gallery/AttachmentMediaActivity : androidx/appcompat/app/AppCompatActivity { public static final field Companion Lio/getstream/chat/android/ui/gallery/AttachmentMediaActivity$Companion; public fun ()V @@ -1661,6 +1698,37 @@ public final class io/getstream/chat/android/ui/gallery/AttachmentMediaActivity$ public static synthetic fun createIntent$default (Lio/getstream/chat/android/ui/gallery/AttachmentMediaActivity$Companion;Landroid/content/Context;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Landroid/content/Intent; } +public final class io/getstream/chat/android/ui/gallery/MediaAttachmentGridViewStyle { + public fun (ZLandroid/graphics/drawable/Drawable;Ljava/lang/Integer;IFFIIIILandroid/graphics/drawable/Drawable;)V + public final fun component1 ()Z + public final fun component10 ()I + public final fun component11 ()Landroid/graphics/drawable/Drawable; + public final fun component2 ()Landroid/graphics/drawable/Drawable; + public final fun component3 ()Ljava/lang/Integer; + public final fun component4 ()I + public final fun component5 ()F + public final fun component6 ()F + public final fun component7 ()I + public final fun component8 ()I + public final fun component9 ()I + public final fun copy (ZLandroid/graphics/drawable/Drawable;Ljava/lang/Integer;IFFIIIILandroid/graphics/drawable/Drawable;)Lio/getstream/chat/android/ui/gallery/MediaAttachmentGridViewStyle; + public static synthetic fun copy$default (Lio/getstream/chat/android/ui/gallery/MediaAttachmentGridViewStyle;ZLandroid/graphics/drawable/Drawable;Ljava/lang/Integer;IFFIIIILandroid/graphics/drawable/Drawable;ILjava/lang/Object;)Lio/getstream/chat/android/ui/gallery/MediaAttachmentGridViewStyle; + public fun equals (Ljava/lang/Object;)Z + public final fun getImagePlaceholder ()Landroid/graphics/drawable/Drawable; + public final fun getPlayVideoButtonIcon ()Landroid/graphics/drawable/Drawable; + public final fun getPlayVideoIconBackgroundColor ()I + public final fun getPlayVideoIconCornerRadius ()F + public final fun getPlayVideoIconElevation ()F + public final fun getPlayVideoIconPaddingBottom ()I + public final fun getPlayVideoIconPaddingEnd ()I + public final fun getPlayVideoIconPaddingStart ()I + public final fun getPlayVideoIconPaddingTop ()I + public final fun getPlayVideoIconTint ()Ljava/lang/Integer; + public final fun getShowUserAvatars ()Z + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + public final class io/getstream/chat/android/ui/gallery/options/AttachmentGalleryOptionsViewStyle { public fun (Lio/getstream/chat/android/ui/common/style/TextStyle;IZLandroid/graphics/drawable/Drawable;ZLandroid/graphics/drawable/Drawable;ZLandroid/graphics/drawable/Drawable;ZLandroid/graphics/drawable/Drawable;I)V public final fun component1 ()Lio/getstream/chat/android/ui/common/style/TextStyle; @@ -1684,8 +1752,8 @@ public final class io/getstream/chat/android/ui/gallery/options/AttachmentGaller public final fun getOptionTextStyle ()Lio/getstream/chat/android/ui/common/style/TextStyle; public final fun getReplyOptionDrawable ()Landroid/graphics/drawable/Drawable; public final fun getReplyOptionEnabled ()Z - public final fun getSaveImageOptionDrawable ()Landroid/graphics/drawable/Drawable; - public final fun getSaveImageOptionEnabled ()Z + public final fun getSaveMediaOptionDrawable ()Landroid/graphics/drawable/Drawable; + public final fun getSaveMediaOptionEnabled ()Z public final fun getShowInChatOptionDrawable ()Landroid/graphics/drawable/Drawable; public final fun getShowInChatOptionEnabled ()Z public fun hashCode ()I diff --git a/stream-chat-android-ui-components/detekt-baseline.xml b/stream-chat-android-ui-components/detekt-baseline.xml index fe90fa973a0..3700d8f5c9e 100644 --- a/stream-chat-android-ui-components/detekt-baseline.xml +++ b/stream-chat-android-ui-components/detekt-baseline.xml @@ -25,6 +25,7 @@ LargeClass:MessageInputView.kt$MessageInputView : ConstraintLayout LargeClass:MessageListView.kt$MessageListView : ConstraintLayout LongMethod:AttachmentGalleryOptionsViewStyle.kt$AttachmentGalleryOptionsViewStyle.Companion$operator fun invoke(context: Context, it: TypedArray): AttachmentGalleryOptionsViewStyle + LongMethod:AttachmentGalleryViewMediaStyle.kt$AttachmentGalleryViewMediaStyle.Companion$operator fun invoke(context: Context, it: TypedArray): AttachmentGalleryViewMediaStyle LongMethod:AttachmentsPickerDialogStyle.kt$AttachmentsPickerDialogStyle.Companion$internal operator fun invoke(context: Context, attrs: AttributeSet?): AttachmentsPickerDialogStyle LongMethod:AvatarStyle.kt$AvatarStyle.Companion$operator fun invoke(context: Context, attrs: AttributeSet?): AvatarStyle LongMethod:ChannelActionsDialogViewStyle.kt$ChannelActionsDialogViewStyle.Companion$operator fun invoke(context: Context, attrs: AttributeSet?): ChannelActionsDialogViewStyle @@ -33,6 +34,7 @@ LongMethod:FileAttachmentViewStyle.kt$FileAttachmentViewStyle.Companion$operator fun invoke(context: Context, attrs: AttributeSet?): FileAttachmentViewStyle LongMethod:GiphyMediaAttachmentViewStyle.kt$GiphyMediaAttachmentViewStyle.Companion$internal operator fun invoke(context: Context, attrs: AttributeSet?): GiphyMediaAttachmentViewStyle LongMethod:GiphyViewHolderStyle.kt$GiphyViewHolderStyle.Companion$operator fun invoke(context: Context, attributes: TypedArray): GiphyViewHolderStyle + LongMethod:MediaAttachmentGridViewStyle.kt$MediaAttachmentGridViewStyle.Companion$operator fun invoke(context: Context, it: TypedArray): MediaAttachmentGridViewStyle LongMethod:MentionListViewStyle.kt$MentionListViewStyle.Companion$operator fun invoke(context: Context, attrs: AttributeSet?): MentionListViewStyle LongMethod:MessageComposerViewStyle.kt$MessageComposerViewStyle.Companion$internal operator fun invoke(context: Context, attrs: AttributeSet?): MessageComposerViewStyle LongMethod:MessageInputViewStyle.kt$MessageInputViewStyle.Companion$internal operator fun invoke(context: Context, attrs: AttributeSet?): MessageInputViewStyle @@ -48,7 +50,6 @@ LongMethod:SwipeViewHolder.kt$SwipeViewHolder$@SuppressLint("ClickableViewAccessibility") public fun setSwipeListener(view: View, swipeListener: ChannelListView.SwipeListener?) LongParameterList:EditReactionsBubbleDrawer.kt$EditReactionsBubbleDrawer$( context: Context, canvas: Canvas, bubbleWidth: Int, bubbleHeight: Int, isMyMessage: Boolean, isSingleReaction: Boolean, messageAnchorPosition: Float, canvasBounds: IntRange, ) LongParameterList:GiphyMediaAttachmentViewStyle.kt$GiphyMediaAttachmentViewStyle$( public val progressIcon: Drawable, public val giphyIcon: Drawable, public val placeholderIcon: Drawable, @ColorInt public val imageBackgroundColor: Int, public val giphyType: GiphyInfoType, public val scaleType: ImageView.ScaleType, public val sizingMode: GiphySizingMode, public val width: Int, public val height: Int, public val dimensionRatio: Float, ) - MagicNumber:AttachmentGalleryActivity.kt$AttachmentGalleryActivity$500 MagicNumber:ChannelAvatarView.kt$ChannelAvatarView$0.5f MagicNumber:ChannelAvatarView.kt$ChannelAvatarView$3 MagicNumber:ChannelAvatarView.kt$ChannelAvatarView$4 @@ -73,7 +74,13 @@ MagicNumber:UserAvatarView.kt$UserAvatarView$8f MaxLineLength:AttachmentActivity.kt$AttachmentActivity$Toast.makeText(this, getString(R.string.stream_ui_message_list_attachment_display_error), Toast.LENGTH_SHORT).show() MaxLineLength:AttachmentGalleryActivity.kt$AttachmentGalleryActivity$deleteOptionHandler = { setResultAndFinish(AttachmentOptionResult.Delete(attachmentGalleryResultItem)) } - MaxLineLength:AttachmentGalleryActivity.kt$AttachmentGalleryActivity$showInChatOptionHandler = { setResultAndFinish(AttachmentOptionResult.ShowInChat(attachmentGalleryResultItem)) } + MaxLineLength:AttachmentGalleryViewMediaStyle.kt$AttachmentGalleryViewMediaStyle.Companion$R.styleable.AttachmentGalleryVideoAttachments_streamUiAttachmentGalleryViewMediaPlayVideoIconBackgroundColor + MaxLineLength:AttachmentGalleryViewMediaStyle.kt$AttachmentGalleryViewMediaStyle.Companion$R.styleable.AttachmentGalleryVideoAttachments_streamUiAttachmentGalleryViewMediaPlayVideoIconCornerRadius + MaxLineLength:AttachmentGalleryViewMediaStyle.kt$AttachmentGalleryViewMediaStyle.Companion$R.styleable.AttachmentGalleryVideoAttachments_streamUiAttachmentGalleryViewMediaPlayVideoIconElevation + MaxLineLength:AttachmentGalleryViewMediaStyle.kt$AttachmentGalleryViewMediaStyle.Companion$R.styleable.AttachmentGalleryVideoAttachments_streamUiAttachmentGalleryViewMediaPlayVideoIconPaddingBottom + MaxLineLength:AttachmentGalleryViewMediaStyle.kt$AttachmentGalleryViewMediaStyle.Companion$R.styleable.AttachmentGalleryVideoAttachments_streamUiAttachmentGalleryViewMediaPlayVideoIconPaddingEnd + MaxLineLength:AttachmentGalleryViewMediaStyle.kt$AttachmentGalleryViewMediaStyle.Companion$R.styleable.AttachmentGalleryVideoAttachments_streamUiAttachmentGalleryViewMediaPlayVideoIconPaddingStart + MaxLineLength:AttachmentGalleryViewMediaStyle.kt$AttachmentGalleryViewMediaStyle.Companion$R.styleable.AttachmentGalleryVideoAttachments_streamUiAttachmentGalleryViewMediaPlayVideoIconPaddingTop MaxLineLength:AttachmentSelectionDialogStyle.kt$AttachmentSelectionDialogStyle.Companion$allowAccessToCameraIcon = context.getDrawableCompat(R.drawable.stream_ui_attachment_permission_camera)!! MaxLineLength:AttachmentSelectionDialogStyle.kt$AttachmentSelectionDialogStyle.Companion$allowAccessToGalleryIcon = context.getDrawableCompat(R.drawable.stream_ui_attachment_permission_media)!! MaxLineLength:AttachmentsPickerDialogStyle.kt$AttachmentsPickerDialogStyle$* @@ -160,6 +167,7 @@ MaxLineLength:SuggestionListController.kt$SuggestionListController$suggestionListControllerListener?.onSuggestionListUiVisibilityChanged(suggestionListUi.isSuggestionListVisible()) MaxLineLength:SuggestionListViewStyle.kt$SuggestionListViewStyle$* MaxLineLength:SwipeViewHolder.kt$SwipeViewHolder$* If the swipe view is swiped of not. When true, swipe view is completely swiped, when false it is in the default state + MaxLineLength:TransformStyle.kt$TransformStyle$public MaxLineLength:TransformStyle.kt$TransformStyle$public var attachmentGalleryOptionsStyleTransformer: StyleTransformer<AttachmentGalleryOptionsViewStyle> = noopTransformer() MaxLineLength:TransformStyle.kt$TransformStyle$public var defaultQuotedAttachmentViewStyleTransformer: StyleTransformer<DefaultQuotedAttachmentViewStyle> = noopTransformer() MaxLineLength:TransformStyle.kt$TransformStyle$public var unsupportedAttachmentStyleTransformer: StyleTransformer<UnsupportedAttachmentViewStyle> = noopTransformer() @@ -176,6 +184,8 @@ TooGenericExceptionCaught:AttachmentGalleryActivity.kt$AttachmentGalleryActivity$e: Exception TooGenericExceptionCaught:ChatFontsImpl.kt$ChatFontsImpl$t: Throwable TooGenericExceptionCaught:MessageInputView.kt$MessageInputView$e: Exception + TooGenericExceptionThrown:AttachmentGalleryPagerAdapter.kt$AttachmentGalleryPagerAdapter$throw Throwable("Unsupported attachment type") + TooManyFunctions:AttachmentGalleryActivity.kt$AttachmentGalleryActivity : AppCompatActivity TooManyFunctions:ChannelListView.kt$ChannelListView : FrameLayout TooManyFunctions:MessageInputFieldView.kt$MessageInputFieldView : FrameLayout TooManyFunctions:MessageInputView.kt$MessageInputView : ConstraintLayout diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/TransformStyle.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/TransformStyle.kt index 20fe3d2ea0b..1cf1b6bd81d 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/TransformStyle.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/TransformStyle.kt @@ -19,6 +19,8 @@ package io.getstream.chat.android.ui import io.getstream.chat.android.ui.avatar.AvatarStyle import io.getstream.chat.android.ui.channel.list.ChannelActionsDialogViewStyle import io.getstream.chat.android.ui.channel.list.ChannelListViewStyle +import io.getstream.chat.android.ui.gallery.AttachmentGalleryViewMediaStyle +import io.getstream.chat.android.ui.gallery.MediaAttachmentGridViewStyle import io.getstream.chat.android.ui.gallery.options.AttachmentGalleryOptionsViewStyle import io.getstream.chat.android.ui.mention.list.MentionListViewStyle import io.getstream.chat.android.ui.message.composer.AttachmentsPickerDialogStyle @@ -55,7 +57,7 @@ public object TransformStyle { public var singleReactionViewStyleTransformer: StyleTransformer = noopTransformer() public var channelActionsDialogStyleTransformer: StyleTransformer = noopTransformer() public var giphyViewHolderStyleTransformer: StyleTransformer = noopTransformer() - public var imageAttachmentStyleTransformer: StyleTransformer = noopTransformer() + public var mediaAttachmentStyleTransformer: StyleTransformer = noopTransformer() public var messageReplyStyleTransformer: StyleTransformer = noopTransformer() public var fileAttachmentStyleTransformer: StyleTransformer = noopTransformer() public var unsupportedAttachmentStyleTransformer: StyleTransformer = noopTransformer() @@ -70,6 +72,8 @@ public object TransformStyle { public var attachmentGalleryOptionsStyleTransformer: StyleTransformer = noopTransformer() public var messageComposerStyleTransformer: StyleTransformer = noopTransformer() public var attachmentsPickerStyleTransformer: StyleTransformer = noopTransformer() + public var attachmentGalleryViewMediaStyle: StyleTransformer = noopTransformer() + public var mediaAttachmentGridViewStyle: StyleTransformer = noopTransformer() private fun noopTransformer() = StyleTransformer { it } } diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/common/internal/AttachmentUtils.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/common/internal/AttachmentUtils.kt index 6b3ce6b847f..74da69baa65 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/common/internal/AttachmentUtils.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/common/internal/AttachmentUtils.kt @@ -35,7 +35,7 @@ private val FILE_THUMB_TRANSFORMATION = RoundedCorners(3.dpToPxPrecise()) internal fun ImageView.loadAttachmentThumb(attachment: Attachment): Disposable { return with(attachment) { when { - type == ModelType.attach_video && !thumbUrl.isNullOrBlank() -> + type == ModelType.attach_video && ChatUI.videoThumbnailsEnabled && !thumbUrl.isNullOrBlank() -> load(data = thumbUrl, transformation = FILE_THUMB_TRANSFORMATION) type == ModelType.attach_image && !imageUrl.isNullOrBlank() -> load(data = imageUrl, transformation = FILE_THUMB_TRANSFORMATION) diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/gallery/AttachmentGalleryActivity.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/gallery/AttachmentGalleryActivity.kt index c0ad12201f1..46dbcf8691f 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/gallery/AttachmentGalleryActivity.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/gallery/AttachmentGalleryActivity.kt @@ -22,18 +22,22 @@ import android.net.Uri import android.os.Bundle import android.os.Parcelable import android.text.format.DateUtils +import android.view.View +import android.widget.Toast import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity import androidx.constraintlayout.widget.ConstraintSet import androidx.core.content.ContextCompat import androidx.core.view.isVisible +import androidx.lifecycle.lifecycleScope import androidx.viewpager2.widget.ViewPager2 import com.getstream.sdk.chat.StreamFileUtil import com.getstream.sdk.chat.images.StreamImageLoader +import com.getstream.sdk.chat.model.ModelType import com.getstream.sdk.chat.utils.PermissionChecker import com.getstream.sdk.chat.utils.extensions.constrainViewToParentBySide -import com.getstream.sdk.chat.utils.extensions.imagePreviewUrl import com.getstream.sdk.chat.utils.formatTime +import io.getstream.chat.android.client.models.Attachment import io.getstream.chat.android.core.internal.coroutines.DispatcherProvider import io.getstream.chat.android.ui.ChatUI import io.getstream.chat.android.ui.R @@ -48,9 +52,9 @@ import io.getstream.chat.android.ui.gallery.options.AttachmentGalleryOptionsView import io.getstream.chat.android.ui.gallery.options.internal.AttachmentGalleryOptionsDialogFragment import io.getstream.chat.android.ui.gallery.overview.internal.MediaAttachmentDialogFragment import io.getstream.logging.StreamLog -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.delay +import kotlinx.coroutines.Job import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import kotlinx.parcelize.Parcelize import java.util.Date @@ -71,15 +75,23 @@ public class AttachmentGalleryActivity : AppCompatActivity() { private var showInChatOptionEnabled: Boolean = true /** - * If the "save image" option is present in the list + * If the "save media" option is present in the list */ - private var saveImageOptionEnabled: Boolean = true + private var saveMediaOptionEnabled: Boolean = true /** * If the "delete" option is present in the list. */ private var deleteOptionEnabled: Boolean = true + /** + * Represents the job used to share an attachment. + * + * If the attachment is larger, this could end up being a longer run job + * so cancel it accordingly. + */ + private var shareMediaJob: Job? = null + private val initialIndex: Int by lazy { intent.getIntExtra(EXTRA_KEY_INITIAL_INDEX, 0) } private val viewModel: AttachmentGalleryViewModel by viewModels() private lateinit var adapter: AttachmentGalleryPagerAdapter @@ -98,21 +110,6 @@ public class AttachmentGalleryActivity : AppCompatActivity() { } private var isFullScreen = false - private var onSharePictureListener: (pictureUri: Uri) -> Unit = { pictureUri -> - ContextCompat.startActivity( - this, - Intent.createChooser( - Intent(Intent.ACTION_SEND).apply { - type = "image/*" - putExtra(Intent.EXTRA_STREAM, pictureUri) - addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - }, - getString(R.string.stream_ui_attachment_gallery_share), - ), - null - ) - } - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -129,7 +126,7 @@ public class AttachmentGalleryActivity : AppCompatActivity() { } else { this.attachmentGalleryItems = attachmentGalleryItems setupGalleryAdapter() - setupShareImageButton() + setupShareMediaButton() setupAttachmentActionsButton() obtainOptionsViewStyle() observePageChanges() @@ -139,8 +136,8 @@ public class AttachmentGalleryActivity : AppCompatActivity() { private fun setupGalleryAdapter() { adapter = AttachmentGalleryPagerAdapter( fragmentActivity = this, - imageList = attachmentGalleryItems.map { it.attachment.imagePreviewUrl!! }, - imageClickListener = { + mediaList = attachmentGalleryItems.map { it.attachment }, + mediaClickListener = { isFullScreen = !isFullScreen if (isFullScreen) enterFullScreenMode() else exitFullScreenMode() } @@ -149,30 +146,140 @@ public class AttachmentGalleryActivity : AppCompatActivity() { binding.galleryViewPager.setCurrentItem(initialIndex, false) } - private fun setupShareImageButton() { - binding.shareImageButton.setOnClickListener { - it.isEnabled = false - GlobalScope.launch(DispatcherProvider.Main) { + /** + * Sets an on click listener with media sharing capability + * on the share button. + */ + private fun setupShareMediaButton() { + binding.shareMediaButton.setOnClickListener { + if (shareMediaJob == null || shareMediaJob?.isCompleted == true) { + setSharingInProgressUi() + shareMedia() + } else { + shareMediaJob?.cancel() + setNoSharingInProgressUi() + } + } + } + + /** + * Begins the process of sharing media depending on + * the attachment type. + */ + private fun shareMedia() { + val attachment = adapter.getItem(binding.galleryViewPager.currentItem) + + when (attachment.type) { + ModelType.attach_image -> shareImage(attachment = attachment) + ModelType.attach_video -> shareVideo(attachment = attachment) + else -> toastFailedShare() + } + } + + /** + * Prepares an image attachment for sharing. + * + * @param attachment The attachment to share. + **/ + private fun shareImage(attachment: Attachment) { + val imageUrl = attachment.imageUrl + + if (imageUrl != null) { + shareMediaJob?.cancel() + + shareMediaJob = lifecycleScope.launch(DispatcherProvider.Main) { StreamImageLoader.instance().loadAsBitmap( context = applicationContext, - url = adapter.getItem(binding.galleryViewPager.currentItem) + url = imageUrl )?.let { bitmap -> StreamFileUtil.writeImageToSharableFile(applicationContext, bitmap) - }?.let(onSharePictureListener) + }?.let { + launchShareActivity(mediaUri = it, attachmentType = attachment.type) + } + setNoSharingInProgressUi() + } + } else { + toastFailedShare() + setNoSharingInProgressUi() + } + } - delay(500) - it.isEnabled = true + /** + * Prepares a video attachment for sharing. + * + * @param attachment The attachment to share. + */ + private fun shareVideo(attachment: Attachment) { + shareMediaJob?.cancel() + + shareMediaJob = lifecycleScope.launch(DispatcherProvider.IO) { + val result = withContext(DispatcherProvider.IO) { + StreamFileUtil.writeFileToShareableFile( + context = applicationContext, + attachment = attachment + ) + } + + if (result.isSuccess) { + launchShareActivity(mediaUri = result.data(), attachmentType = attachment.type) + } else { + withContext(DispatcherProvider.Main) { + toastFailedShare() + } + } + withContext(DispatcherProvider.Main) { + setNoSharingInProgressUi() + } + } + } + + private fun launchShareActivity( + mediaUri: Uri, + attachmentType: String?, + ) { + val type = when (attachmentType) { + ModelType.attach_image -> "image/*" + ModelType.attach_video -> "video/*" + else -> { + toastFailedShare() + return } } + + ContextCompat.startActivity( + this, + Intent.createChooser( + Intent(Intent.ACTION_SEND).apply { + this.type = type + putExtra(Intent.EXTRA_STREAM, mediaUri) + addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + }, + getString(R.string.stream_ui_attachment_gallery_share), + ), + null + ) + } + + /** + * Displays a toast saying that sharing the attachment has failed. + */ + private fun toastFailedShare() { + Toast.makeText( + applicationContext, + applicationContext.getString(R.string.stream_ui_attachment_gallery_share_error), + Toast.LENGTH_SHORT + ).show() } private fun setupAttachmentActionsButton() { binding.attachmentActionsButton.setOnClickListener { AttachmentGalleryOptionsDialogFragment.newInstance( - showInChatOptionHandler = { setResultAndFinish(AttachmentOptionResult.ShowInChat(attachmentGalleryResultItem)) }, + showInChatOptionHandler = { + setResultAndFinish(AttachmentOptionResult.ShowInChat(attachmentGalleryResultItem)) + }, deleteOptionHandler = { setResultAndFinish(AttachmentOptionResult.Delete(attachmentGalleryResultItem)) }, replyOptionHandler = { setResultAndFinish(AttachmentOptionResult.Reply(attachmentGalleryResultItem)) }, - saveImageOptionHandler = handleSaveImage, + saveMediaOptionHandler = handleSaveMedia, isMessageMine = attachmentGalleryItems[binding.galleryViewPager.currentItem].isMine, ).show(supportFragmentManager, AttachmentGalleryOptionsDialogFragment.TAG) } @@ -189,7 +296,7 @@ public class AttachmentGalleryActivity : AppCompatActivity() { onGalleryPageSelected(initialIndex) } - private val handleSaveImage = AttachmentGalleryOptionsDialogFragment.AttachmentOptionHandler { + private val handleSaveMedia = AttachmentGalleryOptionsDialogFragment.AttachmentOptionHandler { permissionChecker.checkWriteStoragePermissions( binding.root, onPermissionGranted = { @@ -207,7 +314,7 @@ public class AttachmentGalleryActivity : AppCompatActivity() { } private fun onGalleryPageSelected(position: Int) { - binding.imageCountTextView.text = getString( + binding.mediaInformationTextView.text = getString( R.string.stream_ui_attachment_gallery_count, position + 1, attachmentGalleryItems.size @@ -241,7 +348,7 @@ public class AttachmentGalleryActivity : AppCompatActivity() { binding.galleryOverviewButton.setOnClickListener { MediaAttachmentDialogFragment.newInstance() .apply { - setImageClickListener { + setMediaClickListener { binding.galleryViewPager.setCurrentItem(it, true) dismiss() } @@ -279,7 +386,47 @@ public class AttachmentGalleryActivity : AppCompatActivity() { } /** - * Obtains style attributes for the options list. + * Sets up the UI in a way that signals to the user + * that sharing is in progress. + */ + private fun setSharingInProgressUi() { + with(binding) { + mediaInformationTextView.text = + applicationContext.getString(R.string.stream_ui_attachment_gallery_preview_preparing) + progressBar.visibility = View.VISIBLE + + val drawable = + ContextCompat.getDrawable(applicationContext, R.drawable.stream_ui_ic_clear)?.mutate() + ?.apply { setTint(ContextCompat.getColor(applicationContext, R.color.stream_ui_black)) } + + binding.shareMediaButton.setImageDrawable(drawable) + } + } + + /** + * Sets up the UI in a way that signals to the user + * that no attachment is being shared currently. + */ + private fun setNoSharingInProgressUi() { + with(binding) { + progressBar.visibility = View.GONE + shareMediaButton.setImageDrawable( + ContextCompat.getDrawable( + applicationContext, + R.drawable.stream_ui_ic_share + ) + ) + + mediaInformationTextView.text = getString( + R.string.stream_ui_attachment_gallery_count, + galleryViewPager.currentItem + 1, + attachmentGalleryItems.size + ) + } + } + + /** + * Obtains style attributes for the gallery and its children. */ private fun obtainOptionsViewStyle() { try { @@ -292,11 +439,11 @@ public class AttachmentGalleryActivity : AppCompatActivity() { val style = AttachmentGalleryOptionsViewStyle(this, it) replyOptionEnabled = style.replyOptionEnabled showInChatOptionEnabled = style.showInChatOptionEnabled - saveImageOptionEnabled = style.saveImageOptionEnabled + saveMediaOptionEnabled = style.saveMediaOptionEnabled deleteOptionEnabled = style.deleteOptionEnabled } } catch (e: Exception) { - logger.e(e) { "Failed to obtain style attribute for the options menu" } + logger.e(e) { "Failed to obtain styles" } } } @@ -309,10 +456,19 @@ public class AttachmentGalleryActivity : AppCompatActivity() { private fun shouldShowOptionsButton(isMine: Boolean): Boolean { return replyOptionEnabled || showInChatOptionEnabled || - saveImageOptionEnabled || + saveMediaOptionEnabled || (deleteOptionEnabled && isMine) } + /** + * Clear the cache when the activity is + * destroyed. + */ + override fun onDestroy() { + super.onDestroy() + StreamFileUtil.clearStreamCache(context = applicationContext) + } + public fun interface AttachmentShowInChatOptionHandler { public fun onClick(result: AttachmentGalleryResultItem) } diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/gallery/AttachmentGalleryViewMediaStyle.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/gallery/AttachmentGalleryViewMediaStyle.kt new file mode 100644 index 00000000000..fa1768ce7a6 --- /dev/null +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/gallery/AttachmentGalleryViewMediaStyle.kt @@ -0,0 +1,174 @@ +/* + * Copyright (c) 2014-2022 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.ui.gallery + +import android.app.ActionBar +import android.content.Context +import android.content.res.TypedArray +import android.graphics.drawable.Drawable +import android.util.AttributeSet +import androidx.annotation.ColorInt +import androidx.core.content.ContextCompat +import io.getstream.chat.android.ui.R +import io.getstream.chat.android.ui.TransformStyle +import io.getstream.chat.android.ui.common.extensions.internal.getColorCompat +import io.getstream.chat.android.ui.common.extensions.internal.getColorOrNull +import io.getstream.chat.android.ui.common.extensions.internal.getDrawableCompat + +/** + * Controls the appearance of the main portion of the attachment gallery used to view media. + * + * @param viewMediaPlayVideoButtonIcon The drawable for the play button icon displayed above videos in the main viewing + * area of the gallery. + * @param viewMediaPlayVideoButtonIcon The drawable for the play button icon displayed above videos in the main viewing + * area of the gallery. + * @param viewMediaPlayVideoIconTint Tints the icon overlaid above videos in the main viewing area of the gallery. + * @param viewMediaPlayVideoIconBackgroundColor The background color of the View containing the play button overlaid + * above the main viewing area of the gallery. + * @param viewMediaPlayVideoIconCornerRadius The corner radius of the play button in the main viewing area of the + * gallery. + * @param viewMediaPlayVideoIconElevation The elevation of the play button in the main viewing area of the gallery. + * @param viewMediaPlayVideoIconPaddingTop Sets the top padding of the play video icon displayed above the main viewing + * area of the gallery. + * @param viewMediaPlayVideoIconPaddingBottom Sets the bottom padding of the play video icon in the main viewing + * area of the gallery. + * @param viewMediaPlayVideoIconPaddingStart Sets the start padding of the play video icon in the main viewing + * area of the gallery. + * @param viewMediaPlayVideoIconPaddingEnd Sets the end padding of the play video icon in the main viewing + * area of the gallery. + * @param viewMediaPlayVideoIconWidth Sets the width of the play video button in the main viewing area of the gallery. + * @param viewMediaPlayVideoIconHeight Sets the width of the play video button in the main viewing area of the gallery. + * @param imagePlaceholder A placeholder drawable used before the image is loaded. + */ +public data class AttachmentGalleryViewMediaStyle( + val viewMediaPlayVideoButtonIcon: Drawable?, + @ColorInt val viewMediaPlayVideoIconTint: Int?, + @ColorInt val viewMediaPlayVideoIconBackgroundColor: Int, + val viewMediaPlayVideoIconCornerRadius: Float, + val viewMediaPlayVideoIconElevation: Float, + val viewMediaPlayVideoIconPaddingTop: Int, + val viewMediaPlayVideoIconPaddingBottom: Int, + val viewMediaPlayVideoIconPaddingStart: Int, + val viewMediaPlayVideoIconPaddingEnd: Int, + val viewMediaPlayVideoIconWidth: Int, + val viewMediaPlayVideoIconHeight: Int, + val imagePlaceholder: Drawable?, +) { + + internal companion object { + + operator fun invoke(context: Context, attrs: AttributeSet?): AttachmentGalleryViewMediaStyle { + context.obtainStyledAttributes( + attrs, + R.styleable.AttachmentGalleryVideoAttachments, + R.attr.streamUiAttachmentGalleryVideoAttachmentsStyle, + R.style.StreamUi_AttachmentGallery_VideoAttachments + ).let { styledAttributes -> + val style = AttachmentGalleryViewMediaStyle(context, styledAttributes) + styledAttributes.recycle() + return style + } + } + + operator fun invoke(context: Context, it: TypedArray): AttachmentGalleryViewMediaStyle { + + val viewMediaPlayVideoButtonIcon = it.getDrawable( + R.styleable.AttachmentGalleryVideoAttachments_streamUiAttachmentGalleryViewMediaPlayVideoButtonIcon + ) ?: context.getDrawableCompat(R.drawable.stream_ui_ic_play) + + val viewMediaPlayVideoIconTint = it.getColorOrNull( + R.styleable.AttachmentGalleryVideoAttachments_streamUiAttachmentGalleryViewMediaPlayVideoIconTint + ) + + val viewMediaPlayVideoIconBackgroundColor = + it.getColor( + R.styleable.AttachmentGalleryVideoAttachments_streamUiAttachmentGalleryViewMediaPlayVideoIconBackgroundColor, + context.getColorCompat(R.color.stream_ui_literal_white) + ) + + val viewMediaPlayVideoIconCornerRadius = + it.getDimension( + R.styleable.AttachmentGalleryVideoAttachments_streamUiAttachmentGalleryViewMediaPlayVideoIconCornerRadius, + 0f + ) + + val viewMediaPlayVideoIconElevation = + it.getDimension( + R.styleable.AttachmentGalleryVideoAttachments_streamUiAttachmentGalleryViewMediaPlayVideoIconElevation, + 0f + ) + + val viewMediaPlayVideoIconPaddingTop = + it.getDimensionPixelSize( + R.styleable.AttachmentGalleryVideoAttachments_streamUiAttachmentGalleryViewMediaPlayVideoIconPaddingTop, + 0 + ) + + val viewMediaPlayVideoIconPaddingBottom = + it.getDimensionPixelSize( + R.styleable.AttachmentGalleryVideoAttachments_streamUiAttachmentGalleryViewMediaPlayVideoIconPaddingBottom, + 0 + ) + + val viewMediaPlayVideoIconPaddingStart = + it.getDimensionPixelSize( + R.styleable.AttachmentGalleryVideoAttachments_streamUiAttachmentGalleryViewMediaPlayVideoIconPaddingStart, + 0 + ) + + val viewMediaPlayVideoIconPaddingEnd = + it.getDimensionPixelSize( + R.styleable.AttachmentGalleryVideoAttachments_streamUiAttachmentGalleryViewMediaPlayVideoIconPaddingEnd, + 0 + ) + + val viewMediaPlayVideoIconWidth = + it.getLayoutDimension( + R.styleable.AttachmentGalleryVideoAttachments_streamUiAttachmentGalleryViewMediaPlayIconWidth, + ActionBar.LayoutParams.WRAP_CONTENT + ) + + val viewMediaPlayVideoIconHeight = + it.getLayoutDimension( + R.styleable.AttachmentGalleryVideoAttachments_streamUiAttachmentGalleryViewMediaPlayIconHeight, + ActionBar.LayoutParams.WRAP_CONTENT + ) + + val imagePlaceholder = it.getDrawable( + R.styleable.AttachmentGalleryVideoAttachments_streamUiAttachmentGalleryViewMediaImagePlaceholder + ) ?: ContextCompat.getDrawable( + context, + R.drawable.stream_ui_picture_placeholder + ) + + return AttachmentGalleryViewMediaStyle( + viewMediaPlayVideoButtonIcon = viewMediaPlayVideoButtonIcon, + viewMediaPlayVideoIconTint = viewMediaPlayVideoIconTint, + viewMediaPlayVideoIconBackgroundColor = viewMediaPlayVideoIconBackgroundColor, + viewMediaPlayVideoIconCornerRadius = viewMediaPlayVideoIconCornerRadius, + viewMediaPlayVideoIconElevation = viewMediaPlayVideoIconElevation, + viewMediaPlayVideoIconPaddingTop = viewMediaPlayVideoIconPaddingTop, + viewMediaPlayVideoIconPaddingBottom = viewMediaPlayVideoIconPaddingBottom, + viewMediaPlayVideoIconPaddingStart = viewMediaPlayVideoIconPaddingStart, + viewMediaPlayVideoIconPaddingEnd = viewMediaPlayVideoIconPaddingEnd, + viewMediaPlayVideoIconWidth = viewMediaPlayVideoIconWidth, + viewMediaPlayVideoIconHeight = viewMediaPlayVideoIconHeight, + imagePlaceholder = imagePlaceholder, + ).let(TransformStyle.attachmentGalleryViewMediaStyle::transform) + } + } +} diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/gallery/MediaAttachmentGridViewStyle.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/gallery/MediaAttachmentGridViewStyle.kt new file mode 100644 index 00000000000..1b8bd7fdb61 --- /dev/null +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/gallery/MediaAttachmentGridViewStyle.kt @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2014-2022 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.ui.gallery + +import android.content.Context +import android.content.res.TypedArray +import android.graphics.drawable.Drawable +import android.util.AttributeSet +import androidx.annotation.ColorInt +import androidx.core.content.ContextCompat +import io.getstream.chat.android.ui.R +import io.getstream.chat.android.ui.TransformStyle +import io.getstream.chat.android.ui.common.extensions.internal.getColorCompat +import io.getstream.chat.android.ui.common.extensions.internal.getColorOrNull +import io.getstream.chat.android.ui.common.extensions.internal.getDrawableCompat +import io.getstream.chat.android.ui.gallery.overview.MediaAttachmentGridView + +/** + * Controls the appearance of [MediaAttachmentGridView]. + * + * @param showUserAvatars Controls whether the avatar of the user who had sent the attachment is displayed + * over the attachment preview or not. + * @param playVideoButtonIcon The drawable for the play button icon overlaid above video attachments in + * the media overview segment of the gallery. + * @param playVideoIconTint Tints the icon overlaid on top of video attachment previews in the media + * overview segment of the gallery. + * @param playVideoIconBackgroundColor The background color of the View containing the play button in + * the media overview segment of the gallery. + * @param playVideoIconCornerRadius The corner radius of the play button in the media + * overview segment of the gallery.. + * @param playVideoIconElevation The elevation of the play button in the media + * overview segment of the gallery + * @param playVideoIconPaddingTop Sets the top padding of the play video icon in the media + * overview segment of the gallery. + * @param playVideoIconPaddingBottom Sets the bottom padding of the play video icon in the media + * overview segment of the gallery. + * @param playVideoIconPaddingStart Sets the start padding of the play video icon in the media + * overview segment of the gallery. + * @param playVideoIconPaddingEnd Sets the end padding of the play video icon in the media + * overview segment of the gallery. + * @param imagePlaceholder A placeholder drawable used before the image is loaded. + */ +public data class MediaAttachmentGridViewStyle( + val showUserAvatars: Boolean, + val playVideoButtonIcon: Drawable?, + @ColorInt val playVideoIconTint: Int?, + @ColorInt val playVideoIconBackgroundColor: Int, + val playVideoIconCornerRadius: Float, + val playVideoIconElevation: Float, + val playVideoIconPaddingTop: Int, + val playVideoIconPaddingBottom: Int, + val playVideoIconPaddingStart: Int, + val playVideoIconPaddingEnd: Int, + val imagePlaceholder: Drawable?, +) { + internal companion object { + + operator fun invoke(context: Context, attrs: AttributeSet?): MediaAttachmentGridViewStyle { + context.obtainStyledAttributes( + attrs, + R.styleable.MediaAttachmentGridView, + R.attr.streamUiMediaAttachmentGridViewStyle, + R.style.StreamUi_MediaAttachmentGridView + ).let { styledAttributes -> + val style = MediaAttachmentGridViewStyle(context, styledAttributes) + styledAttributes.recycle() + return style + } + } + + operator fun invoke(context: Context, it: TypedArray): MediaAttachmentGridViewStyle { + + val showUserAvatars = + it.getBoolean( + R.styleable.MediaAttachmentGridView_streamUiMediaAttachmentGridViewShowUserAvatars, + true + ) + + val playVideoButtonIcon = it.getDrawable( + R.styleable.MediaAttachmentGridView_streamUiMediaAttachmentGridViewPlayVideoButtonIcon + ) ?: context.getDrawableCompat(R.drawable.stream_ui_ic_play) + + val playVideoIconTint = it.getColorOrNull( + R.styleable.MediaAttachmentGridView_streamUiMediaAttachmentGridViewPlayVideoIconTint + ) + + val playVideoIconBackgroundColor = + it.getColor( + R.styleable.MediaAttachmentGridView_streamUiMediaAttachmentGridViewPlayVideoIconBackgroundColor, + context.getColorCompat(R.color.stream_ui_literal_white) + ) + + val playVideoIconCornerRadius = + it.getDimension( + R.styleable.MediaAttachmentGridView_streamUiMediaAttachmentGridViewPlayVideoIconCornerRadius, + 0f + ) + + val playVideoIconElevation = + it.getDimension( + R.styleable.MediaAttachmentGridView_streamUiMediaAttachmentGridViewPlayVideoIconElevation, + 0f + ) + + val playVideoIconPaddingTop = + it.getDimensionPixelSize( + R.styleable.MediaAttachmentGridView_streamUiMediaAttachmentGridViewPlayVideoIconPaddingTop, + 0 + ) + + val playVideoIconPaddingBottom = + it.getDimensionPixelSize( + R.styleable.MediaAttachmentGridView_streamUiMediaAttachmentGridViewPlayVideoIconPaddingBottom, + 0 + ) + + val playVideoIconPaddingStart = + it.getDimensionPixelSize( + R.styleable.MediaAttachmentGridView_streamUiMediaAttachmentGridViewPlayVideoIconPaddingStart, + 0 + ) + + val playVideoIconPaddingEnd = + it.getDimensionPixelSize( + R.styleable.MediaAttachmentGridView_streamUiMediaAttachmentGridViewPlayVideoIconPaddingEnd, + 0 + ) + + val imagePlaceholder = it.getDrawable( + R.styleable.MediaAttachmentGridView_streamUiMediaAttachmentGridViewImagePlaceholder + ) ?: ContextCompat.getDrawable( + context, + R.drawable.stream_ui_picture_placeholder + ) + + return MediaAttachmentGridViewStyle( + showUserAvatars = showUserAvatars, + playVideoButtonIcon = playVideoButtonIcon, + playVideoIconTint = playVideoIconTint, + playVideoIconBackgroundColor = playVideoIconBackgroundColor, + playVideoIconCornerRadius = playVideoIconCornerRadius, + playVideoIconElevation = playVideoIconElevation, + playVideoIconPaddingTop = playVideoIconPaddingTop, + playVideoIconPaddingBottom = playVideoIconPaddingBottom, + playVideoIconPaddingStart = playVideoIconPaddingStart, + playVideoIconPaddingEnd = playVideoIconPaddingEnd, + imagePlaceholder = imagePlaceholder, + ).let(TransformStyle.mediaAttachmentGridViewStyle::transform) + } + } +} diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/gallery/internal/AttachmentGalleryPageFragment.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/gallery/internal/AttachmentGalleryImagePageFragment.kt similarity index 61% rename from stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/gallery/internal/AttachmentGalleryPageFragment.kt rename to stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/gallery/internal/AttachmentGalleryImagePageFragment.kt index 0c0104e7853..baa281b42d1 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/gallery/internal/AttachmentGalleryPageFragment.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/gallery/internal/AttachmentGalleryImagePageFragment.kt @@ -22,21 +22,22 @@ import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment import com.getstream.sdk.chat.images.load -import io.getstream.chat.android.ui.databinding.StreamUiItemImageGalleryBinding +import io.getstream.chat.android.client.models.Attachment +import io.getstream.chat.android.ui.databinding.StreamUiItemAttachmentGalleryImageBinding -internal class AttachmentGalleryPageFragment : Fragment() { +internal class AttachmentGalleryImagePageFragment : Fragment() { - private var _binding: StreamUiItemImageGalleryBinding? = null + private var _binding: StreamUiItemAttachmentGalleryImageBinding? = null private val binding get() = _binding!! - private val imageUrl: String by lazy { - requireNotNull(requireArguments().getString(ARG_IMAGE_URL)) { "Image URL must not be null" } + private val imageUrl: String? by lazy { + requireArguments().getString(ARG_IMAGE_URL) } private var imageClickListener: () -> Unit = {} override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { - return StreamUiItemImageGalleryBinding.inflate(inflater) + return StreamUiItemAttachmentGalleryImageBinding.inflate(inflater) .apply { _binding = this } .root } @@ -44,7 +45,18 @@ internal class AttachmentGalleryPageFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) with(binding.photoView) { - load(data = imageUrl) + load( + data = imageUrl, + onStart = { + binding.placeHolderImageView.visibility = View.GONE + binding.progressBar.visibility = View.VISIBLE + }, + onComplete = { + binding.placeHolderImageView.visibility = View.VISIBLE + binding.progressBar.visibility = View.GONE + } + ) + setOnClickListener { imageClickListener() } @@ -59,10 +71,10 @@ internal class AttachmentGalleryPageFragment : Fragment() { companion object { private const val ARG_IMAGE_URL = "image_url" - fun create(imageUrl: String, imageClickListener: () -> Unit = {}): Fragment { - return AttachmentGalleryPageFragment().apply { + fun create(attachment: Attachment, imageClickListener: () -> Unit = {}): Fragment { + return AttachmentGalleryImagePageFragment().apply { arguments = Bundle().apply { - putString(ARG_IMAGE_URL, imageUrl) + putString(ARG_IMAGE_URL, attachment.imageUrl) } this.imageClickListener = imageClickListener } diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/gallery/internal/AttachmentGalleryPagerAdapter.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/gallery/internal/AttachmentGalleryPagerAdapter.kt index 1f9daebdc5b..b8274d85867 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/gallery/internal/AttachmentGalleryPagerAdapter.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/gallery/internal/AttachmentGalleryPagerAdapter.kt @@ -19,17 +19,25 @@ package io.getstream.chat.android.ui.gallery.internal import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity import androidx.viewpager2.adapter.FragmentStateAdapter +import com.getstream.sdk.chat.model.ModelType +import io.getstream.chat.android.client.models.Attachment internal class AttachmentGalleryPagerAdapter( fragmentActivity: FragmentActivity, - private val imageList: List, - private val imageClickListener: () -> Unit, + private val mediaList: List, + private val mediaClickListener: () -> Unit, ) : FragmentStateAdapter(fragmentActivity) { - override fun getItemCount(): Int = imageList.size + override fun getItemCount(): Int = mediaList.size override fun createFragment(position: Int): Fragment { - return AttachmentGalleryPageFragment.create(getItem(position), imageClickListener) + val attachment = getItem(position) + + return when (attachment.type) { + ModelType.attach_image -> AttachmentGalleryImagePageFragment.create(attachment, mediaClickListener) + ModelType.attach_video -> AttachmentGalleryVideoPageFragment.create(attachment, mediaClickListener) + else -> throw Throwable("Unsupported attachment type") + } } - fun getItem(position: Int): String = imageList[position] + fun getItem(position: Int): Attachment = mediaList[position] } diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/gallery/internal/AttachmentGalleryVideoPageFragment.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/gallery/internal/AttachmentGalleryVideoPageFragment.kt new file mode 100644 index 00000000000..d7288544139 --- /dev/null +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/gallery/internal/AttachmentGalleryVideoPageFragment.kt @@ -0,0 +1,264 @@ +/* + * Copyright (c) 2014-2022 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.ui.gallery.internal + +import android.content.Context +import android.net.Uri +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.MediaController +import android.widget.Toast +import androidx.core.view.updateLayoutParams +import androidx.fragment.app.Fragment +import com.getstream.sdk.chat.images.load +import io.getstream.chat.android.client.models.Attachment +import io.getstream.chat.android.ui.ChatUI +import io.getstream.chat.android.ui.R +import io.getstream.chat.android.ui.common.extensions.internal.createStreamThemeWrapper +import io.getstream.chat.android.ui.databinding.StreamUiItemAttachmentGalleryVideoBinding +import io.getstream.chat.android.ui.gallery.AttachmentGalleryViewMediaStyle +import io.getstream.chat.android.ui.gallery.options.AttachmentGalleryOptionsViewStyle + +internal class AttachmentGalleryVideoPageFragment : Fragment() { + + private var _binding: StreamUiItemAttachmentGalleryVideoBinding? = null + private val binding get() = _binding!! + + /** + * Holds the style necessary to stylize the play button. + * + * Fetching the style depends on [Context] so use this property + * only after it has been obtained during or after [onAttach]. + */ + private val style by lazy { + AttachmentGalleryViewMediaStyle( + context = requireContext().createStreamThemeWrapper(), + attrs = null + ) + } + + /** + * If the video has been prepared and the video player + * is ready for playback. + * + * VideoView does not expose state so we use these to track + * state externally. + */ + private var playbackPrepared = false + set(value) { + field = value + if (playbackPrepared && playbackStartRequested) { + binding.thumbnailImageView.visibility = View.GONE + binding.playButtonCardView.visibility = View.GONE + binding.progressBar.visibility = View.GONE + } else { + binding.thumbnailImageView.visibility = View.VISIBLE + binding.playButtonCardView.visibility = View.VISIBLE + binding.progressBar.visibility = View.GONE + } + } + + /** + * If the user has pressed play. + * + * VideoView does not expose state so we use these to track + * state externally. + */ + private var playbackStartRequested = false + set(value) { + field = value + if (!playbackPrepared && playbackStartRequested) { + binding.progressBar.visibility = View.VISIBLE + binding.playButtonCardView.visibility = View.GONE + binding.thumbnailImageView.visibility = View.GONE + } else { + binding.playButtonCardView.visibility = View.GONE + binding.thumbnailImageView.visibility = View.GONE + binding.progressBar.visibility = View.GONE + } + } + + /** + * Resets the state and hides the controller. + * + * Important for resetting state when paging through + * fragments in a pager. + */ + override fun onPause() { + super.onPause() + resetState() + mediaController.hide() + } + + /** + * Contains the URL to the thumbnail of the video. + */ + private val thumbUrl: String? by lazy { + requireArguments().getString(ARG_THUMB_URL) + } + + /** + * Contains the URL necessary to reproduce the video. + */ + private val assetUrl: String? by lazy { + requireArguments().getString(ARG_ASSET_URL) + } + + private val mediaController: MediaController by lazy { + createMediaController(requireContext()) + } + + private var imageClickListener: () -> Unit = {} + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + return StreamUiItemAttachmentGalleryVideoBinding.inflate(inflater) + .apply { _binding = this } + .root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupVideoThumbnail() + setupPlayButton() + loadVideo() + } + + private fun setupVideoThumbnail() { + if (ChatUI.videoThumbnailsEnabled) { + binding.thumbnailImageView.load(data = thumbUrl) + } + } + + /** + * Resets this video page's state. + */ + private fun resetState() { + playbackStartRequested = false + binding.thumbnailImageView.visibility = View.VISIBLE + binding.playButtonCardView.visibility = View.VISIBLE + } + + /** + * Sets up the play icon overlaid above video attachment previews + * by pulling relevant values from [AttachmentGalleryOptionsViewStyle]. + **/ + private fun setupPlayButton() { + setupPlayButtonIcon() + setupPlayButtonCard() + setupOnPlayButtonClickedListener() + } + + /** + * Sets up the play button icon hosted in an image view. + */ + private fun setupPlayButtonIcon() { + with(binding.playButtonImageView) { + updateLayoutParams { + width = style.viewMediaPlayVideoIconWidth + height = style.viewMediaPlayVideoIconHeight + } + + val playVideoDrawable = style.viewMediaPlayVideoButtonIcon?.mutate()?.apply { + val tintColor = style.viewMediaPlayVideoIconTint + + if (tintColor != null) { + this.setTint(tintColor) + } + } + + setImageDrawable(playVideoDrawable) + setPaddingRelative( + style.viewMediaPlayVideoIconPaddingStart, + style.viewMediaPlayVideoIconPaddingTop, + style.viewMediaPlayVideoIconPaddingEnd, + style.viewMediaPlayVideoIconPaddingBottom + ) + } + } + + /** + * Sets up the card wrapping the image view that contains the + * play button icon. + */ + private fun setupPlayButtonCard() { + with(binding.playButtonCardView) { + elevation = style.viewMediaPlayVideoIconElevation + setCardBackgroundColor(style.viewMediaPlayVideoIconBackgroundColor) + radius = style.viewMediaPlayVideoIconCornerRadius + } + } + + private fun setupOnPlayButtonClickedListener() { + binding.playButtonCardView.setOnClickListener { + binding.videoView.start() + playbackStartRequested = true + } + } + + private fun loadVideo() { + binding.videoView.apply { + setVideoURI(Uri.parse(assetUrl)) + this.setMediaController(mediaController) + setOnErrorListener { _, _, _ -> + Toast.makeText( + requireContext(), + requireContext().getString(R.string.stream_ui_attachment_gallery_video_display_error), + Toast.LENGTH_SHORT + ).show() + true + } + setOnPreparedListener { + playbackPrepared = true + } + + mediaController.setAnchorView(binding.root) + } + } + + /** + * Creates a custom instance of [MediaController]. + * + * @param context The Context used to create the [MediaController]. + */ + private fun createMediaController( + context: Context, + ): MediaController { + return object : MediaController(context) {} + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } + + companion object { + private const val ARG_THUMB_URL = "thumb_url" + private const val ARG_ASSET_URL = "asset_url" + + fun create(attachment: Attachment, imageClickListener: () -> Unit = {}): Fragment { + return AttachmentGalleryVideoPageFragment().apply { + arguments = Bundle().apply { + putString(ARG_THUMB_URL, attachment.thumbUrl) + putString(ARG_ASSET_URL, attachment.assetUrl) + } + this.imageClickListener = imageClickListener + } + } + } +} diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/gallery/options/AttachmentGalleryOptionsViewStyle.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/gallery/options/AttachmentGalleryOptionsViewStyle.kt index 43fb70a08d8..9f7262880e1 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/gallery/options/AttachmentGalleryOptionsViewStyle.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/gallery/options/AttachmentGalleryOptionsViewStyle.kt @@ -30,10 +30,10 @@ import io.getstream.chat.android.ui.common.extensions.internal.getDimension import io.getstream.chat.android.ui.common.extensions.internal.getDrawableCompat import io.getstream.chat.android.ui.common.extensions.internal.use import io.getstream.chat.android.ui.common.style.TextStyle -import io.getstream.chat.android.ui.gallery.options.internal.AttachmentGalleryOptionsView +import io.getstream.chat.android.ui.gallery.AttachmentGalleryActivity /** - * Style for [AttachmentGalleryOptionsView]. + * Controls how video attachments are displayed inside of the [AttachmentGalleryActivity]. * * @param optionTextStyle The text style of each option. * @param backgroundColor The background color of the options dialog. @@ -41,8 +41,8 @@ import io.getstream.chat.android.ui.gallery.options.internal.AttachmentGalleryOp * @param replyOptionDrawable The icon to the "reply" option. * @param showInChatOptionEnabled If the "show in chat" option present in the list. * @param showInChatOptionDrawable The icon for the "show in chat" option. - * @param saveImageOptionEnabled If the "save image" option is present in the list. - * @param saveImageOptionDrawable The icon for the "save image" option. + * @param saveMediaOptionEnabled If the "save media" option is present in the list. + * @param saveMediaOptionDrawable The icon for the "save media" option. * @param deleteOptionEnabled If the "delete" option is present in the list. * @param deleteOptionDrawable The icon for the "delete" option. * @param deleteOptionTextColor The text color of the "delete" option. @@ -54,8 +54,8 @@ public data class AttachmentGalleryOptionsViewStyle( val replyOptionDrawable: Drawable, val showInChatOptionEnabled: Boolean, val showInChatOptionDrawable: Drawable, - val saveImageOptionEnabled: Boolean, - val saveImageOptionDrawable: Drawable, + val saveMediaOptionEnabled: Boolean, + val saveMediaOptionDrawable: Drawable, val deleteOptionEnabled: Boolean, val deleteOptionDrawable: Drawable, @ColorInt val deleteOptionTextColor: Int, @@ -117,13 +117,13 @@ public data class AttachmentGalleryOptionsViewStyle( R.styleable.AttachmentOptionsView_streamUiShowInChatIcon ) ?: context.getDrawableCompat(R.drawable.stream_ui_ic_show_in_chat)!! - val saveImageOptionEnabled = it.getBoolean( - R.styleable.AttachmentOptionsView_streamUiSaveImageEnabled, + val saveMediaOptionEnabled = it.getBoolean( + R.styleable.AttachmentOptionsView_streamUiSaveMediaEnabled, true ) - val saveImageOptionDrawable = it.getDrawable( - R.styleable.AttachmentOptionsView_streamUiSaveImageIcon, + val saveMediaOptionDrawable = it.getDrawable( + R.styleable.AttachmentOptionsView_streamUiSaveMediaIcon, ) ?: context.getDrawableCompat(R.drawable.stream_ui_ic_download)!! val deleteOptionEnabled = it.getBoolean( @@ -147,8 +147,8 @@ public data class AttachmentGalleryOptionsViewStyle( replyOptionDrawable = replyOptionDrawable, showInChatOptionEnabled = showInChatOptionEnabled, showInChatOptionDrawable = showInChatOptionDrawable, - saveImageOptionEnabled = saveImageOptionEnabled, - saveImageOptionDrawable = saveImageOptionDrawable, + saveMediaOptionEnabled = saveMediaOptionEnabled, + saveMediaOptionDrawable = saveMediaOptionDrawable, deleteOptionEnabled = deleteOptionEnabled, deleteOptionDrawable = deleteOptionDrawable, deleteOptionTextColor = deleteOptionTextColor, diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/gallery/options/internal/AttachmentGalleryOptionsDialogFragment.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/gallery/options/internal/AttachmentGalleryOptionsDialogFragment.kt index 6ba9b3780ec..47f23ea60c6 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/gallery/options/internal/AttachmentGalleryOptionsDialogFragment.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/gallery/options/internal/AttachmentGalleryOptionsDialogFragment.kt @@ -92,7 +92,7 @@ internal class AttachmentGalleryOptionsDialogFragment : FullScreenDialogFragment showInChatOptionHandler?.onAttachmentOptionClick() dismiss() } - binding.attachmentOptionsMenu.setSaveImageClickListener { + binding.attachmentOptionsMenu.setSaveMediaClickListener { saveImageOptionHandler?.onAttachmentOptionClick() dismiss() } @@ -170,21 +170,21 @@ internal class AttachmentGalleryOptionsDialogFragment : FullScreenDialogFragment * @param showInChatOptionHandler A callback for the "show in chat" option. * @param replyOptionHandler A callback for the "reply" option. * @param deleteOptionHandler A callback for the "delete" option. - * @param saveImageOptionHandler A callback for the "save image" option. + * @param saveMediaOptionHandler A callback for the "save image" option. * @param isMessageMine If the message belongs to the current user. */ fun newInstance( showInChatOptionHandler: AttachmentOptionHandler, replyOptionHandler: AttachmentOptionHandler, deleteOptionHandler: AttachmentOptionHandler, - saveImageOptionHandler: AttachmentOptionHandler, + saveMediaOptionHandler: AttachmentOptionHandler, isMessageMine: Boolean, ): AttachmentGalleryOptionsDialogFragment { return AttachmentGalleryOptionsDialogFragment().apply { setShowInChatOptionHandler(showInChatOptionHandler) setReplyOptionHandler(replyOptionHandler) setDeleteOptionHandler(deleteOptionHandler) - setSaveImageOptionHandler(saveImageOptionHandler) + setSaveImageOptionHandler(saveMediaOptionHandler) setIsMessageMine(isMessageMine) } } diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/gallery/options/internal/AttachmentGalleryOptionsView.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/gallery/options/internal/AttachmentGalleryOptionsView.kt index 64706ccf98a..be78bd86b0e 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/gallery/options/internal/AttachmentGalleryOptionsView.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/gallery/options/internal/AttachmentGalleryOptionsView.kt @@ -28,8 +28,8 @@ import io.getstream.chat.android.ui.databinding.StreamUiAttachmentGalleryOptions import io.getstream.chat.android.ui.gallery.options.AttachmentGalleryOptionsViewStyle /** - * Represents the image options menu, used to perform different actions for - * the currently selected image. + * Represents the media options menu, used to perform different actions for + * the currently selected media. */ internal class AttachmentGalleryOptionsView : FrameLayout { @@ -81,12 +81,12 @@ internal class AttachmentGalleryOptionsView : FrameLayout { } /** - * Registers a callback to be invoked when the "save image" option is clicked. + * Registers a callback to be invoked when the "save media" option is clicked. * * @param listener The callback that will run. */ - fun setSaveImageClickListener(listener: OnClickListener) { - binding.saveImage.setOnClickListener(listener) + fun setSaveMediaClickListener(listener: OnClickListener) { + binding.saveMedia.setOnClickListener(listener) } /** @@ -120,11 +120,11 @@ internal class AttachmentGalleryOptionsView : FrameLayout { binding.showInChat.isVisible = false } - if (style.saveImageOptionEnabled) { - binding.saveImage.setStartDrawable(style.saveImageOptionDrawable) - binding.saveImage.setTextStyle(style.optionTextStyle) + if (style.saveMediaOptionEnabled) { + binding.saveMedia.setStartDrawable(style.saveMediaOptionDrawable) + binding.saveMedia.setTextStyle(style.optionTextStyle) } else { - binding.saveImage.isVisible = false + binding.saveMedia.isVisible = false } if (style.deleteOptionEnabled) { diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/gallery/overview/MediaAttachmentGridView.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/gallery/overview/MediaAttachmentGridView.kt index b519d5a28bc..f2744e0001d 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/gallery/overview/MediaAttachmentGridView.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/gallery/overview/MediaAttachmentGridView.kt @@ -26,12 +26,11 @@ import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.RecyclerView import com.getstream.sdk.chat.utils.Utils import com.getstream.sdk.chat.view.EndlessScrollListener -import io.getstream.chat.android.ui.R import io.getstream.chat.android.ui.common.extensions.internal.createStreamThemeWrapper import io.getstream.chat.android.ui.common.extensions.internal.streamThemeInflater -import io.getstream.chat.android.ui.common.extensions.internal.use import io.getstream.chat.android.ui.databinding.StreamUiMediaAttachmentGridViewBinding import io.getstream.chat.android.ui.gallery.AttachmentGalleryItem +import io.getstream.chat.android.ui.gallery.MediaAttachmentGridViewStyle import io.getstream.chat.android.ui.gallery.overview.internal.MediaAttachmentAdapter import java.text.DateFormat import java.text.SimpleDateFormat @@ -42,8 +41,14 @@ public class MediaAttachmentGridView : FrameLayout { private val binding = StreamUiMediaAttachmentGridViewBinding.inflate(streamThemeInflater, this, true) private val dateFormat: DateFormat = SimpleDateFormat("MMM yyyy", Locale.US) private var showUserAvatars: Boolean = false + + /** + * Style used to change the appearance of the view + */ + private lateinit var style: MediaAttachmentGridViewStyle + private val adapter: MediaAttachmentAdapter by lazy { - MediaAttachmentAdapter(showUserAvatars = showUserAvatars) { + MediaAttachmentAdapter(style = style) { mediaClickListener?.onClick(it) } } @@ -63,26 +68,25 @@ public class MediaAttachmentGridView : FrameLayout { private var mediaClickListener: MediaClickListener? = null private var loadMoreListener: OnLoadMoreListener? = null - public constructor(context: Context) : super(context.createStreamThemeWrapper()) { - init(context, null) - } + public constructor(context: Context) : this(context, null) - public constructor(context: Context, attrs: AttributeSet?) : super(context.createStreamThemeWrapper(), attrs) { - init(context, attrs) - } + public constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0) public constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super( context.createStreamThemeWrapper(), attrs, defStyleAttr ) { - init(context, attrs) + init(attrs) } - private fun init(context: Context, attrs: AttributeSet?) { - context.obtainStyledAttributes(attrs, R.styleable.MediaAttachmentGridView).use { - showUserAvatars = it.getBoolean(R.styleable.MediaAttachmentGridView_streamUiShowUserAvatars, false) - } + private fun init(attrs: AttributeSet?) { + style = MediaAttachmentGridViewStyle( + context = context, + attrs = attrs + ) + + showUserAvatars = style.showUserAvatars binding.mediaRecyclerView.apply { adapter = this@MediaAttachmentGridView.adapter diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/gallery/overview/internal/MediaAttachmentAdapter.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/gallery/overview/internal/MediaAttachmentAdapter.kt index 3b86bee5d3e..76c12346d0d 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/gallery/overview/internal/MediaAttachmentAdapter.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/gallery/overview/internal/MediaAttachmentAdapter.kt @@ -16,39 +16,58 @@ package io.getstream.chat.android.ui.gallery.overview.internal +import android.view.View import android.view.ViewGroup import androidx.core.view.isVisible import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import com.getstream.sdk.chat.images.load +import com.getstream.sdk.chat.model.ModelType import com.getstream.sdk.chat.utils.extensions.imagePreviewUrl -import io.getstream.chat.android.ui.R +import io.getstream.chat.android.ui.ChatUI import io.getstream.chat.android.ui.common.extensions.internal.streamThemeInflater import io.getstream.chat.android.ui.databinding.StreamUiItemMediaAttachmentBinding import io.getstream.chat.android.ui.gallery.AttachmentGalleryItem +import io.getstream.chat.android.ui.gallery.MediaAttachmentGridViewStyle +import io.getstream.chat.android.ui.gallery.options.AttachmentGalleryOptionsViewStyle internal class MediaAttachmentAdapter( - private val showUserAvatars: Boolean, + private val style: MediaAttachmentGridViewStyle, private val mediaAttachmentClickListener: MediaAttachmentClickListener, ) : ListAdapter( AttachmentGalleryItemDiffCallback ) { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MediaAttachmentViewHolder { + return StreamUiItemMediaAttachmentBinding .inflate(parent.streamThemeInflater, parent, false) - .let { MediaAttachmentViewHolder(it, showUserAvatars, mediaAttachmentClickListener) } + .let { + MediaAttachmentViewHolder( + binding = it, + mediaAttachmentClickListener = mediaAttachmentClickListener, + style = style + ) + } } override fun onBindViewHolder(holder: MediaAttachmentViewHolder, position: Int) { holder.bind(getItem(position)) } + /** + * The ViewHolder used for displaying media previews. + * + * @param binding The binding used to build a UI. + * @param mediaAttachmentClickListener Click listener used to detect + * clicks on media attachment previews. + * @param style Used to change the appearance of the UI. + */ class MediaAttachmentViewHolder( private val binding: StreamUiItemMediaAttachmentBinding, - private val showUserAvatars: Boolean, private val mediaAttachmentClickListener: MediaAttachmentClickListener, + private val style: MediaAttachmentGridViewStyle, ) : RecyclerView.ViewHolder(binding.root) { init { @@ -58,17 +77,101 @@ internal class MediaAttachmentAdapter( } fun bind(attachmentGalleryItem: AttachmentGalleryItem) { + setupUserAvatar(attachmentGalleryItem) + setupPlayButton(attachmentGalleryItem.attachment.type) + loadImage(attachmentGalleryItem) + } + + /** + * Loads the given image. + * + * @param attachmentGalleryItem The attachment to be displayed. + */ + private fun loadImage(attachmentGalleryItem: AttachmentGalleryItem) { + val isVideoAttachment = attachmentGalleryItem.attachment.type == ModelType.attach_video + val shouldLoadImage = attachmentGalleryItem.attachment.type == ModelType.attach_image || + (attachmentGalleryItem.attachment.type == ModelType.attach_video && ChatUI.videoThumbnailsEnabled) + binding.mediaImageView.load( - data = attachmentGalleryItem.attachment.imagePreviewUrl, - placeholderResId = R.drawable.stream_ui_placeholder, + data = if (shouldLoadImage) { + attachmentGalleryItem.attachment.imagePreviewUrl + } else { + null + }, + placeholderDrawable = if (!isVideoAttachment) { + style.imagePlaceholder + } else { + null + }, + onStart = { binding.progressBar.visibility = View.VISIBLE }, + onComplete = { + binding.playButtonCardView.isVisible = + attachmentGalleryItem.attachment.type == ModelType.attach_video + binding.progressBar.visibility = View.GONE + } ) + } + /** + * Toggles the visibility of user avatars and load the + * given image. + * + * @param attachmentGalleryItem The attachment to be displayed. + */ + private fun setupUserAvatar(attachmentGalleryItem: AttachmentGalleryItem) { val user = attachmentGalleryItem.user - if (showUserAvatars) { - binding.userAvatarView.isVisible = true + + if (style.showUserAvatars) { + binding.userAvatarCardView.isVisible = true binding.userAvatarView.setUser(user) } else { - binding.userAvatarView.isVisible = false + binding.userAvatarCardView.isVisible = false + } + } + + /** + * Sets up the play icon overlaid above video attachment previews + * by pulling relevant values from [AttachmentGalleryOptionsViewStyle]. + **/ + private fun setupPlayButton(attachmentType: String?) { + if (attachmentType == ModelType.attach_video) { + setupPlayButtonIcon() + setupPlayButtonCard() + } + } + + /** + * Sets up the play button icon hosted in an image view. + */ + private fun setupPlayButtonIcon() { + with(binding.playButtonImageView) { + val playVideoDrawable = style.playVideoButtonIcon?.mutate()?.apply { + val tintColor = style.playVideoIconTint + + if (tintColor != null) { + this.setTint(tintColor) + } + } + + setImageDrawable(playVideoDrawable) + setPaddingRelative( + style.playVideoIconPaddingStart, + style.playVideoIconPaddingTop, + style.playVideoIconPaddingEnd, + style.playVideoIconPaddingBottom + ) + } + } + + /** + * Sets up the card wrapping the image view that contains the + * play button icon. + */ + private fun setupPlayButtonCard() { + with(binding.playButtonCardView) { + elevation = style.playVideoIconElevation + setCardBackgroundColor(style.playVideoIconBackgroundColor) + radius = style.playVideoIconCornerRadius } } } diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/gallery/overview/internal/MediaAttachmentDialogFragment.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/gallery/overview/internal/MediaAttachmentDialogFragment.kt index e5a9f8fefa5..172ec795d3c 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/gallery/overview/internal/MediaAttachmentDialogFragment.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/gallery/overview/internal/MediaAttachmentDialogFragment.kt @@ -31,7 +31,7 @@ internal class MediaAttachmentDialogFragment : BottomSheetDialogFragment() { private val binding get() = _binding!! private val viewModel: AttachmentGalleryViewModel by viewModels() - private var imageClickListener: (Int) -> Unit = {} + private var mediaClickListener: (Int) -> Unit = {} override fun getTheme(): Int = R.style.StreamUiBottomSheetDialogTheme @@ -48,7 +48,7 @@ internal class MediaAttachmentDialogFragment : BottomSheetDialogFragment() { dismiss() } mediaAttachmentGridView.setMediaClickListener { - imageClickListener.invoke(it) + mediaClickListener.invoke(it) } viewModel.attachmentGalleryItemsLiveData.observe(viewLifecycleOwner, mediaAttachmentGridView::setAttachments) } @@ -59,8 +59,8 @@ internal class MediaAttachmentDialogFragment : BottomSheetDialogFragment() { super.onDestroyView() } - fun setImageClickListener(listener: (Int) -> Unit) { - imageClickListener = listener + fun setMediaClickListener(listener: (Int) -> Unit) { + mediaClickListener = listener } internal companion object { diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/message/list/MessageListView.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/message/list/MessageListView.kt index 3a372f676e2..484e5d83f49 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/message/list/MessageListView.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/message/list/MessageListView.kt @@ -77,7 +77,6 @@ import io.getstream.chat.android.ui.common.extensions.internal.createStreamTheme import io.getstream.chat.android.ui.common.extensions.internal.getFragmentManager import io.getstream.chat.android.ui.common.extensions.internal.isCurrentUser import io.getstream.chat.android.ui.common.extensions.internal.isGiphy -import io.getstream.chat.android.ui.common.extensions.internal.isImage import io.getstream.chat.android.ui.common.extensions.internal.streamThemeInflater import io.getstream.chat.android.ui.common.extensions.internal.use import io.getstream.chat.android.ui.common.extensions.isGiphyNotEphemeral @@ -390,7 +389,7 @@ public class MessageListView : ConstraintLayout { }, optionClickListener: (MessageAction) -> Unit = { messageAction: MessageAction -> handleMessageAction(messageAction) - } + }, ) { MessageOptionsDialogFragment.newInstance( context = context, @@ -472,9 +471,15 @@ public class MessageListView : ConstraintLayout { } } else { val destination = when { - message.attachments.all(Attachment::isImage) -> { + message.attachments.all { + attachment.type == ModelType.attach_image || + attachment.type == ModelType.attach_video + } -> { val filteredAttachments = message.attachments - .filter { it.type == ModelType.attach_image && !it.imagePreviewUrl.isNullOrEmpty() } + .filter { + it.type == ModelType.attach_image && !it.imagePreviewUrl.isNullOrEmpty() || + it.type == ModelType.attach_video && !it.assetUrl.isNullOrEmpty() + } val attachmentGalleryItems = filteredAttachments.map { AttachmentGalleryItem( attachment = it, @@ -1496,7 +1501,7 @@ public class MessageListView : ConstraintLayout { * @param handler The handler to use. */ public fun setAttachmentShowInChatOptionClickHandler( - handler: AttachmentGalleryActivity.AttachmentShowInChatOptionHandler + handler: AttachmentGalleryActivity.AttachmentShowInChatOptionHandler, ) { this._attachmentShowInChatOptionClickHandler = handler } diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/message/list/adapter/view/MediaAttachmentViewStyle.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/message/list/adapter/view/MediaAttachmentViewStyle.kt index 1a662c76f36..de9a13aa76d 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/message/list/adapter/view/MediaAttachmentViewStyle.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/message/list/adapter/view/MediaAttachmentViewStyle.kt @@ -32,7 +32,7 @@ import io.getstream.chat.android.ui.common.style.TextStyle /** * Style for [io.getstream.chat.android.ui.message.list.adapter.view.internal.MediaAttachmentView]. - * Use this class together with [TransformStyle.imageAttachmentStyleTransformer] to change styles programmatically. + * Use this class together with [TransformStyle.mediaAttachmentStyleTransformer] to change styles programmatically. * * @param progressIcon Animated progress drawable. Default value is * [R.drawable.stream_ui_rotating_indeterminate_progress_gradient]. @@ -194,7 +194,7 @@ public data class MediaAttachmentViewStyle( playVideoIconPaddingStart = playVideoIconPaddingStart, playVideoIconPaddingEnd = playVideoIconPaddingEnd, playVideoIconCornerRadius = playVideoIconCornerRadius - ).let(TransformStyle.imageAttachmentStyleTransformer::transform) + ).let(TransformStyle.mediaAttachmentStyleTransformer::transform) } } } diff --git a/stream-chat-android-ui-components/src/main/res/layout/stream_ui_activity_attachment_gallery.xml b/stream-chat-android-ui-components/src/main/res/layout/stream_ui_activity_attachment_gallery.xml index b5c33936272..f18c0830f6c 100644 --- a/stream-chat-android-ui-components/src/main/res/layout/stream_ui_activity_attachment_gallery.xml +++ b/stream-chat-android-ui-components/src/main/res/layout/stream_ui_activity_attachment_gallery.xml @@ -44,13 +44,13 @@ - + + + + + + + + diff --git a/stream-chat-android-ui-components/src/main/res/layout/stream_ui_attachment_gallery_options_view.xml b/stream-chat-android-ui-components/src/main/res/layout/stream_ui_attachment_gallery_options_view.xml index e79d2b6effa..d9dac86a34c 100644 --- a/stream-chat-android-ui-components/src/main/res/layout/stream_ui_attachment_gallery_options_view.xml +++ b/stream-chat-android-ui-components/src/main/res/layout/stream_ui_attachment_gallery_options_view.xml @@ -49,9 +49,9 @@ /> diff --git a/stream-chat-android-ui-components/src/main/res/layout/stream_ui_dialog_media_attachment.xml b/stream-chat-android-ui-components/src/main/res/layout/stream_ui_dialog_media_attachment.xml index be10998a990..ed23dfe5604 100644 --- a/stream-chat-android-ui-components/src/main/res/layout/stream_ui_dialog_media_attachment.xml +++ b/stream-chat-android-ui-components/src/main/res/layout/stream_ui_dialog_media_attachment.xml @@ -54,7 +54,7 @@ android:id="@+id/mediaAttachmentGridView" android:layout_width="match_parent" android:layout_height="wrap_content" - app:streamUiShowUserAvatars="true" + app:streamUiMediaAttachmentGridViewShowUserAvatars="true" /> diff --git a/stream-chat-android-ui-components/src/main/res/layout/stream_ui_item_attachment_gallery_image.xml b/stream-chat-android-ui-components/src/main/res/layout/stream_ui_item_attachment_gallery_image.xml new file mode 100644 index 00000000000..02dd1444d58 --- /dev/null +++ b/stream-chat-android-ui-components/src/main/res/layout/stream_ui_item_attachment_gallery_image.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + diff --git a/stream-chat-android-ui-components/src/main/res/layout/stream_ui_item_attachment_gallery_video.xml b/stream-chat-android-ui-components/src/main/res/layout/stream_ui_item_attachment_gallery_video.xml new file mode 100644 index 00000000000..71195a1f7ed --- /dev/null +++ b/stream-chat-android-ui-components/src/main/res/layout/stream_ui_item_attachment_gallery_video.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/stream-chat-android-ui-components/src/main/res/layout/stream_ui_item_image_gallery.xml b/stream-chat-android-ui-components/src/main/res/layout/stream_ui_item_image_gallery.xml deleted file mode 100644 index 4c3f106c724..00000000000 --- a/stream-chat-android-ui-components/src/main/res/layout/stream_ui_item_image_gallery.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - diff --git a/stream-chat-android-ui-components/src/main/res/layout/stream_ui_item_media_attachment.xml b/stream-chat-android-ui-components/src/main/res/layout/stream_ui_item_media_attachment.xml index bdaa8a500dc..d7bd511a845 100644 --- a/stream-chat-android-ui-components/src/main/res/layout/stream_ui_item_media_attachment.xml +++ b/stream-chat-android-ui-components/src/main/res/layout/stream_ui_item_media_attachment.xml @@ -21,12 +21,14 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:background="?selectableItemBackground" + android:padding="1dp" > - + + + + + + + + + + + diff --git a/stream-chat-android-ui-components/src/main/res/values-en/strings_attachment_gallery.xml b/stream-chat-android-ui-components/src/main/res/values-en/strings_attachment_gallery.xml index 97213e2a35a..bbbaa219aa4 100644 --- a/stream-chat-android-ui-components/src/main/res/values-en/strings_attachment_gallery.xml +++ b/stream-chat-android-ui-components/src/main/res/values-en/strings_attachment_gallery.xml @@ -20,7 +20,7 @@ Reply Delete Show in chat - Save Image + Save Media Share Image diff --git a/stream-chat-android-ui-components/src/main/res/values-es/strings_attachment_gallery.xml b/stream-chat-android-ui-components/src/main/res/values-es/strings_attachment_gallery.xml index b853a3637ad..cefcd3b58f5 100644 --- a/stream-chat-android-ui-components/src/main/res/values-es/strings_attachment_gallery.xml +++ b/stream-chat-android-ui-components/src/main/res/values-es/strings_attachment_gallery.xml @@ -20,7 +20,6 @@ "Eliminar" "Fotos" "Responder" - "Guardar imagen" "Compartir Imagen" "Mostrar en el chat" \ No newline at end of file diff --git a/stream-chat-android-ui-components/src/main/res/values-fr/strings_attachment_gallery.xml b/stream-chat-android-ui-components/src/main/res/values-fr/strings_attachment_gallery.xml index 10a66c26de0..5948481ba40 100644 --- a/stream-chat-android-ui-components/src/main/res/values-fr/strings_attachment_gallery.xml +++ b/stream-chat-android-ui-components/src/main/res/values-fr/strings_attachment_gallery.xml @@ -19,7 +19,6 @@ "Envoyé %1$s à %2$s" "Supprimer" "Réponse" - "Sauvegarder l\'image" "Partager l\'image" "Montrer dans le Chat" \ No newline at end of file diff --git a/stream-chat-android-ui-components/src/main/res/values-hi/strings_attachment_gallery.xml b/stream-chat-android-ui-components/src/main/res/values-hi/strings_attachment_gallery.xml index d72ee83d2e9..92d0baae7a8 100644 --- a/stream-chat-android-ui-components/src/main/res/values-hi/strings_attachment_gallery.xml +++ b/stream-chat-android-ui-components/src/main/res/values-hi/strings_attachment_gallery.xml @@ -20,7 +20,6 @@ "मिटाएं" "फ़ोटोज" "जवाब दें" - "फ़ोटो सेव करें" "फ़ोटो शेयर करें" "चैट में देखें" \ No newline at end of file diff --git a/stream-chat-android-ui-components/src/main/res/values-id/strings_attachment_gallery.xml b/stream-chat-android-ui-components/src/main/res/values-id/strings_attachment_gallery.xml index ea6e854849d..a5978883059 100644 --- a/stream-chat-android-ui-components/src/main/res/values-id/strings_attachment_gallery.xml +++ b/stream-chat-android-ui-components/src/main/res/values-id/strings_attachment_gallery.xml @@ -20,7 +20,6 @@ "Hapus" "Foto" "Balas" - "Simpan Gambar" "Bagikan Gambar" "Tampilkan di chat" \ No newline at end of file diff --git a/stream-chat-android-ui-components/src/main/res/values-in/strings_attachment_gallery.xml b/stream-chat-android-ui-components/src/main/res/values-in/strings_attachment_gallery.xml index 2c6efab0532..796f2065340 100644 --- a/stream-chat-android-ui-components/src/main/res/values-in/strings_attachment_gallery.xml +++ b/stream-chat-android-ui-components/src/main/res/values-in/strings_attachment_gallery.xml @@ -20,7 +20,6 @@ Balas Hapus Tampilkan di chat - Simpan Gambar Bagikan Gambar diff --git a/stream-chat-android-ui-components/src/main/res/values-it/strings_attachment_gallery.xml b/stream-chat-android-ui-components/src/main/res/values-it/strings_attachment_gallery.xml index 3a97bcafca8..bcd5085902c 100644 --- a/stream-chat-android-ui-components/src/main/res/values-it/strings_attachment_gallery.xml +++ b/stream-chat-android-ui-components/src/main/res/values-it/strings_attachment_gallery.xml @@ -20,7 +20,6 @@ "Cancella" "Foto" "Rispondi" - "Salva immagine" "Condividi immagine" "Mostra nella chat" \ No newline at end of file diff --git a/stream-chat-android-ui-components/src/main/res/values-ja/strings_attachment_gallery.xml b/stream-chat-android-ui-components/src/main/res/values-ja/strings_attachment_gallery.xml index 80df931fd63..dd2181937b7 100644 --- a/stream-chat-android-ui-components/src/main/res/values-ja/strings_attachment_gallery.xml +++ b/stream-chat-android-ui-components/src/main/res/values-ja/strings_attachment_gallery.xml @@ -20,7 +20,6 @@ "削除" "写真" "返信" - "画像を保存" "画像を共有する" "チャットで表示" \ No newline at end of file diff --git a/stream-chat-android-ui-components/src/main/res/values-ko/strings_attachment_gallery.xml b/stream-chat-android-ui-components/src/main/res/values-ko/strings_attachment_gallery.xml index d6fd3ec021c..5576ef64e96 100644 --- a/stream-chat-android-ui-components/src/main/res/values-ko/strings_attachment_gallery.xml +++ b/stream-chat-android-ui-components/src/main/res/values-ko/strings_attachment_gallery.xml @@ -20,7 +20,6 @@ "삭제" "사진" "회신" - "이미지를 저장합니다" "이미지 공유" "채팅에서 확인하기" \ No newline at end of file diff --git a/stream-chat-android-ui-components/src/main/res/values/attrs_attachment_gallery_activity.xml b/stream-chat-android-ui-components/src/main/res/values/attrs_attachment_gallery_activity.xml index 7dc1d3ca8a0..d9a54c9b671 100644 --- a/stream-chat-android-ui-components/src/main/res/values/attrs_attachment_gallery_activity.xml +++ b/stream-chat-android-ui-components/src/main/res/values/attrs_attachment_gallery_activity.xml @@ -25,5 +25,7 @@ + + diff --git a/stream-chat-android-ui-components/src/main/res/values/attrs_attachment_gallery_video_attachments.xml b/stream-chat-android-ui-components/src/main/res/values/attrs_attachment_gallery_video_attachments.xml new file mode 100644 index 00000000000..89e8091bbf0 --- /dev/null +++ b/stream-chat-android-ui-components/src/main/res/values/attrs_attachment_gallery_video_attachments.xml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/stream-chat-android-ui-components/src/main/res/values/attrs_attachment_options_view.xml b/stream-chat-android-ui-components/src/main/res/values/attrs_attachment_options_view.xml index 050136e4dc9..f2f02548410 100644 --- a/stream-chat-android-ui-components/src/main/res/values/attrs_attachment_options_view.xml +++ b/stream-chat-android-ui-components/src/main/res/values/attrs_attachment_options_view.xml @@ -41,10 +41,10 @@ - - + + - + diff --git a/stream-chat-android-ui-components/src/main/res/values/attrs_media_attachment_grid_view.xml b/stream-chat-android-ui-components/src/main/res/values/attrs_media_attachment_grid_view.xml index 05b026664af..e0237c4655e 100644 --- a/stream-chat-android-ui-components/src/main/res/values/attrs_media_attachment_grid_view.xml +++ b/stream-chat-android-ui-components/src/main/res/values/attrs_media_attachment_grid_view.xml @@ -17,6 +17,36 @@ - + + + + + + + + + + + + + + + + + + + + + + diff --git a/stream-chat-android-ui-components/src/main/res/values/strings_attachment_gallery.xml b/stream-chat-android-ui-components/src/main/res/values/strings_attachment_gallery.xml index 97213e2a35a..582fe691b7d 100644 --- a/stream-chat-android-ui-components/src/main/res/values/strings_attachment_gallery.xml +++ b/stream-chat-android-ui-components/src/main/res/values/strings_attachment_gallery.xml @@ -20,8 +20,12 @@ Reply Delete Show in chat - Save Image - Share Image + Save Media + Share Media + Could not share attachment + Unsupported attachment + Preparing… + Error. Video can\'t be displayed Photos diff --git a/stream-chat-android-ui-components/src/main/res/values/styles.xml b/stream-chat-android-ui-components/src/main/res/values/styles.xml index b98d79d3572..94718915924 100644 --- a/stream-chat-android-ui-components/src/main/res/values/styles.xml +++ b/stream-chat-android-ui-components/src/main/res/values/styles.xml @@ -254,6 +254,8 @@ @style/StreamUi.Thread.Avatar @style/StreamUi.Reply.Avatar @style/StreamUi.MessageOptions.Avatar + @style/StreamUi.MediaAttachmentGridView + @style/StreamUi.AttachmentGallery.VideoAttachments + + + +