Skip to content

Commit 8d93e57

Browse files
authored
Merge pull request #5754 from luflow/feature/split-chat-and-reaction-permission
feat(permissions): Add support for separate REACT permission
2 parents 234d045 + 51567c6 commit 8d93e57

7 files changed

Lines changed: 124 additions & 10 deletions

File tree

app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3977,6 +3977,10 @@ class ChatActivity :
39773977
}
39783978

39793979
override fun onClickReaction(chatMessage: ChatMessage, emoji: String) {
3980+
if (!participantPermissions.hasReactPermission()) {
3981+
Snackbar.make(binding.root, R.string.reaction_forbidden, Snackbar.LENGTH_LONG).show()
3982+
return
3983+
}
39803984
VibrationUtils.vibrateShort(context)
39813985
if (chatMessage.reactionsSelf?.contains(emoji) == true) {
39823986
chatViewModel.deleteReaction(roomToken, chatMessage, emoji)
@@ -3995,7 +3999,7 @@ class ChatActivity :
39953999
roomToken,
39964000
chatMessage,
39974001
conversationUser,
3998-
participantPermissions.hasChatPermission(),
4002+
participantPermissions.hasReactPermission(),
39994003
ncApi
40004004
).show()
40014005
}
@@ -4030,6 +4034,7 @@ class ChatActivity :
40304034
currentConversation,
40314035
isShowMessageDeletionButton(message),
40324036
participantPermissions.hasChatPermission(),
4037+
participantPermissions.hasReactPermission(),
40334038
spreedCapabilities
40344039
).show()
40354040
}

app/src/main/java/com/nextcloud/talk/ui/dialog/MessageActionsDialog.kt

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ class MessageActionsDialog(
6969
private val currentConversation: ConversationModel?,
7070
private val showMessageDeletionButton: Boolean,
7171
private val hasChatPermission: Boolean,
72+
private val hasReactPermission: Boolean,
7273
private val spreedCapabilities: SpreedCapability
7374
) : BottomSheetDialog(chatActivity) {
7475

@@ -138,7 +139,7 @@ class MessageActionsDialog(
138139

139140
viewThemeUtils.material.colorBottomSheetBackground(dialogMessageActionsBinding.root)
140141
viewThemeUtils.material.colorBottomSheetDragHandle(dialogMessageActionsBinding.bottomSheetDragHandle)
141-
initEmojiBar(hasChatPermission)
142+
initEmojiBar(hasReactPermission)
142143
initMenuItemCopy(!message.isDeleted)
143144
initMenuItems(networkMonitor.isOnline.value)
144145
}
@@ -264,9 +265,10 @@ class MessageActionsDialog(
264265
}
265266
}
266267

267-
private fun initEmojiBar(hasChatPermission: Boolean) {
268+
private fun initEmojiBar(hasReactPermission: Boolean) {
268269
if (hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.REACTIONS) &&
269-
isPermitted(hasChatPermission) &&
270+
hasReactPermission &&
271+
isConversationWritable() &&
270272
isReactableMessageType(message)
271273
) {
272274
val recentEmojiManager = RecentEmojiManager(context, MAX_RECENTS)
@@ -353,9 +355,8 @@ class MessageActionsDialog(
353355
}
354356
}
355357

356-
private fun isPermitted(hasChatPermission: Boolean): Boolean =
357-
hasChatPermission &&
358-
ConversationEnums.ConversationReadOnlyState.CONVERSATION_READ_ONLY !=
358+
private fun isConversationWritable(): Boolean =
359+
ConversationEnums.ConversationReadOnlyState.CONVERSATION_READ_ONLY !=
359360
currentConversation?.conversationReadOnlyState
360361

361362
private fun isReactableMessageType(message: ChatMessage): Boolean =

app/src/main/java/com/nextcloud/talk/ui/dialog/ShowReactionsDialog.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ class ShowReactionsDialog(
4848
private val roomToken: String,
4949
private val chatMessage: ChatMessage,
5050
private val user: User?,
51-
private val hasChatPermission: Boolean,
51+
private val hasReactPermission: Boolean,
5252
private val ncApi: NcApi
5353
) : BottomSheetDialog(activity),
5454
ReactionItemClickListener {
@@ -186,7 +186,7 @@ class ShowReactionsDialog(
186186
}
187187

188188
override fun onClick(reactionItem: ReactionItem) {
189-
if (hasChatPermission && reactionItem.reactionVoter.actorId?.equals(user?.userId) == true) {
189+
if (hasReactPermission && reactionItem.reactionVoter.actorId?.equals(user?.userId) == true) {
190190
deleteReaction(chatMessage, reactionItem.reaction!!)
191191
adapter?.list?.remove(reactionItem)
192192
dismiss()

app/src/main/java/com/nextcloud/talk/utils/CapabilitiesUtil.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,8 @@ enum class SpreedFeatures(val value: String) {
6363
IMPORTANT_CONVERSATIONS("important-conversations"),
6464
THREADS("threads"),
6565
PINNED_MESSAGES("pinned-messages"),
66-
SCHEDULED_MESSAGES("scheduled-messages")
66+
SCHEDULED_MESSAGES("scheduled-messages"),
67+
REACT_PERMISSION("react-permission")
6768
}
6869

6970
@Suppress("TooManyFunctions")

app/src/main/java/com/nextcloud/talk/utils/ParticipantPermissions.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ class ParticipantPermissions(
2626
private val canPublishVideo = (conversation.permissions and PUBLISH_VIDEO) == PUBLISH_VIDEO
2727
val canPublishScreen = (conversation.permissions and PUBLISH_SCREEN) == PUBLISH_SCREEN
2828
private val hasChatPermission = (conversation.permissions and CHAT) == CHAT
29+
private val hasReactPermission = (conversation.permissions and REACT) == REACT
2930

3031
private fun hasConversationPermissions(): Boolean =
3132
CapabilitiesUtil.hasSpreedFeatureCapability(
@@ -70,6 +71,15 @@ class ParticipantPermissions(
7071
return true
7172
}
7273

74+
fun hasReactPermission(): Boolean {
75+
if (CapabilitiesUtil.hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.REACT_PERMISSION)) {
76+
// Server supports separate react permission - check REACT bit
77+
return hasReactPermission
78+
}
79+
// Older server without react-permission capability - fall back to chat permission
80+
return hasChatPermission()
81+
}
82+
7383
companion object {
7484

7585
val TAG = ParticipantPermissions::class.simpleName
@@ -82,5 +92,6 @@ class ParticipantPermissions(
8292
const val PUBLISH_VIDEO = 32
8393
const val PUBLISH_SCREEN = 64
8494
const val CHAT = 128
95+
const val REACT = 256
8596
}
8697
}

app/src/main/res/values/strings.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -550,6 +550,7 @@ How to translate with transifex:
550550
<string name="read_storage_no_permission">Sharing files from storage is not possible without permissions</string>
551551
<string name="open_in_files_app">Open in Files app</string>
552552
<string name="send_to_forbidden">You are not allowed to share content to this chat</string>
553+
<string name="reaction_forbidden">You are not allowed to add or remove reactions in this conversation</string>
553554

554555
<string name="typing_is_typing">is typing …</string>
555556
<string name="typing_are_typing">are typing …</string>

app/src/test/java/com/nextcloud/talk/utils/ParticipantPermissionsTest.kt

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,101 @@ class ParticipantPermissionsTest : TestCase() {
4747
assertTrue(attendeePermissions.canPublishVideo())
4848
}
4949

50+
@Test
51+
fun test_reactPermissionWithReactCapability() {
52+
val spreedCapability = SpreedCapability()
53+
// Server with react-permission also supports chat-permission
54+
spreedCapability.features = listOf("chat-permission", "react-permission")
55+
val conversation = createConversation()
56+
57+
// With react-permission capability, only REACT bit matters
58+
conversation.permissions = ParticipantPermissions.REACT or
59+
ParticipantPermissions.CUSTOM
60+
61+
val user = User()
62+
user.id = 1
63+
64+
val attendeePermissions =
65+
ParticipantPermissions(
66+
spreedCapability,
67+
ConversationModel.mapToConversationModel(conversation, user)
68+
)
69+
70+
assertTrue(attendeePermissions.hasReactPermission())
71+
assertFalse(attendeePermissions.hasChatPermission())
72+
}
73+
74+
@Test
75+
fun test_reactPermissionDeniedWithReactCapability() {
76+
val spreedCapability = SpreedCapability()
77+
// Server with react-permission also supports chat-permission
78+
spreedCapability.features = listOf("chat-permission", "react-permission")
79+
val conversation = createConversation()
80+
81+
// With react-permission capability, only CHAT but no REACT - should NOT allow reactions
82+
conversation.permissions = ParticipantPermissions.CHAT or
83+
ParticipantPermissions.CUSTOM
84+
85+
val user = User()
86+
user.id = 1
87+
88+
val attendeePermissions =
89+
ParticipantPermissions(
90+
spreedCapability,
91+
ConversationModel.mapToConversationModel(conversation, user)
92+
)
93+
94+
assertFalse(attendeePermissions.hasReactPermission())
95+
assertTrue(attendeePermissions.hasChatPermission())
96+
}
97+
98+
@Test
99+
fun test_reactPermissionFallbackToChatOnOlderServer() {
100+
val spreedCapability = SpreedCapability()
101+
// Older server without react-permission capability but with chat-permission
102+
spreedCapability.features = listOf("chat-permission")
103+
val conversation = createConversation()
104+
105+
// Only CHAT permission set - should allow reactions as fallback for older servers
106+
conversation.permissions = ParticipantPermissions.CHAT or
107+
ParticipantPermissions.CUSTOM
108+
109+
val user = User()
110+
user.id = 1
111+
112+
val attendeePermissions =
113+
ParticipantPermissions(
114+
spreedCapability,
115+
ConversationModel.mapToConversationModel(conversation, user)
116+
)
117+
118+
assertTrue(attendeePermissions.hasReactPermission())
119+
assertTrue(attendeePermissions.hasChatPermission())
120+
}
121+
122+
@Test
123+
fun test_reactPermissionDeniedOnOlderServerWithoutChatPermission() {
124+
val spreedCapability = SpreedCapability()
125+
// Older server without react-permission capability but with chat-permission
126+
spreedCapability.features = listOf("chat-permission")
127+
val conversation = createConversation()
128+
129+
// No CHAT permission - should deny reactions on older servers
130+
conversation.permissions = ParticipantPermissions.CUSTOM
131+
132+
val user = User()
133+
user.id = 1
134+
135+
val attendeePermissions =
136+
ParticipantPermissions(
137+
spreedCapability,
138+
ConversationModel.mapToConversationModel(conversation, user)
139+
)
140+
141+
assertFalse(attendeePermissions.hasReactPermission())
142+
assertFalse(attendeePermissions.hasChatPermission())
143+
}
144+
50145
private fun createConversation() =
51146
Conversation(
52147
token = "test",

0 commit comments

Comments
 (0)