Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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 @@ -47,6 +47,7 @@

## stream-chat-android-ui-common
### 🐞 Fixed
- Fix `Thread Reply` option visible for messages belonging to a thread. [#6021](https://github.com/GetStream/stream-chat-android/pull/6021)

### ⬆️ Improved

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,11 @@ internal class MessageOptionItemVisibilityTest {
ownCapabilities: Set<String>,
expectedResult: Boolean,
) {
messageOptionItemVisibility.canThreadReplyToMessage(isInThread, message, ownCapabilities) `should be` expectedResult
messageOptionItemVisibility.canThreadReplyToMessage(
isInThread,
message,
ownCapabilities,
) `should be` expectedResult
}

@ParameterizedTest
Expand Down Expand Up @@ -408,40 +412,62 @@ internal class MessageOptionItemVisibilityTest {
),
)

@Suppress("LongMethod")
@JvmStatic
fun canThreadReplyToMessageArguments() = listOf(
// case: threads disabled
Arguments.of(
MessageOptionItemVisibility(isThreadReplyVisible = false),
randomBoolean(),
randomMessage(),
randomMessage(parentId = null, threadParticipants = emptyList()),
randomChannelCapabilities(),
false,
),
// case: message not synced
Arguments.of(
MessageOptionItemVisibility(),
randomBoolean(),
randomMessage(syncStatus = randomSyncStatus(exclude = listOf(SyncStatus.COMPLETED))),
randomMessage(
parentId = null,
threadParticipants = emptyList(),
syncStatus = randomSyncStatus(exclude = listOf(SyncStatus.COMPLETED)),
),
randomChannelCapabilities(),
false,
),
// case: no SEND_REPLY capability
Arguments.of(
MessageOptionItemVisibility(),
randomBoolean(),
randomMessage(),
randomMessage(parentId = null, threadParticipants = emptyList()),
randomChannelCapabilities(exclude = setOf(ChannelCapabilities.SEND_REPLY)),
false,
),
// case: already in thread
Arguments.of(
MessageOptionItemVisibility(isThreadReplyVisible = true),
true,
randomMessage(syncStatus = SyncStatus.COMPLETED),
randomMessage(parentId = null, threadParticipants = emptyList(), syncStatus = SyncStatus.COMPLETED),
randomChannelCapabilities(include = setOf(ChannelCapabilities.SEND_REPLY)),
false,
),
// case: message is a thread reply
Arguments.of(
MessageOptionItemVisibility(isThreadReplyVisible = true),
false,
randomMessage(syncStatus = SyncStatus.COMPLETED),
randomMessage(
parentId = "parentId",
threadParticipants = emptyList(),
syncStatus = SyncStatus.COMPLETED,
),
randomChannelCapabilities(include = setOf(ChannelCapabilities.SEND_REPLY)),
false,
),
// case: all conditions met
Arguments.of(
MessageOptionItemVisibility(isThreadReplyVisible = true),
false,
randomMessage(parentId = null, threadParticipants = emptyList(), syncStatus = SyncStatus.COMPLETED),
randomChannelCapabilities(include = setOf(ChannelCapabilities.SEND_REPLY)),
true,
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ package io.getstream.chat.android.ui.common.utils

import io.getstream.chat.android.client.utils.attachment.isGiphy
import io.getstream.chat.android.client.utils.message.hasSharedLocation
import io.getstream.chat.android.client.utils.message.isThreadReply
import io.getstream.chat.android.models.AttachmentType
import io.getstream.chat.android.models.ChannelCapabilities
import io.getstream.chat.android.models.Message
Expand All @@ -41,25 +42,84 @@ private fun Set<String>.canFlagMessage(): Boolean = contains(ChannelCapabilities
private fun Set<String>.canPinMessage(): Boolean = contains(ChannelCapabilities.PIN_MESSAGE)
private fun Set<String>.canMarkAsUnread(): Boolean = contains(ChannelCapabilities.READ_EVENTS)

/**
* Determines whether a reply (quote) can be made to the given message.
*
* A reply is allowed when:
* - Reply functionality is enabled in the UI configuration
* - The message has been successfully synced with the backend
* - The user has the capability to quote messages in the channel
*
* @param replyEnabled Whether the reply feature is enabled in the UI.
* @param message The message to check for reply eligibility.
* @param ownCapabilities The set of capabilities the current user has in the channel.
* @return `true` if a reply can be made to the message, `false` otherwise.
*/
public fun canReplyToMessage(
replyEnabled: Boolean,
message: Message,
ownCapabilities: Set<String>,
): Boolean = replyEnabled && message.isSynced() && ownCapabilities.contains(ChannelCapabilities.QUOTE_MESSAGE)

/**
* Determines whether a thread reply can be made to the given message.
*
* A thread reply is allowed when:
* - Thread functionality is enabled in the UI configuration
* - The user is not currently viewing a thread
* - The message is not a thread reply
* - The message has been successfully synced with the backend
* - The user has the capability to send replies in the channel
*
* @param threadsEnabled Whether the thread feature is enabled in the UI.
* @param isInThread Whether the user is currently viewing messages within a thread.
* @param message The message to check for thread reply eligibility.
* @param ownCapabilities The set of capabilities the current user has in the channel.
* @return `true` if a thread reply can be made to the message, `false` otherwise.
*/
public fun canThreadReplyToMessage(
threadsEnabled: Boolean,
isInThread: Boolean,
message: Message,
ownCapabilities: Set<String>,
): Boolean =
threadsEnabled && !isInThread && message.isSynced() && ownCapabilities.contains(ChannelCapabilities.SEND_REPLY)
threadsEnabled &&
!isInThread &&
!message.isThreadReply() &&
message.isSynced() &&
ownCapabilities.contains(ChannelCapabilities.SEND_REPLY)

/**
* Determines whether the given message can be copied to the clipboard.
*
* A message can be copied when:
* - Copy text functionality is enabled in the UI configuration
* - The message contains text only (no attachments) OR contains links
*
* @param copyTextEnabled Whether the copy text feature is enabled in the UI.
* @param message The message to check for copy eligibility.
* @return `true` if the message can be copied, `false` otherwise.
*/
public fun canCopyMessage(
copyTextEnabled: Boolean,
message: Message,
): Boolean = copyTextEnabled && (message.isTextOnly() || message.hasLinks())

/**
* Determines whether the given message can be edited.
*
* A message can be edited when:
* - Edit message functionality is enabled in the UI configuration
* - The user has permission to edit their own messages and it's their message, OR they can edit any message
* - The message is not a Giphy command
* - The message does not contain a shared location attachment
*
* @param editMessageEnabled Whether the edit message feature is enabled in the UI.
* @param currentUser The currently authenticated user.
* @param message The message to check for edit eligibility.
* @param ownCapabilities The set of capabilities the current user has in the channel.
* @return `true` if the message can be edited, `false` otherwise.
*/
public fun canEditMessage(
editMessageEnabled: Boolean,
currentUser: User?,
Expand All @@ -69,6 +129,19 @@ public fun canEditMessage(
with(ownCapabilities) { ((message.isOwnMessage(currentUser) && canEditOwnMessage()) || canEditAnyMessage()) } &&
!message.isGiphyCommand() && !message.hasSharedLocation()

/**
* Determines whether the given message can be deleted.
*
* A message can be deleted when:
* - Delete message functionality is enabled in the UI configuration
* - The user has permission to delete their own messages and it's their message, OR they can delete any message
*
* @param deleteMessageEnabled Whether the delete message feature is enabled in the UI.
* @param currentUser The currently authenticated user.
* @param message The message to check for delete eligibility.
* @param ownCapabilities The set of capabilities the current user has in the channel.
* @return `true` if the message can be deleted, `false` otherwise.
*/
public fun canDeleteMessage(
deleteMessageEnabled: Boolean,
currentUser: User?,
Expand All @@ -77,30 +150,93 @@ public fun canDeleteMessage(
): Boolean = deleteMessageEnabled &&
with(ownCapabilities) { ((message.isOwnMessage(currentUser) && canDeleteOwnMessage()) || canDeleteAnyMessage()) }

/**
* Determines whether the given message can be flagged.
*
* A message can be flagged when:
* - Flag functionality is enabled in the UI configuration
* - The user has the capability to flag messages in the channel
* - The message was not sent by the current user (users cannot flag their own messages)
*
* @param flagEnabled Whether the flag message feature is enabled in the UI.
* @param currentUser The currently authenticated user.
* @param message The message to check for flag eligibility.
* @param ownCapabilities The set of capabilities the current user has in the channel.
* @return `true` if the message can be flagged, `false` otherwise.
*/
public fun canFlagMessage(
flagEnabled: Boolean,
currentUser: User?,
message: Message,
ownCapabilities: Set<String>,
): Boolean = flagEnabled && ownCapabilities.canFlagMessage() && !message.isOwnMessage(currentUser)

/**
* Determines whether the given message can be pinned or unpinned.
*
* A message can be pinned when:
* - Pin message functionality is enabled in the UI configuration
* - The message has been successfully synced with the backend
* - The user has the capability to pin messages in the channel
*
* @param pinMessageEnabled Whether the pin message feature is enabled in the UI.
* @param message The message to check for pin eligibility.
* @param ownCapabilities The set of capabilities the current user has in the channel.
* @return `true` if the message can be pinned or unpinned, `false` otherwise.
*/
public fun canPinMessage(
pinMessageEnabled: Boolean,
message: Message,
ownCapabilities: Set<String>,
): Boolean = pinMessageEnabled && message.isSynced() && ownCapabilities.canPinMessage()

/**
* Determines whether the user who sent the given message can be blocked.
*
* A user can be blocked when:
* - Block user functionality is enabled in the UI configuration
* - The message was not sent by the current user (users cannot block themselves)
*
* @param blockUserEnabled Whether the block user feature is enabled in the UI.
* @param currentUser The currently authenticated user.
* @param message The message whose sender to check for block eligibility.
* @return `true` if the message sender can be blocked, `false` otherwise.
*/
public fun canBlockUser(
blockUserEnabled: Boolean,
currentUser: User?,
message: Message,
): Boolean = blockUserEnabled && !message.isOwnMessage(currentUser)

/**
* Determines whether messages in the channel can be marked as unread.
*
* Messages can be marked as unread when:
* - Mark as unread functionality is enabled in the UI configuration
* - The user has the capability to receive read events in the channel
*
* @param markAsUnreadEnabled Whether the mark as unread feature is enabled in the UI.
* @param ownCapabilities The set of capabilities the current user has in the channel.
* @return `true` if messages can be marked as unread, `false` otherwise.
*/
public fun canMarkAsUnread(
markAsUnreadEnabled: Boolean,
ownCapabilities: Set<String>,
): Boolean = markAsUnreadEnabled && ownCapabilities.canMarkAsUnread()

/**
* Determines whether the given message can be retried.
*
* A message can be retried when:
* - Retry message functionality is enabled in the UI configuration
* - The message was sent by the current user
* - The message has failed permanently
*
* @param retryMessageEnabled Whether the retry message feature is enabled in the UI.
* @param currentUser The currently authenticated user.
* @param message The message to check for retry eligibility.
* @return `true` if the message can be retried, `false` otherwise.
*/
public fun canRetryMessage(
retryMessageEnabled: Boolean,
currentUser: User?,
Expand Down
Loading
Loading