Skip to content
Open
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
5 changes: 5 additions & 0 deletions app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3864,6 +3864,10 @@ class ChatActivity :
}

override fun onClickReaction(chatMessage: ChatMessage, emoji: String) {
if (!participantPermissions.hasReactPermission()) {
Snackbar.make(binding.root, R.string.nc_common_error_sorry, Snackbar.LENGTH_LONG).show()
return
}
VibrationUtils.vibrateShort(context)
if (chatMessage.reactionsSelf?.contains(emoji) == true) {
chatViewModel.deleteReaction(roomToken, chatMessage, emoji)
Expand Down Expand Up @@ -3917,6 +3921,7 @@ class ChatActivity :
currentConversation,
isShowMessageDeletionButton(message),
participantPermissions.hasChatPermission(),
participantPermissions.hasReactPermission(),
spreedCapabilities
).show()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ class MessageActionsDialog(
private val currentConversation: ConversationModel?,
private val showMessageDeletionButton: Boolean,
private val hasChatPermission: Boolean,
private val hasReactPermission: Boolean,
private val spreedCapabilities: SpreedCapability
) : BottomSheetDialog(chatActivity) {

Expand Down Expand Up @@ -138,7 +139,7 @@ class MessageActionsDialog(

viewThemeUtils.material.colorBottomSheetBackground(dialogMessageActionsBinding.root)
viewThemeUtils.material.colorBottomSheetDragHandle(dialogMessageActionsBinding.bottomSheetDragHandle)
initEmojiBar(hasChatPermission)
initEmojiBar(hasReactPermission)
initMenuItemCopy(!message.isDeleted)
initMenuItems(networkMonitor.isOnline.value)
}
Expand Down Expand Up @@ -264,9 +265,9 @@ class MessageActionsDialog(
}
}

private fun initEmojiBar(hasChatPermission: Boolean) {
private fun initEmojiBar(hasReactPermission: Boolean) {
if (hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.REACTIONS) &&
isPermitted(hasChatPermission) &&
isPermitted(hasReactPermission) &&
isReactableMessageType(message)
) {
val recentEmojiManager = RecentEmojiManager(context, MAX_RECENTS)
Expand Down Expand Up @@ -353,8 +354,8 @@ class MessageActionsDialog(
}
}

private fun isPermitted(hasChatPermission: Boolean): Boolean =
hasChatPermission &&
private fun isPermitted(hasPermission: Boolean): Boolean =
hasPermission &&
ConversationEnums.ConversationReadOnlyState.CONVERSATION_READ_ONLY !=
currentConversation?.conversationReadOnlyState

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ enum class SpreedFeatures(val value: String) {
SENSITIVE_CONVERSATIONS("sensitive-conversations"),
IMPORTANT_CONVERSATIONS("important-conversations"),
THREADS("threads"),
PINNED_MESSAGES("pinned-messages")
PINNED_MESSAGES("pinned-messages"),
REACT_PERMISSION("react-permission")
}

@Suppress("TooManyFunctions")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ class ParticipantPermissions(
private val canPublishVideo = (conversation.permissions and PUBLISH_VIDEO) == PUBLISH_VIDEO
val canPublishScreen = (conversation.permissions and PUBLISH_SCREEN) == PUBLISH_SCREEN
private val hasChatPermission = (conversation.permissions and CHAT) == CHAT
private val hasReactPermission = (conversation.permissions and REACT) == REACT

private fun hasConversationPermissions(): Boolean =
CapabilitiesUtil.hasSpreedFeatureCapability(
Expand Down Expand Up @@ -70,6 +71,20 @@ class ParticipantPermissions(
return true
}

fun hasReactPermission(): Boolean {
if (CapabilitiesUtil.hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.REACT_PERMISSION)) {
// Server supports separate react permission - check REACT bit
return hasReactPermission
}
if (CapabilitiesUtil.hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.CHAT_PERMISSION)) {
// Older server without react-permission capability - fall back to CHAT permission
// as that's what controlled reactions before the split
return hasChatPermission
}
// if capability is not available then the spreed version doesn't support to restrict this
return true
}

companion object {

val TAG = ParticipantPermissions::class.simpleName
Expand All @@ -82,5 +97,6 @@ class ParticipantPermissions(
const val PUBLISH_VIDEO = 32
const val PUBLISH_SCREEN = 64
const val CHAT = 128
const val REACT = 256
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,99 @@ class ParticipantPermissionsTest : TestCase() {
assertTrue(attendeePermissions.canPublishVideo())
}

@Test
fun test_reactPermissionWithReactCapability() {
val spreedCapability = SpreedCapability()
spreedCapability.features = listOf("react-permission")
val conversation = createConversation()

// With react-permission capability, only REACT bit matters
conversation.permissions = ParticipantPermissions.REACT or
ParticipantPermissions.CUSTOM

val user = User()
user.id = 1

val attendeePermissions =
ParticipantPermissions(
spreedCapability,
ConversationModel.mapToConversationModel(conversation, user)
)

assertTrue(attendeePermissions.hasReactPermission())
assertFalse(attendeePermissions.hasChatPermission())
}

@Test
fun test_reactPermissionDeniedWithReactCapability() {
val spreedCapability = SpreedCapability()
spreedCapability.features = listOf("react-permission")
val conversation = createConversation()

// With react-permission capability, only CHAT but no REACT - should NOT allow reactions
conversation.permissions = ParticipantPermissions.CHAT or
ParticipantPermissions.CUSTOM

val user = User()
user.id = 1

val attendeePermissions =
ParticipantPermissions(
spreedCapability,
ConversationModel.mapToConversationModel(conversation, user)
)

assertFalse(attendeePermissions.hasReactPermission())
assertTrue(attendeePermissions.hasChatPermission())
}

@Test
fun test_reactPermissionFallbackToChatOnOlderServer() {
val spreedCapability = SpreedCapability()
// Older server without react-permission capability but with chat-permission
spreedCapability.features = listOf("chat-permission")
val conversation = createConversation()

// Only CHAT permission set - should allow reactions as fallback for older servers
conversation.permissions = ParticipantPermissions.CHAT or
ParticipantPermissions.CUSTOM

val user = User()
user.id = 1

val attendeePermissions =
ParticipantPermissions(
spreedCapability,
ConversationModel.mapToConversationModel(conversation, user)
)

assertTrue(attendeePermissions.hasReactPermission())
assertTrue(attendeePermissions.hasChatPermission())
}

@Test
fun test_reactPermissionDeniedOnOlderServerWithoutChatPermission() {
val spreedCapability = SpreedCapability()
// Older server without react-permission capability but with chat-permission
spreedCapability.features = listOf("chat-permission")
val conversation = createConversation()

// No CHAT permission - should deny reactions on older servers
conversation.permissions = ParticipantPermissions.CUSTOM

val user = User()
user.id = 1

val attendeePermissions =
ParticipantPermissions(
spreedCapability,
ConversationModel.mapToConversationModel(conversation, user)
)

assertFalse(attendeePermissions.hasReactPermission())
assertFalse(attendeePermissions.hasChatPermission())
}

private fun createConversation() =
Conversation(
token = "test",
Expand Down
Loading