Skip to content

Commit d02e107

Browse files
authored
Merge pull request #3255 from element-hq/feature/fga/pinned_event_feature_flag
WIP Pinned events : add feature flag and pin/unpin actions
2 parents 1cbd03a + 6743e3b commit d02e107

File tree

33 files changed

+746
-333
lines changed

33 files changed

+746
-333
lines changed

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt

Lines changed: 47 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,12 @@ import android.os.Build
2020
import androidx.compose.runtime.Composable
2121
import androidx.compose.runtime.LaunchedEffect
2222
import androidx.compose.runtime.MutableState
23+
import androidx.compose.runtime.State
2324
import androidx.compose.runtime.collectAsState
2425
import androidx.compose.runtime.derivedStateOf
2526
import androidx.compose.runtime.getValue
2627
import androidx.compose.runtime.mutableStateOf
28+
import androidx.compose.runtime.produceState
2729
import androidx.compose.runtime.remember
2830
import androidx.compose.runtime.rememberCoroutineScope
2931
import androidx.compose.runtime.saveable.rememberSaveable
@@ -73,12 +75,13 @@ import io.element.android.libraries.matrix.api.room.MatrixRoomInfo
7375
import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState
7476
import io.element.android.libraries.matrix.api.room.MessageEventType
7577
import io.element.android.libraries.matrix.api.room.isDm
78+
import io.element.android.libraries.matrix.api.room.powerlevels.canPinUnpin
79+
import io.element.android.libraries.matrix.api.room.powerlevels.canRedactOther
80+
import io.element.android.libraries.matrix.api.room.powerlevels.canRedactOwn
81+
import io.element.android.libraries.matrix.api.room.powerlevels.canSendMessage
7682
import io.element.android.libraries.matrix.ui.messages.reply.map
7783
import io.element.android.libraries.matrix.ui.model.getAvatarData
7884
import io.element.android.libraries.matrix.ui.room.canCall
79-
import io.element.android.libraries.matrix.ui.room.canRedactOtherAsState
80-
import io.element.android.libraries.matrix.ui.room.canRedactOwnAsState
81-
import io.element.android.libraries.matrix.ui.room.canSendMessageAsState
8285
import io.element.android.libraries.textcomposer.model.MessageComposerMode
8386
import io.element.android.libraries.ui.strings.CommonStrings
8487
import kotlinx.collections.immutable.toPersistentList
@@ -131,10 +134,9 @@ class MessagesPresenter @AssistedInject constructor(
131134
val readReceiptBottomSheetState = readReceiptBottomSheetPresenter.present()
132135

133136
val syncUpdateFlow = room.syncUpdateFlow.collectAsState()
134-
val userHasPermissionToSendMessage by room.canSendMessageAsState(type = MessageEventType.ROOM_MESSAGE, updateKey = syncUpdateFlow.value)
135-
val userHasPermissionToRedactOwn by room.canRedactOwnAsState(updateKey = syncUpdateFlow.value)
136-
val userHasPermissionToRedactOther by room.canRedactOtherAsState(updateKey = syncUpdateFlow.value)
137-
val userHasPermissionToSendReaction by room.canSendMessageAsState(type = MessageEventType.REACTION, updateKey = syncUpdateFlow.value)
137+
138+
val userEventPermissions by userEventPermissions(syncUpdateFlow.value)
139+
138140
val roomName: AsyncData<String> by remember {
139141
derivedStateOf { roomInfo?.name?.let { AsyncData.Success(it) } ?: AsyncData.Uninitialized }
140142
}
@@ -211,11 +213,8 @@ class MessagesPresenter @AssistedInject constructor(
211213
roomName = roomName,
212214
roomAvatar = roomAvatar,
213215
heroes = heroes,
214-
userHasPermissionToSendMessage = userHasPermissionToSendMessage,
215-
userHasPermissionToRedactOwn = userHasPermissionToRedactOwn,
216-
userHasPermissionToRedactOther = userHasPermissionToRedactOther,
217-
userHasPermissionToSendReaction = userHasPermissionToSendReaction,
218216
composerState = composerState,
217+
userEventPermissions = userEventPermissions,
219218
voiceMessageComposerState = voiceMessageComposerState,
220219
timelineState = timelineState,
221220
typingNotificationState = typingNotificationState,
@@ -235,6 +234,19 @@ class MessagesPresenter @AssistedInject constructor(
235234
)
236235
}
237236

237+
@Composable
238+
private fun userEventPermissions(updateKey: Long): State<UserEventPermissions> {
239+
return produceState(UserEventPermissions.DEFAULT, key1 = updateKey) {
240+
value = UserEventPermissions(
241+
canSendMessage = room.canSendMessage(type = MessageEventType.ROOM_MESSAGE).getOrElse { true },
242+
canSendReaction = room.canSendMessage(type = MessageEventType.REACTION).getOrElse { true },
243+
canRedactOwn = room.canRedactOwn().getOrElse { false },
244+
canRedactOther = room.canRedactOther().getOrElse { false },
245+
canPinUnpin = room.canPinUnpin().getOrElse { false },
246+
)
247+
}
248+
}
249+
238250
private fun MatrixRoomInfo.avatarData(): AvatarData {
239251
return AvatarData(
240252
id = id.value,
@@ -268,6 +280,30 @@ class MessagesPresenter @AssistedInject constructor(
268280
TimelineItemAction.Forward -> handleForwardAction(targetEvent)
269281
TimelineItemAction.ReportContent -> handleReportAction(targetEvent)
270282
TimelineItemAction.EndPoll -> handleEndPollAction(targetEvent, timelineState)
283+
TimelineItemAction.Pin -> handlePinAction(targetEvent)
284+
TimelineItemAction.Unpin -> handleUnpinAction(targetEvent)
285+
}
286+
}
287+
288+
private suspend fun handlePinAction(targetEvent: TimelineItem.Event) {
289+
if (targetEvent.eventId == null) return
290+
timelineController.invokeOnCurrentTimeline {
291+
pinEvent(targetEvent.eventId)
292+
.onFailure {
293+
Timber.e(it, "Failed to pin event ${targetEvent.eventId}")
294+
snackbarDispatcher.post(SnackbarMessage(CommonStrings.common_error))
295+
}
296+
}
297+
}
298+
299+
private suspend fun handleUnpinAction(targetEvent: TimelineItem.Event) {
300+
if (targetEvent.eventId == null) return
301+
timelineController.invokeOnCurrentTimeline {
302+
unpinEvent(targetEvent.eventId)
303+
.onFailure {
304+
Timber.e(it, "Failed to unpin event ${targetEvent.eventId}")
305+
snackbarDispatcher.post(SnackbarMessage(CommonStrings.common_error))
306+
}
271307
}
272308
}
273309

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesState.kt

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,7 @@ data class MessagesState(
3737
val roomName: AsyncData<String>,
3838
val roomAvatar: AsyncData<AvatarData>,
3939
val heroes: ImmutableList<AvatarData>,
40-
val userHasPermissionToSendMessage: Boolean,
41-
val userHasPermissionToRedactOwn: Boolean,
42-
val userHasPermissionToRedactOther: Boolean,
43-
val userHasPermissionToSendReaction: Boolean,
40+
val userEventPermissions: UserEventPermissions,
4441
val composerState: MessageComposerState,
4542
val voiceMessageComposerState: VoiceMessageComposerState,
4643
val timelineState: TimelineState,

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ open class MessagesStateProvider : PreviewParameterProvider<MessagesState> {
5353
aMessagesState(),
5454
aMessagesState(hasNetworkConnection = false),
5555
aMessagesState(composerState = aMessageComposerState(showAttachmentSourcePicker = true)),
56-
aMessagesState(userHasPermissionToSendMessage = false),
56+
aMessagesState(userEventPermissions = aUserEventPermissions(canSendMessage = false)),
5757
aMessagesState(showReinvitePrompt = true),
5858
aMessagesState(
5959
roomName = AsyncData.Uninitialized,
@@ -93,10 +93,7 @@ open class MessagesStateProvider : PreviewParameterProvider<MessagesState> {
9393
fun aMessagesState(
9494
roomName: AsyncData<String> = AsyncData.Success("Room name"),
9595
roomAvatar: AsyncData<AvatarData> = AsyncData.Success(AvatarData("!id:domain", "Room name", size = AvatarSize.TimelineRoom)),
96-
userHasPermissionToSendMessage: Boolean = true,
97-
userHasPermissionToRedactOwn: Boolean = false,
98-
userHasPermissionToRedactOther: Boolean = false,
99-
userHasPermissionToSendReaction: Boolean = true,
96+
userEventPermissions: UserEventPermissions = aUserEventPermissions(),
10097
composerState: MessageComposerState = aMessageComposerState(
10198
textEditorState = TextEditorState.Rich(aRichTextEditorState(initialText = "Hello", initialFocus = true)),
10299
isFullScreen = false,
@@ -122,10 +119,7 @@ fun aMessagesState(
122119
roomName = roomName,
123120
roomAvatar = roomAvatar,
124121
heroes = persistentListOf(),
125-
userHasPermissionToSendMessage = userHasPermissionToSendMessage,
126-
userHasPermissionToRedactOwn = userHasPermissionToRedactOwn,
127-
userHasPermissionToRedactOther = userHasPermissionToRedactOther,
128-
userHasPermissionToSendReaction = userHasPermissionToSendReaction,
122+
userEventPermissions = userEventPermissions,
129123
composerState = composerState,
130124
voiceMessageComposerState = voiceMessageComposerState,
131125
typingNotificationState = aTypingNotificationState(),
@@ -145,6 +139,20 @@ fun aMessagesState(
145139
eventSink = eventSink,
146140
)
147141

142+
fun aUserEventPermissions(
143+
canRedactOwn: Boolean = false,
144+
canRedactOther: Boolean = false,
145+
canSendMessage: Boolean = true,
146+
canSendReaction: Boolean = true,
147+
canPinUnpin: Boolean = false,
148+
) = UserEventPermissions(
149+
canRedactOwn = canRedactOwn,
150+
canRedactOther = canRedactOther,
151+
canSendMessage = canSendMessage,
152+
canSendReaction = canSendReaction,
153+
canPinUnpin = canPinUnpin,
154+
)
155+
148156
fun aReactionSummaryState(
149157
target: ReactionSummaryState.Summary? = null,
150158
eventSink: (ReactionSummaryEvents) -> Unit = {}

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -154,10 +154,7 @@ fun MessagesView(
154154
state.actionListState.eventSink(
155155
ActionListEvents.ComputeForMessage(
156156
event = event,
157-
canRedactOwn = state.userHasPermissionToRedactOwn,
158-
canRedactOther = state.userHasPermissionToRedactOther,
159-
canSendMessage = state.userHasPermissionToSendMessage,
160-
canSendReaction = state.userHasPermissionToSendReaction,
157+
userEventPermissions = state.userEventPermissions,
161158
)
162159
)
163160
}
@@ -408,7 +405,7 @@ private fun MessagesViewComposerBottomSheetContents(
408405
subcomposing: Boolean,
409406
state: MessagesState,
410407
) {
411-
if (state.userHasPermissionToSendMessage) {
408+
if (state.userEventPermissions.canSendMessage) {
412409
Column(modifier = Modifier.fillMaxWidth()) {
413410
MentionSuggestionsPickerView(
414411
modifier = Modifier
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Copyright (c) 2024 New Vector Ltd
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.element.android.features.messages.impl
18+
19+
/**
20+
* Represents the permissions a user has in a room.
21+
* It's dependent of the user's power level in the room.
22+
*/
23+
data class UserEventPermissions(
24+
val canRedactOwn: Boolean,
25+
val canRedactOther: Boolean,
26+
val canSendMessage: Boolean,
27+
val canSendReaction: Boolean,
28+
val canPinUnpin: Boolean,
29+
) {
30+
companion object {
31+
val DEFAULT = UserEventPermissions(
32+
canRedactOwn = false,
33+
canRedactOther = false,
34+
canSendMessage = true,
35+
canSendReaction = true,
36+
canPinUnpin = false
37+
)
38+
}
39+
}

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListEvents.kt

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,13 @@
1616

1717
package io.element.android.features.messages.impl.actionlist
1818

19+
import io.element.android.features.messages.impl.UserEventPermissions
1920
import io.element.android.features.messages.impl.timeline.model.TimelineItem
2021

2122
sealed interface ActionListEvents {
2223
data object Clear : ActionListEvents
2324
data class ComputeForMessage(
2425
val event: TimelineItem.Event,
25-
val canRedactOwn: Boolean,
26-
val canRedactOther: Boolean,
27-
val canSendMessage: Boolean,
28-
val canSendReaction: Boolean,
26+
val userEventPermissions: UserEventPermissions,
2927
) : ActionListEvents
3028
}

0 commit comments

Comments
 (0)