Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[AND-320] Swipe to reply (XML artifact) #5626

Merged
merged 10 commits into from
Feb 15, 2025
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
### ⬆️ Improved

### ✅ Added
- Added Swipe To Reply feature to the MessageListView. [#5626](https://github.com/GetStream/stream-chat-android/pull/5626)

### ⚠️ Changed

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package io.getstream.chat.android
import io.getstream.chat.android.models.Attachment
import io.getstream.chat.android.models.AttachmentType
import io.getstream.chat.android.models.Channel
import io.getstream.chat.android.models.ChannelCapabilities
import io.getstream.chat.android.models.ChannelConfig
import io.getstream.chat.android.models.ChannelInfo
import io.getstream.chat.android.models.ChannelMute
Expand Down Expand Up @@ -49,6 +50,7 @@ public fun positiveRandomInt(maxInt: Int = Int.MAX_VALUE - 1): Int =
public fun positiveRandomLong(maxLong: Long = Long.MAX_VALUE - 1): Long =
Random.nextLong(1, maxLong + 1)

public fun randomFloat(): Float = Random.nextFloat()
public fun randomInt(): Int = Random.nextInt()
public fun randomIntBetween(min: Int, max: Int): Int = Random.nextInt(min, max + 1)
public fun randomLong(): Long = Random.nextLong()
Expand Down Expand Up @@ -560,3 +562,48 @@ public fun randomReactionGroup(
firstReactionAt = firstReactionAt,
lastReactionAt = lastReactionAt,
)

public fun allChannelCapabilities(): Set<String> = setOf(
ChannelCapabilities.BAN_CHANNEL_MEMBERS,
ChannelCapabilities.CONNECT_EVENTS,
ChannelCapabilities.DELETE_ANY_MESSAGE,
ChannelCapabilities.DELETE_CHANNEL,
ChannelCapabilities.DELETE_OWN_MESSAGE,
ChannelCapabilities.FLAG_MESSAGE,
ChannelCapabilities.FREEZE_CHANNEL,
ChannelCapabilities.LEAVE_CHANNEL,
ChannelCapabilities.JOIN_CHANNEL,
ChannelCapabilities.MUTE_CHANNEL,
ChannelCapabilities.PIN_MESSAGE,
ChannelCapabilities.QUOTE_MESSAGE,
ChannelCapabilities.READ_EVENTS,
ChannelCapabilities.SEARCH_MESSAGES,
ChannelCapabilities.SEND_CUSTOM_EVENTS,
ChannelCapabilities.SEND_LINKS,
ChannelCapabilities.SEND_MESSAGE,
ChannelCapabilities.SEND_REACTION,
ChannelCapabilities.SEND_REPLY,
ChannelCapabilities.SET_CHANNEL_COOLDOWN,
ChannelCapabilities.UPDATE_ANY_MESSAGE,
ChannelCapabilities.UPDATE_CHANNEL,
ChannelCapabilities.UPDATE_CHANNEL_MEMBERS,
ChannelCapabilities.UPDATE_OWN_MESSAGE,
ChannelCapabilities.UPLOAD_FILE,
ChannelCapabilities.TYPING_EVENTS,
ChannelCapabilities.SLOW_MODE,
ChannelCapabilities.SKIP_SLOW_MODE,
ChannelCapabilities.JOIN_CALL,
ChannelCapabilities.CREATE_CALL,
ChannelCapabilities.CAST_POLL_VOTE,
ChannelCapabilities.SEND_POLL,
)

public fun randomChannelCapabilities(
exclude: Set<String> = emptySet(),
include: Set<String> = emptySet(),
): Set<String> =
allChannelCapabilities()
.minus(exclude)
.shuffled()
.let { it.take(positiveRandomInt(it.size)) }
.toSet() + include
Original file line number Diff line number Diff line change
Expand Up @@ -504,7 +504,7 @@ public class MessageComposerController(
setMessageMode(MessageMode.MessageThread(messageAction.message))
}
is Reply -> {
messageActions.value = messageActions.value + messageAction
messageActions.value = (messageActions.value.filterNot { it is Reply } + messageAction).toSet()
}
is Edit -> {
setMessageInputInternal(messageAction.message.text, MessageInput.Source.Edit)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2542,7 +2542,7 @@ public abstract interface class io/getstream/chat/android/ui/feature/messages/li

public final class io/getstream/chat/android/ui/feature/messages/list/MessageListViewStyle : io/getstream/chat/android/ui/helper/ViewStyle {
public static final field Companion Lio/getstream/chat/android/ui/feature/messages/list/MessageListViewStyle$Companion;
public fun <init> (Lio/getstream/chat/android/ui/feature/messages/list/ScrollButtonViewStyle;Lio/getstream/chat/android/ui/feature/messages/list/MessageListView$NewMessagesBehaviour;Lio/getstream/chat/android/ui/feature/messages/list/MessageListItemStyle;Lio/getstream/chat/android/ui/feature/messages/list/GiphyViewHolderStyle;Lio/getstream/chat/android/ui/feature/messages/list/MessageViewStyle;Lio/getstream/chat/android/ui/feature/messages/list/MessageReplyStyle;Lio/getstream/chat/android/ui/feature/messages/list/UnreadLabelButtonStyle;ZIIZIZIIIZIIZIIZIZIIZZZZZZLio/getstream/chat/android/ui/font/TextStyle;Lio/getstream/chat/android/ui/font/TextStyle;IILio/getstream/chat/android/ui/font/TextStyle;ILio/getstream/chat/android/ui/font/TextStyle;IIIIIIZIIIIIIIIIIIIZZ)V
public fun <init> (Lio/getstream/chat/android/ui/feature/messages/list/ScrollButtonViewStyle;Lio/getstream/chat/android/ui/feature/messages/list/MessageListView$NewMessagesBehaviour;Lio/getstream/chat/android/ui/feature/messages/list/MessageListItemStyle;Lio/getstream/chat/android/ui/feature/messages/list/GiphyViewHolderStyle;Lio/getstream/chat/android/ui/feature/messages/list/MessageViewStyle;Lio/getstream/chat/android/ui/feature/messages/list/MessageReplyStyle;Lio/getstream/chat/android/ui/feature/messages/list/UnreadLabelButtonStyle;ZIIZIZIIIZIIZIIZIZIIZZZZZZLio/getstream/chat/android/ui/font/TextStyle;Lio/getstream/chat/android/ui/font/TextStyle;IILio/getstream/chat/android/ui/font/TextStyle;ILio/getstream/chat/android/ui/font/TextStyle;IIIIIIZIIIIIIIIIIIIZZLandroid/graphics/drawable/Drawable;)V
public final fun component1 ()Lio/getstream/chat/android/ui/feature/messages/list/ScrollButtonViewStyle;
public final fun component10 ()I
public final fun component11 ()Z
Expand Down Expand Up @@ -2601,11 +2601,12 @@ public final class io/getstream/chat/android/ui/feature/messages/list/MessageLis
public final fun component6 ()Lio/getstream/chat/android/ui/feature/messages/list/MessageReplyStyle;
public final fun component60 ()Z
public final fun component61 ()Z
public final fun component62 ()Landroid/graphics/drawable/Drawable;
public final fun component7 ()Lio/getstream/chat/android/ui/feature/messages/list/UnreadLabelButtonStyle;
public final fun component8 ()Z
public final fun component9 ()I
public final fun copy (Lio/getstream/chat/android/ui/feature/messages/list/ScrollButtonViewStyle;Lio/getstream/chat/android/ui/feature/messages/list/MessageListView$NewMessagesBehaviour;Lio/getstream/chat/android/ui/feature/messages/list/MessageListItemStyle;Lio/getstream/chat/android/ui/feature/messages/list/GiphyViewHolderStyle;Lio/getstream/chat/android/ui/feature/messages/list/MessageViewStyle;Lio/getstream/chat/android/ui/feature/messages/list/MessageReplyStyle;Lio/getstream/chat/android/ui/feature/messages/list/UnreadLabelButtonStyle;ZIIZIZIIIZIIZIIZIZIIZZZZZZLio/getstream/chat/android/ui/font/TextStyle;Lio/getstream/chat/android/ui/font/TextStyle;IILio/getstream/chat/android/ui/font/TextStyle;ILio/getstream/chat/android/ui/font/TextStyle;IIIIIIZIIIIIIIIIIIIZZ)Lio/getstream/chat/android/ui/feature/messages/list/MessageListViewStyle;
public static synthetic fun copy$default (Lio/getstream/chat/android/ui/feature/messages/list/MessageListViewStyle;Lio/getstream/chat/android/ui/feature/messages/list/ScrollButtonViewStyle;Lio/getstream/chat/android/ui/feature/messages/list/MessageListView$NewMessagesBehaviour;Lio/getstream/chat/android/ui/feature/messages/list/MessageListItemStyle;Lio/getstream/chat/android/ui/feature/messages/list/GiphyViewHolderStyle;Lio/getstream/chat/android/ui/feature/messages/list/MessageViewStyle;Lio/getstream/chat/android/ui/feature/messages/list/MessageReplyStyle;Lio/getstream/chat/android/ui/feature/messages/list/UnreadLabelButtonStyle;ZIIZIZIIIZIIZIIZIZIIZZZZZZLio/getstream/chat/android/ui/font/TextStyle;Lio/getstream/chat/android/ui/font/TextStyle;IILio/getstream/chat/android/ui/font/TextStyle;ILio/getstream/chat/android/ui/font/TextStyle;IIIIIIZIIIIIIIIIIIIZZIILjava/lang/Object;)Lio/getstream/chat/android/ui/feature/messages/list/MessageListViewStyle;
public final fun copy (Lio/getstream/chat/android/ui/feature/messages/list/ScrollButtonViewStyle;Lio/getstream/chat/android/ui/feature/messages/list/MessageListView$NewMessagesBehaviour;Lio/getstream/chat/android/ui/feature/messages/list/MessageListItemStyle;Lio/getstream/chat/android/ui/feature/messages/list/GiphyViewHolderStyle;Lio/getstream/chat/android/ui/feature/messages/list/MessageViewStyle;Lio/getstream/chat/android/ui/feature/messages/list/MessageReplyStyle;Lio/getstream/chat/android/ui/feature/messages/list/UnreadLabelButtonStyle;ZIIZIZIIIZIIZIIZIZIIZZZZZZLio/getstream/chat/android/ui/font/TextStyle;Lio/getstream/chat/android/ui/font/TextStyle;IILio/getstream/chat/android/ui/font/TextStyle;ILio/getstream/chat/android/ui/font/TextStyle;IIIIIIZIIIIIIIIIIIIZZLandroid/graphics/drawable/Drawable;)Lio/getstream/chat/android/ui/feature/messages/list/MessageListViewStyle;
public static synthetic fun copy$default (Lio/getstream/chat/android/ui/feature/messages/list/MessageListViewStyle;Lio/getstream/chat/android/ui/feature/messages/list/ScrollButtonViewStyle;Lio/getstream/chat/android/ui/feature/messages/list/MessageListView$NewMessagesBehaviour;Lio/getstream/chat/android/ui/feature/messages/list/MessageListItemStyle;Lio/getstream/chat/android/ui/feature/messages/list/GiphyViewHolderStyle;Lio/getstream/chat/android/ui/feature/messages/list/MessageViewStyle;Lio/getstream/chat/android/ui/feature/messages/list/MessageReplyStyle;Lio/getstream/chat/android/ui/feature/messages/list/UnreadLabelButtonStyle;ZIIZIZIIIZIIZIIZIZIIZZZZZZLio/getstream/chat/android/ui/font/TextStyle;Lio/getstream/chat/android/ui/font/TextStyle;IILio/getstream/chat/android/ui/font/TextStyle;ILio/getstream/chat/android/ui/font/TextStyle;IIIIIIZIIIIIIIIIIIIZZLandroid/graphics/drawable/Drawable;IILjava/lang/Object;)Lio/getstream/chat/android/ui/feature/messages/list/MessageListViewStyle;
public fun equals (Ljava/lang/Object;)Z
public final fun getAudioRecordPlayerViewStyle ()Lio/getstream/chat/android/ui/feature/messages/list/MessageViewStyle;
public final fun getBackgroundColor ()I
Expand Down Expand Up @@ -2659,6 +2660,7 @@ public final class io/getstream/chat/android/ui/feature/messages/list/MessageLis
public final fun getScrollButtonEndMargin ()I
public final fun getScrollButtonViewStyle ()Lio/getstream/chat/android/ui/feature/messages/list/ScrollButtonViewStyle;
public final fun getShowReactionsForUnsentMessages ()Z
public final fun getSwipeToReplyIcon ()Landroid/graphics/drawable/Drawable;
public final fun getThreadMessagesStart ()I
public final fun getThreadReplyIcon ()I
public final fun getThreadsEnabled ()Z
Expand Down Expand Up @@ -2795,7 +2797,6 @@ public abstract class io/getstream/chat/android/ui/feature/messages/list/adapter
public fun <init> (Landroid/view/View;)V
public abstract fun bindData (Lio/getstream/chat/android/ui/feature/messages/list/adapter/MessageListItem;Lio/getstream/chat/android/ui/feature/messages/list/adapter/MessageListItemPayloadDiff;)V
protected final fun getContext ()Landroid/content/Context;
protected final fun getData ()Lio/getstream/chat/android/ui/feature/messages/list/adapter/MessageListItem;
public fun messageContainerView ()Landroid/view/View;
public fun onAttachedToWindow ()V
public fun onDetachedFromWindow ()V
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ public data class MessageListItemStyle(
internal val MESSAGE_STROKE_COLOR_MINE = R.color.stream_ui_literal_transparent
internal const val MESSAGE_STROKE_WIDTH_MINE: Float = 0f
internal val MESSAGE_STROKE_COLOR_THEIRS = R.color.stream_ui_grey_whisper
internal val MESSAGE_STROKE_WIDTH_THEIRS: Float = 1.dpToPxPrecise()
internal val MESSAGE_STROKE_WIDTH_THEIRS: Float by lazy { 1.dpToPxPrecise() }

private const val BASE_MESSAGE_MAX_WIDTH_FACTOR = 1
private const val DEFAULT_MESSAGE_MAX_WIDTH_FACTOR = 0.75f
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import android.widget.Toast
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.isVisible
import androidx.fragment.app.FragmentManager
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.ItemAnimator
Expand Down Expand Up @@ -108,6 +109,8 @@ import io.getstream.chat.android.ui.feature.messages.list.background.MessageBack
import io.getstream.chat.android.ui.feature.messages.list.background.MessageBackgroundFactoryImpl
import io.getstream.chat.android.ui.feature.messages.list.internal.HiddenMessageListItemPredicate
import io.getstream.chat.android.ui.feature.messages.list.internal.MessageListScrollHelper
import io.getstream.chat.android.ui.feature.messages.list.internal.SwipeReplyCallback
import io.getstream.chat.android.ui.feature.messages.list.internal.canReplyToMessage
import io.getstream.chat.android.ui.feature.messages.list.internal.poll.AllPollOptionsDialogFragment
import io.getstream.chat.android.ui.feature.messages.list.internal.poll.PollResultsDialogFragment
import io.getstream.chat.android.ui.feature.messages.list.options.message.MessageOptionItem
Expand Down Expand Up @@ -666,6 +669,7 @@ public class MessageListView : ConstraintLayout {

initRecyclerView()
initScrollHelper()
initSwipeToReply()
initLoadingView()
initEmptyStateView()
messageListViewStyle?.unreadLabelButtonStyle?.let { initUnreadLabelButton(it) }
Expand Down Expand Up @@ -727,6 +731,27 @@ public class MessageListView : ConstraintLayout {
}
}

private fun initSwipeToReply() {
messageListViewStyle?.swipeToReplyIcon?.let { swipeToReplyIcon ->
SwipeReplyCallback(swipeToReplyIcon) { message ->
message
?.let { messageListViewStyle?.canReplyToMessage(it, ownCapabilities) }
?: false
}.let { swipeReplyCallback ->
ItemTouchHelper(swipeReplyCallback).let { itemTouchHelper ->
swipeReplyCallback.onReply = {
// We need to detach and attach the itemTouchHelper to the RecyclerView to make it work after
// the reply action is completed.
itemTouchHelper.attachToRecyclerView(null)
itemTouchHelper.attachToRecyclerView(binding.chatMessagesRV)
messageReplyHandler.onMessageReply(it.cid, it)
}
itemTouchHelper.attachToRecyclerView(binding.chatMessagesRV)
}
}
}
}

private fun configureAttributes(attributeSet: AttributeSet?) {
context.obtainStyledAttributes(
attributeSet,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package io.getstream.chat.android.ui.feature.messages.list
import android.content.Context
import android.content.res.TypedArray
import android.graphics.Typeface
import android.graphics.drawable.Drawable
import android.util.AttributeSet
import android.view.Gravity
import androidx.annotation.ColorInt
Expand Down Expand Up @@ -163,28 +164,29 @@ public data class MessageListViewStyle(
public val optionsOverlayMessageOptionsMarginEnd: Int,
public val showReactionsForUnsentMessages: Boolean,
public val readCountEnabled: Boolean,
public val swipeToReplyIcon: Drawable?,
) : ViewStyle {
public companion object {
private val DEFAULT_BACKGROUND_COLOR = R.color.stream_ui_white_snow
private val DEFAULT_SCROLL_BUTTON_ELEVATION = 3.dpToPx().toFloat()
private val DEFAULT_SCROLL_BUTTON_MARGIN = 6.dpToPx()
private val DEFAULT_SCROLL_BUTTON_INTERNAL_MARGIN = 2.dpToPx()
private val DEFAULT_SCROLL_BUTTON_BADGE_ELEVATION = DEFAULT_SCROLL_BUTTON_ELEVATION

private val DEFAULT_EDIT_REACTIONS_MARGIN_TOP = 0.dpToPx()
private val DEFAULT_EDIT_REACTIONS_MARGIN_BOTTOM = 0.dpToPx()
private val DEFAULT_EDIT_REACTIONS_MARGIN_START = 50.dpToPx()
private val DEFAULT_EDIT_REACTIONS_MARGIN_END = 8.dpToPx()

private val DEFAULT_USER_REACTIONS_MARGIN_TOP = 8.dpToPx()
private val DEFAULT_USER_REACTIONS_MARGIN_BOTTOM = 0.dpToPx()
private val DEFAULT_USER_REACTIONS_MARGIN_START = 8.dpToPx()
private val DEFAULT_USER_REACTIONS_MARGIN_END = 8.dpToPx()

private val DEFAULT_MESSAGE_OPTIONS_MARGIN_TOP = 24.dpToPx()
private val DEFAULT_MESSAGE_OPTIONS_MARGIN_BOTTOM = 0.dpToPx()
private val DEFAULT_MESSAGE_OPTIONS_MARGIN_START = 50.dpToPx()
private val DEFAULT_MESSAGE_OPTIONS_MARGIN_END = 8.dpToPx()
private val DEFAULT_SCROLL_BUTTON_ELEVATION by lazy { 3.dpToPx().toFloat() }
private val DEFAULT_SCROLL_BUTTON_MARGIN by lazy { 6.dpToPx() }
private val DEFAULT_SCROLL_BUTTON_INTERNAL_MARGIN by lazy { 2.dpToPx() }
private val DEFAULT_SCROLL_BUTTON_BADGE_ELEVATION by lazy { DEFAULT_SCROLL_BUTTON_ELEVATION }

private val DEFAULT_EDIT_REACTIONS_MARGIN_TOP by lazy { 0.dpToPx() }
private val DEFAULT_EDIT_REACTIONS_MARGIN_BOTTOM by lazy { 0.dpToPx() }
private val DEFAULT_EDIT_REACTIONS_MARGIN_START by lazy { 50.dpToPx() }
private val DEFAULT_EDIT_REACTIONS_MARGIN_END by lazy { 8.dpToPx() }

private val DEFAULT_USER_REACTIONS_MARGIN_TOP by lazy { 8.dpToPx() }
private val DEFAULT_USER_REACTIONS_MARGIN_BOTTOM by lazy { 0.dpToPx() }
private val DEFAULT_USER_REACTIONS_MARGIN_START by lazy { 8.dpToPx() }
private val DEFAULT_USER_REACTIONS_MARGIN_END by lazy { 8.dpToPx() }

private val DEFAULT_MESSAGE_OPTIONS_MARGIN_TOP by lazy { 24.dpToPx() }
private val DEFAULT_MESSAGE_OPTIONS_MARGIN_BOTTOM by lazy { 0.dpToPx() }
private val DEFAULT_MESSAGE_OPTIONS_MARGIN_START by lazy { 50.dpToPx() }
private val DEFAULT_MESSAGE_OPTIONS_MARGIN_END by lazy { 8.dpToPx() }

/**
* Creates an [MessageListViewStyle] instance with the default values.
Expand Down Expand Up @@ -339,6 +341,13 @@ public data class MessageListViewStyle(
R.drawable.stream_ui_ic_arrow_curve_left_grey,
)

val replyToSwipeIcon: Drawable? = context.getDrawableCompat(
attributes.getResourceId(
R.styleable.MessageListView_streamUiSwipeToReplyIcon,
R.drawable.stream_ui_ic_arrow_curve_left_grey,
),
)

val replyEnabled = attributes.getBoolean(R.styleable.MessageListView_streamUiReplyEnabled, true)

val threadReplyIcon = attributes.getResourceId(
Expand Down Expand Up @@ -673,6 +682,7 @@ public data class MessageListViewStyle(
optionsOverlayMessageOptionsMarginEnd = optionsOverlayMessageOptionsMarginEnd,
showReactionsForUnsentMessages = showReactionsForUnsentMessages,
readCountEnabled = readCountEnabled,
swipeToReplyIcon = replyToSwipeIcon,
).let(TransformStyle.messageListStyleTransformer::transform)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ public data class MessageReplyStyle(
).let(TransformStyle.messageReplyStyleTransformer::transform)
}

private val MESSAGE_STROKE_WIDTH_THEIRS: Float = 1.dpToPxPrecise()
private val MESSAGE_STROKE_WIDTH_THEIRS: Float by lazy { 1.dpToPxPrecise() }
private const val VALUE_NOT_SET = Integer.MAX_VALUE
internal val DEFAULT_TEXT_COLOR = R.color.stream_ui_text_color_primary
internal val DEFAULT_TEXT_SIZE = R.dimen.stream_ui_text_medium
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public abstract class BaseMessageItemViewHolder<T : MessageListItem>(
* Can be used for listeners that need to pass along the currently
* bound data as a parameter.
*/
protected lateinit var data: T
internal lateinit var data: T
private set

private var highlightAnimation: ValueAnimator? = null
Expand Down
Loading
Loading