Merge pull request #3255 from element-hq/feature/fga/pinned_event_feature_flag
WIP Pinned events : add feature flag and pin/unpin actions
This commit is contained in:
commit
48d2d3fc53
33 changed files with 755 additions and 342 deletions
|
|
@ -20,10 +20,12 @@ import android.os.Build
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.MutableState
|
import androidx.compose.runtime.MutableState
|
||||||
|
import androidx.compose.runtime.State
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.derivedStateOf
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.produceState
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
|
|
@ -73,12 +75,13 @@ import io.element.android.libraries.matrix.api.room.MatrixRoomInfo
|
||||||
import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState
|
import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState
|
||||||
import io.element.android.libraries.matrix.api.room.MessageEventType
|
import io.element.android.libraries.matrix.api.room.MessageEventType
|
||||||
import io.element.android.libraries.matrix.api.room.isDm
|
import io.element.android.libraries.matrix.api.room.isDm
|
||||||
|
import io.element.android.libraries.matrix.api.room.powerlevels.canPinUnpin
|
||||||
|
import io.element.android.libraries.matrix.api.room.powerlevels.canRedactOther
|
||||||
|
import io.element.android.libraries.matrix.api.room.powerlevels.canRedactOwn
|
||||||
|
import io.element.android.libraries.matrix.api.room.powerlevels.canSendMessage
|
||||||
import io.element.android.libraries.matrix.ui.messages.reply.map
|
import io.element.android.libraries.matrix.ui.messages.reply.map
|
||||||
import io.element.android.libraries.matrix.ui.model.getAvatarData
|
import io.element.android.libraries.matrix.ui.model.getAvatarData
|
||||||
import io.element.android.libraries.matrix.ui.room.canCall
|
import io.element.android.libraries.matrix.ui.room.canCall
|
||||||
import io.element.android.libraries.matrix.ui.room.canRedactOtherAsState
|
|
||||||
import io.element.android.libraries.matrix.ui.room.canRedactOwnAsState
|
|
||||||
import io.element.android.libraries.matrix.ui.room.canSendMessageAsState
|
|
||||||
import io.element.android.libraries.textcomposer.model.MessageComposerMode
|
import io.element.android.libraries.textcomposer.model.MessageComposerMode
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
import kotlinx.collections.immutable.toPersistentList
|
import kotlinx.collections.immutable.toPersistentList
|
||||||
|
|
@ -131,10 +134,9 @@ class MessagesPresenter @AssistedInject constructor(
|
||||||
val readReceiptBottomSheetState = readReceiptBottomSheetPresenter.present()
|
val readReceiptBottomSheetState = readReceiptBottomSheetPresenter.present()
|
||||||
|
|
||||||
val syncUpdateFlow = room.syncUpdateFlow.collectAsState()
|
val syncUpdateFlow = room.syncUpdateFlow.collectAsState()
|
||||||
val userHasPermissionToSendMessage by room.canSendMessageAsState(type = MessageEventType.ROOM_MESSAGE, updateKey = syncUpdateFlow.value)
|
|
||||||
val userHasPermissionToRedactOwn by room.canRedactOwnAsState(updateKey = syncUpdateFlow.value)
|
val userEventPermissions by userEventPermissions(syncUpdateFlow.value)
|
||||||
val userHasPermissionToRedactOther by room.canRedactOtherAsState(updateKey = syncUpdateFlow.value)
|
|
||||||
val userHasPermissionToSendReaction by room.canSendMessageAsState(type = MessageEventType.REACTION, updateKey = syncUpdateFlow.value)
|
|
||||||
val roomName: AsyncData<String> by remember {
|
val roomName: AsyncData<String> by remember {
|
||||||
derivedStateOf { roomInfo?.name?.let { AsyncData.Success(it) } ?: AsyncData.Uninitialized }
|
derivedStateOf { roomInfo?.name?.let { AsyncData.Success(it) } ?: AsyncData.Uninitialized }
|
||||||
}
|
}
|
||||||
|
|
@ -211,11 +213,8 @@ class MessagesPresenter @AssistedInject constructor(
|
||||||
roomName = roomName,
|
roomName = roomName,
|
||||||
roomAvatar = roomAvatar,
|
roomAvatar = roomAvatar,
|
||||||
heroes = heroes,
|
heroes = heroes,
|
||||||
userHasPermissionToSendMessage = userHasPermissionToSendMessage,
|
|
||||||
userHasPermissionToRedactOwn = userHasPermissionToRedactOwn,
|
|
||||||
userHasPermissionToRedactOther = userHasPermissionToRedactOther,
|
|
||||||
userHasPermissionToSendReaction = userHasPermissionToSendReaction,
|
|
||||||
composerState = composerState,
|
composerState = composerState,
|
||||||
|
userEventPermissions = userEventPermissions,
|
||||||
voiceMessageComposerState = voiceMessageComposerState,
|
voiceMessageComposerState = voiceMessageComposerState,
|
||||||
timelineState = timelineState,
|
timelineState = timelineState,
|
||||||
typingNotificationState = typingNotificationState,
|
typingNotificationState = typingNotificationState,
|
||||||
|
|
@ -235,6 +234,19 @@ class MessagesPresenter @AssistedInject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun userEventPermissions(updateKey: Long): State<UserEventPermissions> {
|
||||||
|
return produceState(UserEventPermissions.DEFAULT, key1 = updateKey) {
|
||||||
|
value = UserEventPermissions(
|
||||||
|
canSendMessage = room.canSendMessage(type = MessageEventType.ROOM_MESSAGE).getOrElse { true },
|
||||||
|
canSendReaction = room.canSendMessage(type = MessageEventType.REACTION).getOrElse { true },
|
||||||
|
canRedactOwn = room.canRedactOwn().getOrElse { false },
|
||||||
|
canRedactOther = room.canRedactOther().getOrElse { false },
|
||||||
|
canPinUnpin = room.canPinUnpin().getOrElse { false },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun MatrixRoomInfo.avatarData(): AvatarData {
|
private fun MatrixRoomInfo.avatarData(): AvatarData {
|
||||||
return AvatarData(
|
return AvatarData(
|
||||||
id = id.value,
|
id = id.value,
|
||||||
|
|
@ -268,6 +280,30 @@ class MessagesPresenter @AssistedInject constructor(
|
||||||
TimelineItemAction.Forward -> handleForwardAction(targetEvent)
|
TimelineItemAction.Forward -> handleForwardAction(targetEvent)
|
||||||
TimelineItemAction.ReportContent -> handleReportAction(targetEvent)
|
TimelineItemAction.ReportContent -> handleReportAction(targetEvent)
|
||||||
TimelineItemAction.EndPoll -> handleEndPollAction(targetEvent, timelineState)
|
TimelineItemAction.EndPoll -> handleEndPollAction(targetEvent, timelineState)
|
||||||
|
TimelineItemAction.Pin -> handlePinAction(targetEvent)
|
||||||
|
TimelineItemAction.Unpin -> handleUnpinAction(targetEvent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun handlePinAction(targetEvent: TimelineItem.Event) {
|
||||||
|
if (targetEvent.eventId == null) return
|
||||||
|
timelineController.invokeOnCurrentTimeline {
|
||||||
|
pinEvent(targetEvent.eventId)
|
||||||
|
.onFailure {
|
||||||
|
Timber.e(it, "Failed to pin event ${targetEvent.eventId}")
|
||||||
|
snackbarDispatcher.post(SnackbarMessage(CommonStrings.common_error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun handleUnpinAction(targetEvent: TimelineItem.Event) {
|
||||||
|
if (targetEvent.eventId == null) return
|
||||||
|
timelineController.invokeOnCurrentTimeline {
|
||||||
|
unpinEvent(targetEvent.eventId)
|
||||||
|
.onFailure {
|
||||||
|
Timber.e(it, "Failed to unpin event ${targetEvent.eventId}")
|
||||||
|
snackbarDispatcher.post(SnackbarMessage(CommonStrings.common_error))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -37,10 +37,7 @@ data class MessagesState(
|
||||||
val roomName: AsyncData<String>,
|
val roomName: AsyncData<String>,
|
||||||
val roomAvatar: AsyncData<AvatarData>,
|
val roomAvatar: AsyncData<AvatarData>,
|
||||||
val heroes: ImmutableList<AvatarData>,
|
val heroes: ImmutableList<AvatarData>,
|
||||||
val userHasPermissionToSendMessage: Boolean,
|
val userEventPermissions: UserEventPermissions,
|
||||||
val userHasPermissionToRedactOwn: Boolean,
|
|
||||||
val userHasPermissionToRedactOther: Boolean,
|
|
||||||
val userHasPermissionToSendReaction: Boolean,
|
|
||||||
val composerState: MessageComposerState,
|
val composerState: MessageComposerState,
|
||||||
val voiceMessageComposerState: VoiceMessageComposerState,
|
val voiceMessageComposerState: VoiceMessageComposerState,
|
||||||
val timelineState: TimelineState,
|
val timelineState: TimelineState,
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ open class MessagesStateProvider : PreviewParameterProvider<MessagesState> {
|
||||||
aMessagesState(),
|
aMessagesState(),
|
||||||
aMessagesState(hasNetworkConnection = false),
|
aMessagesState(hasNetworkConnection = false),
|
||||||
aMessagesState(composerState = aMessageComposerState(showAttachmentSourcePicker = true)),
|
aMessagesState(composerState = aMessageComposerState(showAttachmentSourcePicker = true)),
|
||||||
aMessagesState(userHasPermissionToSendMessage = false),
|
aMessagesState(userEventPermissions = aUserEventPermissions(canSendMessage = false)),
|
||||||
aMessagesState(showReinvitePrompt = true),
|
aMessagesState(showReinvitePrompt = true),
|
||||||
aMessagesState(
|
aMessagesState(
|
||||||
roomName = AsyncData.Uninitialized,
|
roomName = AsyncData.Uninitialized,
|
||||||
|
|
@ -93,10 +93,7 @@ open class MessagesStateProvider : PreviewParameterProvider<MessagesState> {
|
||||||
fun aMessagesState(
|
fun aMessagesState(
|
||||||
roomName: AsyncData<String> = AsyncData.Success("Room name"),
|
roomName: AsyncData<String> = AsyncData.Success("Room name"),
|
||||||
roomAvatar: AsyncData<AvatarData> = AsyncData.Success(AvatarData("!id:domain", "Room name", size = AvatarSize.TimelineRoom)),
|
roomAvatar: AsyncData<AvatarData> = AsyncData.Success(AvatarData("!id:domain", "Room name", size = AvatarSize.TimelineRoom)),
|
||||||
userHasPermissionToSendMessage: Boolean = true,
|
userEventPermissions: UserEventPermissions = aUserEventPermissions(),
|
||||||
userHasPermissionToRedactOwn: Boolean = false,
|
|
||||||
userHasPermissionToRedactOther: Boolean = false,
|
|
||||||
userHasPermissionToSendReaction: Boolean = true,
|
|
||||||
composerState: MessageComposerState = aMessageComposerState(
|
composerState: MessageComposerState = aMessageComposerState(
|
||||||
textEditorState = TextEditorState.Rich(aRichTextEditorState(initialText = "Hello", initialFocus = true)),
|
textEditorState = TextEditorState.Rich(aRichTextEditorState(initialText = "Hello", initialFocus = true)),
|
||||||
isFullScreen = false,
|
isFullScreen = false,
|
||||||
|
|
@ -122,10 +119,7 @@ fun aMessagesState(
|
||||||
roomName = roomName,
|
roomName = roomName,
|
||||||
roomAvatar = roomAvatar,
|
roomAvatar = roomAvatar,
|
||||||
heroes = persistentListOf(),
|
heroes = persistentListOf(),
|
||||||
userHasPermissionToSendMessage = userHasPermissionToSendMessage,
|
userEventPermissions = userEventPermissions,
|
||||||
userHasPermissionToRedactOwn = userHasPermissionToRedactOwn,
|
|
||||||
userHasPermissionToRedactOther = userHasPermissionToRedactOther,
|
|
||||||
userHasPermissionToSendReaction = userHasPermissionToSendReaction,
|
|
||||||
composerState = composerState,
|
composerState = composerState,
|
||||||
voiceMessageComposerState = voiceMessageComposerState,
|
voiceMessageComposerState = voiceMessageComposerState,
|
||||||
typingNotificationState = aTypingNotificationState(),
|
typingNotificationState = aTypingNotificationState(),
|
||||||
|
|
@ -145,6 +139,20 @@ fun aMessagesState(
|
||||||
eventSink = eventSink,
|
eventSink = eventSink,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fun aUserEventPermissions(
|
||||||
|
canRedactOwn: Boolean = false,
|
||||||
|
canRedactOther: Boolean = false,
|
||||||
|
canSendMessage: Boolean = true,
|
||||||
|
canSendReaction: Boolean = true,
|
||||||
|
canPinUnpin: Boolean = false,
|
||||||
|
) = UserEventPermissions(
|
||||||
|
canRedactOwn = canRedactOwn,
|
||||||
|
canRedactOther = canRedactOther,
|
||||||
|
canSendMessage = canSendMessage,
|
||||||
|
canSendReaction = canSendReaction,
|
||||||
|
canPinUnpin = canPinUnpin,
|
||||||
|
)
|
||||||
|
|
||||||
fun aReactionSummaryState(
|
fun aReactionSummaryState(
|
||||||
target: ReactionSummaryState.Summary? = null,
|
target: ReactionSummaryState.Summary? = null,
|
||||||
eventSink: (ReactionSummaryEvents) -> Unit = {}
|
eventSink: (ReactionSummaryEvents) -> Unit = {}
|
||||||
|
|
|
||||||
|
|
@ -154,10 +154,7 @@ fun MessagesView(
|
||||||
state.actionListState.eventSink(
|
state.actionListState.eventSink(
|
||||||
ActionListEvents.ComputeForMessage(
|
ActionListEvents.ComputeForMessage(
|
||||||
event = event,
|
event = event,
|
||||||
canRedactOwn = state.userHasPermissionToRedactOwn,
|
userEventPermissions = state.userEventPermissions,
|
||||||
canRedactOther = state.userHasPermissionToRedactOther,
|
|
||||||
canSendMessage = state.userHasPermissionToSendMessage,
|
|
||||||
canSendReaction = state.userHasPermissionToSendReaction,
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -408,7 +405,7 @@ private fun MessagesViewComposerBottomSheetContents(
|
||||||
subcomposing: Boolean,
|
subcomposing: Boolean,
|
||||||
state: MessagesState,
|
state: MessagesState,
|
||||||
) {
|
) {
|
||||||
if (state.userHasPermissionToSendMessage) {
|
if (state.userEventPermissions.canSendMessage) {
|
||||||
Column(modifier = Modifier.fillMaxWidth()) {
|
Column(modifier = Modifier.fillMaxWidth()) {
|
||||||
MentionSuggestionsPickerView(
|
MentionSuggestionsPickerView(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.messages.impl
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the permissions a user has in a room.
|
||||||
|
* It's dependent of the user's power level in the room.
|
||||||
|
*/
|
||||||
|
data class UserEventPermissions(
|
||||||
|
val canRedactOwn: Boolean,
|
||||||
|
val canRedactOther: Boolean,
|
||||||
|
val canSendMessage: Boolean,
|
||||||
|
val canSendReaction: Boolean,
|
||||||
|
val canPinUnpin: Boolean,
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
val DEFAULT = UserEventPermissions(
|
||||||
|
canRedactOwn = false,
|
||||||
|
canRedactOther = false,
|
||||||
|
canSendMessage = true,
|
||||||
|
canSendReaction = true,
|
||||||
|
canPinUnpin = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -16,15 +16,13 @@
|
||||||
|
|
||||||
package io.element.android.features.messages.impl.actionlist
|
package io.element.android.features.messages.impl.actionlist
|
||||||
|
|
||||||
|
import io.element.android.features.messages.impl.UserEventPermissions
|
||||||
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
||||||
|
|
||||||
sealed interface ActionListEvents {
|
sealed interface ActionListEvents {
|
||||||
data object Clear : ActionListEvents
|
data object Clear : ActionListEvents
|
||||||
data class ComputeForMessage(
|
data class ComputeForMessage(
|
||||||
val event: TimelineItem.Event,
|
val event: TimelineItem.Event,
|
||||||
val canRedactOwn: Boolean,
|
val userEventPermissions: UserEventPermissions,
|
||||||
val canRedactOther: Boolean,
|
|
||||||
val canSendMessage: Boolean,
|
|
||||||
val canSendReaction: Boolean,
|
|
||||||
) : ActionListEvents
|
) : ActionListEvents
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,25 +23,36 @@ import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import io.element.android.features.messages.impl.UserEventPermissions
|
||||||
import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction
|
import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction
|
||||||
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemCallNotifyContent
|
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemCallNotifyContent
|
||||||
|
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLegacyCallInviteContent
|
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLegacyCallInviteContent
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPollContent
|
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPollContent
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRedactedContent
|
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRedactedContent
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStateContent
|
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStateContent
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVoiceContent
|
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.canBeCopied
|
import io.element.android.features.messages.impl.timeline.model.event.canBeCopied
|
||||||
|
import io.element.android.features.messages.impl.timeline.model.event.canBeForwarded
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.canReact
|
import io.element.android.features.messages.impl.timeline.model.event.canReact
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
|
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||||
|
import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||||
|
import io.element.android.libraries.matrix.api.core.EventId
|
||||||
|
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||||
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
|
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
|
||||||
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class ActionListPresenter @Inject constructor(
|
class ActionListPresenter @Inject constructor(
|
||||||
private val appPreferencesStore: AppPreferencesStore,
|
private val appPreferencesStore: AppPreferencesStore,
|
||||||
|
private val featureFlagsService: FeatureFlagService,
|
||||||
|
private val room: MatrixRoom,
|
||||||
) : Presenter<ActionListState> {
|
) : Presenter<ActionListState> {
|
||||||
@Composable
|
@Composable
|
||||||
override fun present(): ActionListState {
|
override fun present(): ActionListState {
|
||||||
|
|
@ -52,17 +63,20 @@ class ActionListPresenter @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
val isDeveloperModeEnabled by appPreferencesStore.isDeveloperModeEnabledFlow().collectAsState(initial = false)
|
val isDeveloperModeEnabled by appPreferencesStore.isDeveloperModeEnabledFlow().collectAsState(initial = false)
|
||||||
|
val isPinnedEventsEnabled by featureFlagsService.isFeatureEnabledFlow(FeatureFlags.PinnedEvents).collectAsState(initial = false)
|
||||||
|
val pinnedEventIds by remember {
|
||||||
|
room.roomInfoFlow.map { it.pinnedEventIds }
|
||||||
|
}.collectAsState(initial = persistentListOf())
|
||||||
|
|
||||||
fun handleEvents(event: ActionListEvents) {
|
fun handleEvents(event: ActionListEvents) {
|
||||||
when (event) {
|
when (event) {
|
||||||
ActionListEvents.Clear -> target.value = ActionListState.Target.None
|
ActionListEvents.Clear -> target.value = ActionListState.Target.None
|
||||||
is ActionListEvents.ComputeForMessage -> localCoroutineScope.computeForMessage(
|
is ActionListEvents.ComputeForMessage -> localCoroutineScope.computeForMessage(
|
||||||
timelineItem = event.event,
|
timelineItem = event.event,
|
||||||
userCanRedactOwn = event.canRedactOwn,
|
usersEventPermissions = event.userEventPermissions,
|
||||||
userCanRedactOther = event.canRedactOther,
|
|
||||||
userCanSendMessage = event.canSendMessage,
|
|
||||||
userCanSendReaction = event.canSendReaction,
|
|
||||||
isDeveloperModeEnabled = isDeveloperModeEnabled,
|
isDeveloperModeEnabled = isDeveloperModeEnabled,
|
||||||
|
isPinnedEventsEnabled = isPinnedEventsEnabled,
|
||||||
|
pinnedEventIds = pinnedEventIds,
|
||||||
target = target,
|
target = target,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -76,136 +90,22 @@ class ActionListPresenter @Inject constructor(
|
||||||
|
|
||||||
private fun CoroutineScope.computeForMessage(
|
private fun CoroutineScope.computeForMessage(
|
||||||
timelineItem: TimelineItem.Event,
|
timelineItem: TimelineItem.Event,
|
||||||
userCanRedactOwn: Boolean,
|
usersEventPermissions: UserEventPermissions,
|
||||||
userCanRedactOther: Boolean,
|
|
||||||
userCanSendMessage: Boolean,
|
|
||||||
userCanSendReaction: Boolean,
|
|
||||||
isDeveloperModeEnabled: Boolean,
|
isDeveloperModeEnabled: Boolean,
|
||||||
|
isPinnedEventsEnabled: Boolean,
|
||||||
|
pinnedEventIds: ImmutableList<EventId>,
|
||||||
target: MutableState<ActionListState.Target>
|
target: MutableState<ActionListState.Target>
|
||||||
) = launch {
|
) = launch {
|
||||||
target.value = ActionListState.Target.Loading(timelineItem)
|
target.value = ActionListState.Target.Loading(timelineItem)
|
||||||
val canRedact = timelineItem.isMine && userCanRedactOwn || !timelineItem.isMine && userCanRedactOther
|
|
||||||
val actions =
|
val actions = buildActions(
|
||||||
when (timelineItem.content) {
|
timelineItem = timelineItem,
|
||||||
is TimelineItemCallNotifyContent -> {
|
usersEventPermissions = usersEventPermissions,
|
||||||
if (isDeveloperModeEnabled) {
|
isDeveloperModeEnabled = isDeveloperModeEnabled,
|
||||||
listOf(TimelineItemAction.ViewSource)
|
isPinnedEventsEnabled = isPinnedEventsEnabled,
|
||||||
} else {
|
isEventPinned = pinnedEventIds.contains(timelineItem.eventId),
|
||||||
emptyList()
|
)
|
||||||
}
|
val displayEmojiReactions = usersEventPermissions.canSendReaction &&
|
||||||
}
|
|
||||||
is TimelineItemRedactedContent -> {
|
|
||||||
if (isDeveloperModeEnabled) {
|
|
||||||
listOf(TimelineItemAction.ViewSource)
|
|
||||||
} else {
|
|
||||||
emptyList()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is TimelineItemStateContent -> {
|
|
||||||
buildList {
|
|
||||||
add(TimelineItemAction.Copy)
|
|
||||||
if (timelineItem.isRemote) {
|
|
||||||
add(TimelineItemAction.CopyLink)
|
|
||||||
}
|
|
||||||
if (isDeveloperModeEnabled) {
|
|
||||||
add(TimelineItemAction.ViewSource)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is TimelineItemPollContent -> {
|
|
||||||
val canEndPoll = timelineItem.isRemote &&
|
|
||||||
!timelineItem.content.isEnded &&
|
|
||||||
(timelineItem.isMine || canRedact)
|
|
||||||
buildList {
|
|
||||||
if (timelineItem.isRemote) {
|
|
||||||
// Can only reply or forward messages already uploaded to the server
|
|
||||||
add(TimelineItemAction.Reply)
|
|
||||||
}
|
|
||||||
if (timelineItem.isRemote && timelineItem.isEditable) {
|
|
||||||
add(TimelineItemAction.Edit)
|
|
||||||
}
|
|
||||||
if (canEndPoll) {
|
|
||||||
add(TimelineItemAction.EndPoll)
|
|
||||||
}
|
|
||||||
if (timelineItem.content.canBeCopied()) {
|
|
||||||
add(TimelineItemAction.Copy)
|
|
||||||
}
|
|
||||||
if (timelineItem.isRemote) {
|
|
||||||
add(TimelineItemAction.CopyLink)
|
|
||||||
}
|
|
||||||
if (isDeveloperModeEnabled) {
|
|
||||||
add(TimelineItemAction.ViewSource)
|
|
||||||
}
|
|
||||||
if (!timelineItem.isMine) {
|
|
||||||
add(TimelineItemAction.ReportContent)
|
|
||||||
}
|
|
||||||
if (canRedact) {
|
|
||||||
add(TimelineItemAction.Redact)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is TimelineItemVoiceContent -> {
|
|
||||||
buildList {
|
|
||||||
if (timelineItem.isRemote) {
|
|
||||||
add(TimelineItemAction.Reply)
|
|
||||||
add(TimelineItemAction.Forward)
|
|
||||||
add(TimelineItemAction.CopyLink)
|
|
||||||
}
|
|
||||||
if (isDeveloperModeEnabled) {
|
|
||||||
add(TimelineItemAction.ViewSource)
|
|
||||||
}
|
|
||||||
if (!timelineItem.isMine) {
|
|
||||||
add(TimelineItemAction.ReportContent)
|
|
||||||
}
|
|
||||||
if (canRedact) {
|
|
||||||
add(TimelineItemAction.Redact)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is TimelineItemLegacyCallInviteContent -> {
|
|
||||||
buildList {
|
|
||||||
if (isDeveloperModeEnabled) {
|
|
||||||
add(TimelineItemAction.ViewSource)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> buildList<TimelineItemAction> {
|
|
||||||
if (timelineItem.isRemote) {
|
|
||||||
// Can only reply or forward messages already uploaded to the server
|
|
||||||
if (userCanSendMessage) {
|
|
||||||
if (timelineItem.isThreaded) {
|
|
||||||
add(TimelineItemAction.ReplyInThread)
|
|
||||||
} else {
|
|
||||||
add(TimelineItemAction.Reply)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Stickers can't be forwarded (yet) so we don't show the option
|
|
||||||
// See https://github.com/element-hq/element-x-android/issues/2161
|
|
||||||
if (!timelineItem.isSticker) {
|
|
||||||
add(TimelineItemAction.Forward)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (timelineItem.isEditable) {
|
|
||||||
add(TimelineItemAction.Edit)
|
|
||||||
}
|
|
||||||
if (timelineItem.content.canBeCopied()) {
|
|
||||||
add(TimelineItemAction.Copy)
|
|
||||||
}
|
|
||||||
if (timelineItem.isRemote) {
|
|
||||||
add(TimelineItemAction.CopyLink)
|
|
||||||
}
|
|
||||||
if (isDeveloperModeEnabled) {
|
|
||||||
add(TimelineItemAction.ViewSource)
|
|
||||||
}
|
|
||||||
if (!timelineItem.isMine) {
|
|
||||||
add(TimelineItemAction.ReportContent)
|
|
||||||
}
|
|
||||||
if (canRedact) {
|
|
||||||
add(TimelineItemAction.Redact)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val displayEmojiReactions = userCanSendReaction &&
|
|
||||||
timelineItem.isRemote &&
|
timelineItem.isRemote &&
|
||||||
timelineItem.content.canReact()
|
timelineItem.content.canReact()
|
||||||
if (actions.isNotEmpty() || displayEmojiReactions) {
|
if (actions.isNotEmpty() || displayEmojiReactions) {
|
||||||
|
|
@ -219,3 +119,71 @@ class ActionListPresenter @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun buildActions(
|
||||||
|
timelineItem: TimelineItem.Event,
|
||||||
|
usersEventPermissions: UserEventPermissions,
|
||||||
|
isDeveloperModeEnabled: Boolean,
|
||||||
|
isPinnedEventsEnabled: Boolean,
|
||||||
|
isEventPinned: Boolean,
|
||||||
|
): List<TimelineItemAction> {
|
||||||
|
val canRedact = timelineItem.isMine && usersEventPermissions.canRedactOwn || !timelineItem.isMine && usersEventPermissions.canRedactOther
|
||||||
|
return buildList {
|
||||||
|
if (timelineItem.canBeRepliedTo && usersEventPermissions.canSendMessage) {
|
||||||
|
if (timelineItem.isThreaded) {
|
||||||
|
add(TimelineItemAction.ReplyInThread)
|
||||||
|
} else {
|
||||||
|
add(TimelineItemAction.Reply)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (timelineItem.isRemote && timelineItem.content.canBeForwarded()) {
|
||||||
|
add(TimelineItemAction.Forward)
|
||||||
|
}
|
||||||
|
if (timelineItem.isEditable) {
|
||||||
|
add(TimelineItemAction.Edit)
|
||||||
|
}
|
||||||
|
if (canRedact && timelineItem.content is TimelineItemPollContent && !timelineItem.content.isEnded) {
|
||||||
|
add(TimelineItemAction.EndPoll)
|
||||||
|
}
|
||||||
|
val canPinUnpin = isPinnedEventsEnabled && usersEventPermissions.canPinUnpin && timelineItem.isRemote
|
||||||
|
if (canPinUnpin) {
|
||||||
|
if (isEventPinned) {
|
||||||
|
add(TimelineItemAction.Unpin)
|
||||||
|
} else {
|
||||||
|
add(TimelineItemAction.Pin)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (timelineItem.content.canBeCopied()) {
|
||||||
|
add(TimelineItemAction.Copy)
|
||||||
|
}
|
||||||
|
if (timelineItem.isRemote) {
|
||||||
|
add(TimelineItemAction.CopyLink)
|
||||||
|
}
|
||||||
|
if (isDeveloperModeEnabled) {
|
||||||
|
add(TimelineItemAction.ViewSource)
|
||||||
|
}
|
||||||
|
if (!timelineItem.isMine) {
|
||||||
|
add(TimelineItemAction.ReportContent)
|
||||||
|
}
|
||||||
|
if (canRedact) {
|
||||||
|
add(TimelineItemAction.Redact)
|
||||||
|
}
|
||||||
|
}.postFilter(timelineItem.content)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Post filter the actions based on the content of the event.
|
||||||
|
*/
|
||||||
|
private fun List<TimelineItemAction>.postFilter(content: TimelineItemEventContent): List<TimelineItemAction> {
|
||||||
|
return filter { action ->
|
||||||
|
when (content) {
|
||||||
|
is TimelineItemCallNotifyContent,
|
||||||
|
is TimelineItemLegacyCallInviteContent,
|
||||||
|
is TimelineItemStateContent,
|
||||||
|
is TimelineItemRedactedContent -> {
|
||||||
|
action == TimelineItemAction.ViewSource
|
||||||
|
}
|
||||||
|
else -> true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -39,4 +39,8 @@ sealed class TimelineItemAction(
|
||||||
data object ViewSource : TimelineItemAction(CommonStrings.action_view_source, CommonDrawables.ic_developer_options)
|
data object ViewSource : TimelineItemAction(CommonStrings.action_view_source, CommonDrawables.ic_developer_options)
|
||||||
data object ReportContent : TimelineItemAction(CommonStrings.action_report_content, CompoundDrawables.ic_compound_chat_problem, destructive = true)
|
data object ReportContent : TimelineItemAction(CommonStrings.action_report_content, CompoundDrawables.ic_compound_chat_problem, destructive = true)
|
||||||
data object EndPoll : TimelineItemAction(CommonStrings.action_end_poll, CompoundDrawables.ic_compound_polls_end)
|
data object EndPoll : TimelineItemAction(CommonStrings.action_end_poll, CompoundDrawables.ic_compound_polls_end)
|
||||||
|
data object Pin : TimelineItemAction(CommonStrings.action_pin, CompoundDrawables.ic_compound_pin)
|
||||||
|
|
||||||
|
// TODO use the Unpin compound icon when available.
|
||||||
|
data object Unpin : TimelineItemAction(CommonStrings.action_unpin, CompoundDrawables.ic_compound_pin)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -127,6 +127,7 @@ internal fun aTimelineItemEvent(
|
||||||
transactionId: TransactionId? = null,
|
transactionId: TransactionId? = null,
|
||||||
isMine: Boolean = false,
|
isMine: Boolean = false,
|
||||||
isEditable: Boolean = false,
|
isEditable: Boolean = false,
|
||||||
|
canBeRepliedTo: Boolean = false,
|
||||||
senderDisplayName: String = "Sender",
|
senderDisplayName: String = "Sender",
|
||||||
displayNameAmbiguous: Boolean = false,
|
displayNameAmbiguous: Boolean = false,
|
||||||
content: TimelineItemEventContent = aTimelineItemTextContent(),
|
content: TimelineItemEventContent = aTimelineItemTextContent(),
|
||||||
|
|
@ -150,6 +151,7 @@ internal fun aTimelineItemEvent(
|
||||||
sentTime = "12:34",
|
sentTime = "12:34",
|
||||||
isMine = isMine,
|
isMine = isMine,
|
||||||
isEditable = isEditable,
|
isEditable = isEditable,
|
||||||
|
canBeRepliedTo = canBeRepliedTo,
|
||||||
senderProfile = aProfileTimelineDetailsReady(
|
senderProfile = aProfileTimelineDetailsReady(
|
||||||
displayName = senderDisplayName,
|
displayName = senderDisplayName,
|
||||||
displayNameAmbiguous = displayNameAmbiguous,
|
displayNameAmbiguous = displayNameAmbiguous,
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,6 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVoiceContent
|
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVoiceContent
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemImageContent
|
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemImageContent
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemTextContent
|
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemTextContent
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.canBeRepliedTo
|
|
||||||
import io.element.android.libraries.designsystem.colors.AvatarColorsProvider
|
import io.element.android.libraries.designsystem.colors.AvatarColorsProvider
|
||||||
import io.element.android.libraries.designsystem.components.EqualWidthColumn
|
import io.element.android.libraries.designsystem.components.EqualWidthColumn
|
||||||
import io.element.android.libraries.designsystem.components.avatar.Avatar
|
import io.element.android.libraries.designsystem.components.avatar.Avatar
|
||||||
|
|
@ -148,7 +147,7 @@ fun TimelineItemEventRow(
|
||||||
} else {
|
} else {
|
||||||
Spacer(modifier = Modifier.height(2.dp))
|
Spacer(modifier = Modifier.height(2.dp))
|
||||||
}
|
}
|
||||||
val canReply = timelineRoomInfo.userHasPermissionToSendMessage && event.content.canBeRepliedTo()
|
val canReply = timelineRoomInfo.userHasPermissionToSendMessage && event.canBeRepliedTo
|
||||||
if (canReply) {
|
if (canReply) {
|
||||||
val state: SwipeableActionsState = rememberSwipeableActionsState()
|
val state: SwipeableActionsState = rememberSwipeableActionsState()
|
||||||
val offset = state.offset.floatValue
|
val offset = state.offset.floatValue
|
||||||
|
|
|
||||||
|
|
@ -76,6 +76,7 @@ class TimelineItemEventFactory @Inject constructor(
|
||||||
content = contentFactory.create(currentTimelineItem.event),
|
content = contentFactory.create(currentTimelineItem.event),
|
||||||
isMine = currentTimelineItem.event.isOwn,
|
isMine = currentTimelineItem.event.isOwn,
|
||||||
isEditable = currentTimelineItem.event.isEditable,
|
isEditable = currentTimelineItem.event.isEditable,
|
||||||
|
canBeRepliedTo = currentTimelineItem.event.canBeRepliedTo,
|
||||||
sentTime = sentTime,
|
sentTime = sentTime,
|
||||||
groupPosition = groupPosition,
|
groupPosition = groupPosition,
|
||||||
reactionsState = currentTimelineItem.computeReactionsState(),
|
reactionsState = currentTimelineItem.computeReactionsState(),
|
||||||
|
|
|
||||||
|
|
@ -74,6 +74,7 @@ sealed interface TimelineItem {
|
||||||
val sentTime: String = "",
|
val sentTime: String = "",
|
||||||
val isMine: Boolean = false,
|
val isMine: Boolean = false,
|
||||||
val isEditable: Boolean,
|
val isEditable: Boolean,
|
||||||
|
val canBeRepliedTo: Boolean,
|
||||||
val groupPosition: TimelineItemGroupPosition = TimelineItemGroupPosition.None,
|
val groupPosition: TimelineItemGroupPosition = TimelineItemGroupPosition.None,
|
||||||
val reactionsState: TimelineItemReactions,
|
val reactionsState: TimelineItemReactions,
|
||||||
val readReceiptState: TimelineItemReadReceipts,
|
val readReceiptState: TimelineItemReadReceipts,
|
||||||
|
|
|
||||||
|
|
@ -24,27 +24,27 @@ sealed interface TimelineItemEventContent {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Only text based content and states can be copied.
|
* Only text based content can be copied.
|
||||||
*/
|
*/
|
||||||
fun TimelineItemEventContent.canBeCopied(): Boolean =
|
fun TimelineItemEventContent.canBeCopied(): Boolean =
|
||||||
when (this) {
|
this is TimelineItemTextBasedContent
|
||||||
is TimelineItemTextBasedContent,
|
|
||||||
is TimelineItemStateContent,
|
|
||||||
is TimelineItemRedactedContent -> true
|
|
||||||
else -> false
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine if the event content can be replied to.
|
* Returns true if the event content can be forwarded.
|
||||||
* Note: it should match the logic in [io.element.android.features.messages.impl.actionlist.ActionListPresenter].
|
|
||||||
*/
|
*/
|
||||||
fun TimelineItemEventContent.canBeRepliedTo(): Boolean =
|
fun TimelineItemEventContent.canBeForwarded(): Boolean =
|
||||||
when (this) {
|
when (this) {
|
||||||
is TimelineItemRedactedContent,
|
is TimelineItemTextBasedContent,
|
||||||
is TimelineItemLegacyCallInviteContent,
|
is TimelineItemImageContent,
|
||||||
is TimelineItemCallNotifyContent,
|
is TimelineItemFileContent,
|
||||||
is TimelineItemStateContent -> false
|
is TimelineItemAudioContent,
|
||||||
else -> true
|
is TimelineItemVideoContent,
|
||||||
|
is TimelineItemLocationContent,
|
||||||
|
is TimelineItemVoiceContent -> true
|
||||||
|
// Stickers can't be forwarded (yet) so we don't show the option
|
||||||
|
// See https://github.com/element-hq/element-x-android/issues/2161
|
||||||
|
is TimelineItemStickerContent -> false
|
||||||
|
else -> false
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextContent
|
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextContent
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVideoContent
|
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVideoContent
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemPollContent
|
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemPollContent
|
||||||
|
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemTextContent
|
||||||
import io.element.android.features.messages.impl.typing.TypingNotificationPresenter
|
import io.element.android.features.messages.impl.typing.TypingNotificationPresenter
|
||||||
import io.element.android.features.messages.impl.utils.FakeTextPillificationHelper
|
import io.element.android.features.messages.impl.utils.FakeTextPillificationHelper
|
||||||
import io.element.android.features.messages.impl.voicemessages.composer.VoiceMessageComposerPlayer
|
import io.element.android.features.messages.impl.voicemessages.composer.VoiceMessageComposerPlayer
|
||||||
|
|
@ -77,6 +78,7 @@ import io.element.android.libraries.matrix.test.AN_EVENT_ID
|
||||||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||||
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
||||||
import io.element.android.libraries.matrix.test.A_SESSION_ID_2
|
import io.element.android.libraries.matrix.test.A_SESSION_ID_2
|
||||||
|
import io.element.android.libraries.matrix.test.A_THROWABLE
|
||||||
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
||||||
import io.element.android.libraries.matrix.test.permalink.FakePermalinkBuilder
|
import io.element.android.libraries.matrix.test.permalink.FakePermalinkBuilder
|
||||||
import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser
|
import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser
|
||||||
|
|
@ -137,8 +139,8 @@ class MessagesPresenterTest {
|
||||||
assertThat(initialState.roomName).isEqualTo(AsyncData.Success(""))
|
assertThat(initialState.roomName).isEqualTo(AsyncData.Success(""))
|
||||||
assertThat(initialState.roomAvatar)
|
assertThat(initialState.roomAvatar)
|
||||||
.isEqualTo(AsyncData.Success(AvatarData(id = A_ROOM_ID.value, name = "", url = AN_AVATAR_URL, size = AvatarSize.TimelineRoom)))
|
.isEqualTo(AsyncData.Success(AvatarData(id = A_ROOM_ID.value, name = "", url = AN_AVATAR_URL, size = AvatarSize.TimelineRoom)))
|
||||||
assertThat(initialState.userHasPermissionToSendMessage).isTrue()
|
assertThat(initialState.userEventPermissions.canSendMessage).isTrue()
|
||||||
assertThat(initialState.userHasPermissionToRedactOwn).isTrue()
|
assertThat(initialState.userEventPermissions.canRedactOwn).isTrue()
|
||||||
assertThat(initialState.hasNetworkConnection).isTrue()
|
assertThat(initialState.hasNetworkConnection).isTrue()
|
||||||
assertThat(initialState.snackbarMessage).isNull()
|
assertThat(initialState.snackbarMessage).isNull()
|
||||||
assertThat(initialState.inviteProgress).isEqualTo(AsyncData.Uninitialized)
|
assertThat(initialState.inviteProgress).isEqualTo(AsyncData.Uninitialized)
|
||||||
|
|
@ -155,7 +157,8 @@ class MessagesPresenterTest {
|
||||||
canRedactOtherResult = { Result.success(true) },
|
canRedactOtherResult = { Result.success(true) },
|
||||||
canUserJoinCallResult = { Result.success(true) },
|
canUserJoinCallResult = { Result.success(true) },
|
||||||
typingNoticeResult = { Result.success(Unit) },
|
typingNoticeResult = { Result.success(Unit) },
|
||||||
)
|
canUserPinUnpinResult = { Result.success(true) },
|
||||||
|
)
|
||||||
assertThat(room.markAsReadCalls).isEmpty()
|
assertThat(room.markAsReadCalls).isEmpty()
|
||||||
val presenter = createMessagesPresenter(matrixRoom = room)
|
val presenter = createMessagesPresenter(matrixRoom = room)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
|
|
@ -175,7 +178,8 @@ class MessagesPresenterTest {
|
||||||
canRedactOwnResult = { Result.success(true) },
|
canRedactOwnResult = { Result.success(true) },
|
||||||
canRedactOtherResult = { Result.success(true) },
|
canRedactOtherResult = { Result.success(true) },
|
||||||
typingNoticeResult = { Result.success(Unit) },
|
typingNoticeResult = { Result.success(Unit) },
|
||||||
).apply {
|
canUserPinUnpinResult = { Result.success(true) },
|
||||||
|
).apply {
|
||||||
givenRoomInfo(aRoomInfo(hasRoomCall = true))
|
givenRoomInfo(aRoomInfo(hasRoomCall = true))
|
||||||
}
|
}
|
||||||
val presenter = createMessagesPresenter(matrixRoom = room)
|
val presenter = createMessagesPresenter(matrixRoom = room)
|
||||||
|
|
@ -203,7 +207,8 @@ class MessagesPresenterTest {
|
||||||
canRedactOtherResult = { Result.success(true) },
|
canRedactOtherResult = { Result.success(true) },
|
||||||
canUserJoinCallResult = { Result.success(true) },
|
canUserJoinCallResult = { Result.success(true) },
|
||||||
typingNoticeResult = { Result.success(Unit) },
|
typingNoticeResult = { Result.success(Unit) },
|
||||||
)
|
canUserPinUnpinResult = { Result.success(true) },
|
||||||
|
)
|
||||||
val presenter = createMessagesPresenter(matrixRoom = room, coroutineDispatchers = coroutineDispatchers)
|
val presenter = createMessagesPresenter(matrixRoom = room, coroutineDispatchers = coroutineDispatchers)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
|
|
@ -240,7 +245,8 @@ class MessagesPresenterTest {
|
||||||
canRedactOtherResult = { Result.success(true) },
|
canRedactOtherResult = { Result.success(true) },
|
||||||
canUserJoinCallResult = { Result.success(true) },
|
canUserJoinCallResult = { Result.success(true) },
|
||||||
typingNoticeResult = { Result.success(Unit) },
|
typingNoticeResult = { Result.success(Unit) },
|
||||||
)
|
canUserPinUnpinResult = { Result.success(true) },
|
||||||
|
)
|
||||||
val presenter = createMessagesPresenter(matrixRoom = room, coroutineDispatchers = coroutineDispatchers)
|
val presenter = createMessagesPresenter(matrixRoom = room, coroutineDispatchers = coroutineDispatchers)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
|
|
@ -298,7 +304,8 @@ class MessagesPresenterTest {
|
||||||
canRedactOtherResult = { Result.success(true) },
|
canRedactOtherResult = { Result.success(true) },
|
||||||
canUserJoinCallResult = { Result.success(true) },
|
canUserJoinCallResult = { Result.success(true) },
|
||||||
typingNoticeResult = { Result.success(Unit) },
|
typingNoticeResult = { Result.success(Unit) },
|
||||||
)
|
canUserPinUnpinResult = { Result.success(true) },
|
||||||
|
)
|
||||||
val presenter = createMessagesPresenter(
|
val presenter = createMessagesPresenter(
|
||||||
clipboardHelper = clipboardHelper,
|
clipboardHelper = clipboardHelper,
|
||||||
matrixRoom = matrixRoom,
|
matrixRoom = matrixRoom,
|
||||||
|
|
@ -487,7 +494,8 @@ class MessagesPresenterTest {
|
||||||
canRedactOtherResult = { Result.success(true) },
|
canRedactOtherResult = { Result.success(true) },
|
||||||
canUserJoinCallResult = { Result.success(true) },
|
canUserJoinCallResult = { Result.success(true) },
|
||||||
typingNoticeResult = { Result.success(Unit) },
|
typingNoticeResult = { Result.success(Unit) },
|
||||||
)
|
canUserPinUnpinResult = { Result.success(true) },
|
||||||
|
)
|
||||||
|
|
||||||
val redactEventLambda = lambdaRecorder { _: EventId?, _: TransactionId?, _: String? -> Result.success(true) }
|
val redactEventLambda = lambdaRecorder { _: EventId?, _: TransactionId?, _: String? -> Result.success(true) }
|
||||||
liveTimeline.redactEventLambda = redactEventLambda
|
liveTimeline.redactEventLambda = redactEventLambda
|
||||||
|
|
@ -561,7 +569,8 @@ class MessagesPresenterTest {
|
||||||
canRedactOtherResult = { Result.success(true) },
|
canRedactOtherResult = { Result.success(true) },
|
||||||
canUserJoinCallResult = { Result.success(true) },
|
canUserJoinCallResult = { Result.success(true) },
|
||||||
typingNoticeResult = { Result.success(Unit) },
|
typingNoticeResult = { Result.success(Unit) },
|
||||||
)
|
canUserPinUnpinResult = { Result.success(true) },
|
||||||
|
)
|
||||||
val presenter = createMessagesPresenter(matrixRoom = room)
|
val presenter = createMessagesPresenter(matrixRoom = room)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
|
|
@ -596,7 +605,8 @@ class MessagesPresenterTest {
|
||||||
canRedactOtherResult = { Result.success(true) },
|
canRedactOtherResult = { Result.success(true) },
|
||||||
canUserJoinCallResult = { Result.success(true) },
|
canUserJoinCallResult = { Result.success(true) },
|
||||||
typingNoticeResult = { Result.success(Unit) },
|
typingNoticeResult = { Result.success(Unit) },
|
||||||
)
|
canUserPinUnpinResult = { Result.success(true) },
|
||||||
|
)
|
||||||
val presenter = createMessagesPresenter(matrixRoom = room)
|
val presenter = createMessagesPresenter(matrixRoom = room)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
|
|
@ -620,7 +630,8 @@ class MessagesPresenterTest {
|
||||||
canRedactOtherResult = { Result.success(true) },
|
canRedactOtherResult = { Result.success(true) },
|
||||||
canUserJoinCallResult = { Result.success(true) },
|
canUserJoinCallResult = { Result.success(true) },
|
||||||
typingNoticeResult = { Result.success(Unit) },
|
typingNoticeResult = { Result.success(Unit) },
|
||||||
)
|
canUserPinUnpinResult = { Result.success(true) },
|
||||||
|
)
|
||||||
val presenter = createMessagesPresenter(matrixRoom = room)
|
val presenter = createMessagesPresenter(matrixRoom = room)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
|
|
@ -644,7 +655,8 @@ class MessagesPresenterTest {
|
||||||
canRedactOtherResult = { Result.success(true) },
|
canRedactOtherResult = { Result.success(true) },
|
||||||
canUserJoinCallResult = { Result.success(true) },
|
canUserJoinCallResult = { Result.success(true) },
|
||||||
typingNoticeResult = { Result.success(Unit) },
|
typingNoticeResult = { Result.success(Unit) },
|
||||||
)
|
canUserPinUnpinResult = { Result.success(true) },
|
||||||
|
)
|
||||||
room.givenRoomMembersState(
|
room.givenRoomMembersState(
|
||||||
MatrixRoomMembersState.Ready(
|
MatrixRoomMembersState.Ready(
|
||||||
persistentListOf(
|
persistentListOf(
|
||||||
|
|
@ -679,7 +691,8 @@ class MessagesPresenterTest {
|
||||||
canRedactOtherResult = { Result.success(true) },
|
canRedactOtherResult = { Result.success(true) },
|
||||||
canUserJoinCallResult = { Result.success(true) },
|
canUserJoinCallResult = { Result.success(true) },
|
||||||
typingNoticeResult = { Result.success(Unit) },
|
typingNoticeResult = { Result.success(Unit) },
|
||||||
)
|
canUserPinUnpinResult = { Result.success(true) },
|
||||||
|
)
|
||||||
room.givenRoomMembersState(
|
room.givenRoomMembersState(
|
||||||
MatrixRoomMembersState.Error(
|
MatrixRoomMembersState.Error(
|
||||||
failure = Throwable(),
|
failure = Throwable(),
|
||||||
|
|
@ -715,7 +728,8 @@ class MessagesPresenterTest {
|
||||||
canRedactOtherResult = { Result.success(true) },
|
canRedactOtherResult = { Result.success(true) },
|
||||||
canUserJoinCallResult = { Result.success(true) },
|
canUserJoinCallResult = { Result.success(true) },
|
||||||
typingNoticeResult = { Result.success(Unit) },
|
typingNoticeResult = { Result.success(Unit) },
|
||||||
)
|
canUserPinUnpinResult = { Result.success(true) },
|
||||||
|
)
|
||||||
room.givenRoomMembersState(MatrixRoomMembersState.Unknown)
|
room.givenRoomMembersState(MatrixRoomMembersState.Unknown)
|
||||||
val presenter = createMessagesPresenter(matrixRoom = room)
|
val presenter = createMessagesPresenter(matrixRoom = room)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
|
|
@ -741,7 +755,8 @@ class MessagesPresenterTest {
|
||||||
canRedactOtherResult = { Result.success(true) },
|
canRedactOtherResult = { Result.success(true) },
|
||||||
canUserJoinCallResult = { Result.success(true) },
|
canUserJoinCallResult = { Result.success(true) },
|
||||||
typingNoticeResult = { Result.success(Unit) },
|
typingNoticeResult = { Result.success(Unit) },
|
||||||
)
|
canUserPinUnpinResult = { Result.success(true) },
|
||||||
|
)
|
||||||
room.givenRoomMembersState(
|
room.givenRoomMembersState(
|
||||||
MatrixRoomMembersState.Ready(
|
MatrixRoomMembersState.Ready(
|
||||||
persistentListOf(
|
persistentListOf(
|
||||||
|
|
@ -781,13 +796,14 @@ class MessagesPresenterTest {
|
||||||
canRedactOtherResult = { Result.success(true) },
|
canRedactOtherResult = { Result.success(true) },
|
||||||
canUserJoinCallResult = { Result.success(true) },
|
canUserJoinCallResult = { Result.success(true) },
|
||||||
typingNoticeResult = { Result.success(Unit) },
|
typingNoticeResult = { Result.success(Unit) },
|
||||||
)
|
canUserPinUnpinResult = { Result.success(true) },
|
||||||
|
)
|
||||||
val presenter = createMessagesPresenter(matrixRoom = matrixRoom)
|
val presenter = createMessagesPresenter(matrixRoom = matrixRoom)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
val state = awaitFirstItem()
|
val state = awaitFirstItem()
|
||||||
assertThat(state.userHasPermissionToSendMessage).isTrue()
|
assertThat(state.userEventPermissions.canSendMessage).isTrue()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -805,15 +821,16 @@ class MessagesPresenterTest {
|
||||||
canRedactOtherResult = { Result.success(true) },
|
canRedactOtherResult = { Result.success(true) },
|
||||||
canUserJoinCallResult = { Result.success(true) },
|
canUserJoinCallResult = { Result.success(true) },
|
||||||
typingNoticeResult = { Result.success(Unit) },
|
typingNoticeResult = { Result.success(Unit) },
|
||||||
)
|
canUserPinUnpinResult = { Result.success(true) },
|
||||||
|
)
|
||||||
val presenter = createMessagesPresenter(matrixRoom = matrixRoom)
|
val presenter = createMessagesPresenter(matrixRoom = matrixRoom)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
// Default value
|
// Default value
|
||||||
assertThat(awaitItem().userHasPermissionToSendMessage).isTrue()
|
assertThat(awaitItem().userEventPermissions.canSendMessage).isTrue()
|
||||||
skipItems(1)
|
skipItems(1)
|
||||||
assertThat(awaitItem().userHasPermissionToSendMessage).isFalse()
|
assertThat(awaitItem().userEventPermissions.canSendMessage).isFalse()
|
||||||
cancelAndIgnoreRemainingEvents()
|
cancelAndIgnoreRemainingEvents()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -826,14 +843,15 @@ class MessagesPresenterTest {
|
||||||
canRedactOtherResult = { Result.success(false) },
|
canRedactOtherResult = { Result.success(false) },
|
||||||
canUserJoinCallResult = { Result.success(true) },
|
canUserJoinCallResult = { Result.success(true) },
|
||||||
typingNoticeResult = { Result.success(Unit) },
|
typingNoticeResult = { Result.success(Unit) },
|
||||||
)
|
canUserPinUnpinResult = { Result.success(true) },
|
||||||
|
)
|
||||||
val presenter = createMessagesPresenter(matrixRoom = matrixRoom)
|
val presenter = createMessagesPresenter(matrixRoom = matrixRoom)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
val initialState = consumeItemsUntilPredicate { it.userHasPermissionToRedactOwn }.last()
|
val initialState = consumeItemsUntilPredicate { it.userEventPermissions.canRedactOwn }.last()
|
||||||
assertThat(initialState.userHasPermissionToRedactOwn).isTrue()
|
assertThat(initialState.userEventPermissions.canRedactOwn).isTrue()
|
||||||
assertThat(initialState.userHasPermissionToRedactOther).isFalse()
|
assertThat(initialState.userEventPermissions.canRedactOther).isFalse()
|
||||||
cancelAndIgnoreRemainingEvents()
|
cancelAndIgnoreRemainingEvents()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -846,14 +864,15 @@ class MessagesPresenterTest {
|
||||||
canRedactOwnResult = { Result.success(false) },
|
canRedactOwnResult = { Result.success(false) },
|
||||||
canUserJoinCallResult = { Result.success(true) },
|
canUserJoinCallResult = { Result.success(true) },
|
||||||
typingNoticeResult = { Result.success(Unit) },
|
typingNoticeResult = { Result.success(Unit) },
|
||||||
)
|
canUserPinUnpinResult = { Result.success(true) },
|
||||||
|
)
|
||||||
val presenter = createMessagesPresenter(matrixRoom = matrixRoom)
|
val presenter = createMessagesPresenter(matrixRoom = matrixRoom)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
val initialState = consumeItemsUntilPredicate { it.userHasPermissionToRedactOther }.last()
|
val initialState = consumeItemsUntilPredicate { it.userEventPermissions.canRedactOther }.last()
|
||||||
assertThat(initialState.userHasPermissionToRedactOwn).isFalse()
|
assertThat(initialState.userEventPermissions.canRedactOwn).isFalse()
|
||||||
assertThat(initialState.userHasPermissionToRedactOther).isTrue()
|
assertThat(initialState.userEventPermissions.canRedactOther).isTrue()
|
||||||
cancelAndIgnoreRemainingEvents()
|
cancelAndIgnoreRemainingEvents()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -878,6 +897,74 @@ class MessagesPresenterTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - handle action pin`() = runTest {
|
||||||
|
val successPinEventLambda = lambdaRecorder { _: EventId -> Result.success(true) }
|
||||||
|
val failurePinEventLambda = lambdaRecorder { _: EventId -> Result.failure<Boolean>(A_THROWABLE) }
|
||||||
|
val timeline = FakeTimeline()
|
||||||
|
val room = FakeMatrixRoom(
|
||||||
|
liveTimeline = timeline,
|
||||||
|
canUserSendMessageResult = { _, _ -> Result.success(true) },
|
||||||
|
canRedactOwnResult = { Result.success(true) },
|
||||||
|
canRedactOtherResult = { Result.success(true) },
|
||||||
|
canUserJoinCallResult = { Result.success(true) },
|
||||||
|
typingNoticeResult = { Result.success(Unit) },
|
||||||
|
canUserPinUnpinResult = { Result.success(true) },
|
||||||
|
)
|
||||||
|
val presenter = createMessagesPresenter(matrixRoom = room)
|
||||||
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
|
presenter.present()
|
||||||
|
}.test {
|
||||||
|
val messageEvent = aMessageEvent(
|
||||||
|
content = aTimelineItemTextContent()
|
||||||
|
)
|
||||||
|
val initialState = awaitFirstItem()
|
||||||
|
|
||||||
|
timeline.pinEventLambda = successPinEventLambda
|
||||||
|
initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.Pin, messageEvent))
|
||||||
|
assert(successPinEventLambda).isCalledOnce().with(value(messageEvent.eventId))
|
||||||
|
|
||||||
|
timeline.pinEventLambda = failurePinEventLambda
|
||||||
|
initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.Pin, messageEvent))
|
||||||
|
assert(failurePinEventLambda).isCalledOnce().with(value(messageEvent.eventId))
|
||||||
|
assertThat(awaitItem().snackbarMessage).isNotNull()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - handle action unpin`() = runTest {
|
||||||
|
val successUnpinEventLambda = lambdaRecorder { _: EventId -> Result.success(true) }
|
||||||
|
val failureUnpinEventLambda = lambdaRecorder { _: EventId -> Result.failure<Boolean>(A_THROWABLE) }
|
||||||
|
val timeline = FakeTimeline()
|
||||||
|
val room = FakeMatrixRoom(
|
||||||
|
liveTimeline = timeline,
|
||||||
|
canUserSendMessageResult = { _, _ -> Result.success(true) },
|
||||||
|
canRedactOwnResult = { Result.success(true) },
|
||||||
|
canRedactOtherResult = { Result.success(true) },
|
||||||
|
canUserJoinCallResult = { Result.success(true) },
|
||||||
|
typingNoticeResult = { Result.success(Unit) },
|
||||||
|
canUserPinUnpinResult = { Result.success(true) },
|
||||||
|
)
|
||||||
|
val presenter = createMessagesPresenter(matrixRoom = room)
|
||||||
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
|
presenter.present()
|
||||||
|
}.test {
|
||||||
|
val messageEvent = aMessageEvent(
|
||||||
|
content = aTimelineItemTextContent()
|
||||||
|
)
|
||||||
|
val initialState = awaitFirstItem()
|
||||||
|
|
||||||
|
timeline.unpinEventLambda = successUnpinEventLambda
|
||||||
|
initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.Unpin, messageEvent))
|
||||||
|
assert(successUnpinEventLambda).isCalledOnce().with(value(messageEvent.eventId))
|
||||||
|
|
||||||
|
timeline.unpinEventLambda = failureUnpinEventLambda
|
||||||
|
initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.Unpin, messageEvent))
|
||||||
|
assert(failureUnpinEventLambda).isCalledOnce().with(value(messageEvent.eventId))
|
||||||
|
assertThat(awaitItem().snackbarMessage).isNotNull()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun <T> ReceiveTurbine<T>.awaitFirstItem(): T {
|
private suspend fun <T> ReceiveTurbine<T>.awaitFirstItem(): T {
|
||||||
// Skip 2 item if Mentions feature is enabled, else 1
|
// Skip 2 item if Mentions feature is enabled, else 1
|
||||||
skipItems(if (FeatureFlags.Mentions.defaultValue(aBuildMeta())) 2 else 1)
|
skipItems(if (FeatureFlags.Mentions.defaultValue(aBuildMeta())) 2 else 1)
|
||||||
|
|
@ -892,6 +979,7 @@ class MessagesPresenterTest {
|
||||||
canRedactOtherResult = { Result.success(true) },
|
canRedactOtherResult = { Result.success(true) },
|
||||||
canUserJoinCallResult = { Result.success(true) },
|
canUserJoinCallResult = { Result.success(true) },
|
||||||
typingNoticeResult = { Result.success(Unit) },
|
typingNoticeResult = { Result.success(Unit) },
|
||||||
|
canUserPinUnpinResult = { Result.success(true) },
|
||||||
).apply {
|
).apply {
|
||||||
givenRoomInfo(aRoomInfo(id = roomId, name = ""))
|
givenRoomInfo(aRoomInfo(id = roomId, name = ""))
|
||||||
},
|
},
|
||||||
|
|
@ -958,11 +1046,17 @@ class MessagesPresenterTest {
|
||||||
return timelinePresenter
|
return timelinePresenter
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val actionListPresenter = ActionListPresenter(appPreferencesStore = appPreferencesStore)
|
val featureFlagService = FakeFeatureFlagService()
|
||||||
|
val actionListPresenter = ActionListPresenter(
|
||||||
|
appPreferencesStore = appPreferencesStore,
|
||||||
|
featureFlagsService = featureFlagService,
|
||||||
|
room = matrixRoom,
|
||||||
|
)
|
||||||
val typingNotificationPresenter = TypingNotificationPresenter(
|
val typingNotificationPresenter = TypingNotificationPresenter(
|
||||||
room = matrixRoom,
|
room = matrixRoom,
|
||||||
sessionPreferencesStore = sessionPreferencesStore,
|
sessionPreferencesStore = sessionPreferencesStore,
|
||||||
)
|
)
|
||||||
|
|
||||||
val readReceiptBottomSheetPresenter = ReadReceiptBottomSheetPresenter()
|
val readReceiptBottomSheetPresenter = ReadReceiptBottomSheetPresenter()
|
||||||
val customReactionPresenter = CustomReactionPresenter(emojibaseProvider = FakeEmojibaseProvider())
|
val customReactionPresenter = CustomReactionPresenter(emojibaseProvider = FakeEmojibaseProvider())
|
||||||
val reactionSummaryPresenter = ReactionSummaryPresenter(room = matrixRoom)
|
val reactionSummaryPresenter = ReactionSummaryPresenter(room = matrixRoom)
|
||||||
|
|
@ -980,7 +1074,7 @@ class MessagesPresenterTest {
|
||||||
snackbarDispatcher = SnackbarDispatcher(),
|
snackbarDispatcher = SnackbarDispatcher(),
|
||||||
navigator = navigator,
|
navigator = navigator,
|
||||||
clipboardHelper = clipboardHelper,
|
clipboardHelper = clipboardHelper,
|
||||||
featureFlagsService = FakeFeatureFlagService(),
|
featureFlagsService = featureFlagService,
|
||||||
buildMeta = aBuildMeta(),
|
buildMeta = aBuildMeta(),
|
||||||
dispatchers = coroutineDispatchers,
|
dispatchers = coroutineDispatchers,
|
||||||
htmlConverterProvider = FakeHtmlConverterProvider(),
|
htmlConverterProvider = FakeHtmlConverterProvider(),
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ import androidx.compose.ui.test.onAllNodesWithContentDescription
|
||||||
import androidx.compose.ui.test.onAllNodesWithTag
|
import androidx.compose.ui.test.onAllNodesWithTag
|
||||||
import androidx.compose.ui.test.onAllNodesWithText
|
import androidx.compose.ui.test.onAllNodesWithText
|
||||||
import androidx.compose.ui.test.onFirst
|
import androidx.compose.ui.test.onFirst
|
||||||
|
import androidx.compose.ui.test.onLast
|
||||||
import androidx.compose.ui.test.onNodeWithContentDescription
|
import androidx.compose.ui.test.onNodeWithContentDescription
|
||||||
import androidx.compose.ui.test.onNodeWithTag
|
import androidx.compose.ui.test.onNodeWithTag
|
||||||
import androidx.compose.ui.test.onNodeWithText
|
import androidx.compose.ui.test.onNodeWithText
|
||||||
|
|
@ -43,7 +44,6 @@ import io.element.android.features.messages.impl.actionlist.model.TimelineItemAc
|
||||||
import io.element.android.features.messages.impl.attachments.Attachment
|
import io.element.android.features.messages.impl.attachments.Attachment
|
||||||
import io.element.android.features.messages.impl.messagecomposer.aMessageComposerState
|
import io.element.android.features.messages.impl.messagecomposer.aMessageComposerState
|
||||||
import io.element.android.features.messages.impl.timeline.aTimelineItemEvent
|
import io.element.android.features.messages.impl.timeline.aTimelineItemEvent
|
||||||
import io.element.android.features.messages.impl.timeline.aTimelineItemList
|
|
||||||
import io.element.android.features.messages.impl.timeline.aTimelineItemReadReceipts
|
import io.element.android.features.messages.impl.timeline.aTimelineItemReadReceipts
|
||||||
import io.element.android.features.messages.impl.timeline.aTimelineRoomInfo
|
import io.element.android.features.messages.impl.timeline.aTimelineRoomInfo
|
||||||
import io.element.android.features.messages.impl.timeline.aTimelineState
|
import io.element.android.features.messages.impl.timeline.aTimelineState
|
||||||
|
|
@ -53,7 +53,6 @@ import io.element.android.features.messages.impl.timeline.components.reactionsum
|
||||||
import io.element.android.features.messages.impl.timeline.components.receipt.aReadReceiptData
|
import io.element.android.features.messages.impl.timeline.components.receipt.aReadReceiptData
|
||||||
import io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet.ReadReceiptBottomSheetEvents
|
import io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet.ReadReceiptBottomSheetEvents
|
||||||
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemTextContent
|
|
||||||
import io.element.android.libraries.matrix.api.core.UserId
|
import io.element.android.libraries.matrix.api.core.UserId
|
||||||
import io.element.android.libraries.testtags.TestTags
|
import io.element.android.libraries.testtags.TestTags
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
|
|
@ -169,16 +168,20 @@ class MessagesViewTest {
|
||||||
userHasPermissionToRedactOwn: Boolean = false,
|
userHasPermissionToRedactOwn: Boolean = false,
|
||||||
userHasPermissionToRedactOther: Boolean = false,
|
userHasPermissionToRedactOther: Boolean = false,
|
||||||
userHasPermissionToSendReaction: Boolean = false,
|
userHasPermissionToSendReaction: Boolean = false,
|
||||||
|
userCanPinEvent: Boolean = false,
|
||||||
) {
|
) {
|
||||||
val eventsRecorder = EventsRecorder<ActionListEvents>()
|
val eventsRecorder = EventsRecorder<ActionListEvents>()
|
||||||
val state = aMessagesState(
|
val state = aMessagesState(
|
||||||
actionListState = anActionListState(
|
actionListState = anActionListState(
|
||||||
eventSink = eventsRecorder
|
eventSink = eventsRecorder
|
||||||
),
|
),
|
||||||
userHasPermissionToSendMessage = userHasPermissionToSendMessage,
|
userEventPermissions = UserEventPermissions(
|
||||||
userHasPermissionToRedactOwn = userHasPermissionToRedactOwn,
|
canSendMessage = userHasPermissionToSendMessage,
|
||||||
userHasPermissionToRedactOther = userHasPermissionToRedactOther,
|
canRedactOwn = userHasPermissionToRedactOwn,
|
||||||
userHasPermissionToSendReaction = userHasPermissionToSendReaction,
|
canRedactOther = userHasPermissionToRedactOther,
|
||||||
|
canSendReaction = userHasPermissionToSendReaction,
|
||||||
|
canPinUnpin = userCanPinEvent,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
val timelineItem = state.timelineState.timelineItems.first() as TimelineItem.Event
|
val timelineItem = state.timelineState.timelineItems.first() as TimelineItem.Event
|
||||||
rule.setMessagesView(
|
rule.setMessagesView(
|
||||||
|
|
@ -189,10 +192,7 @@ class MessagesViewTest {
|
||||||
eventsRecorder.assertSingle(
|
eventsRecorder.assertSingle(
|
||||||
ActionListEvents.ComputeForMessage(
|
ActionListEvents.ComputeForMessage(
|
||||||
event = timelineItem,
|
event = timelineItem,
|
||||||
canRedactOwn = state.userHasPermissionToRedactOwn,
|
userEventPermissions = state.userEventPermissions,
|
||||||
canRedactOther = state.userHasPermissionToRedactOther,
|
|
||||||
canSendMessage = state.userHasPermissionToSendMessage,
|
|
||||||
canSendReaction = state.userHasPermissionToSendReaction,
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -237,9 +237,11 @@ class MessagesViewTest {
|
||||||
|
|
||||||
private fun swipeTest(userHasPermissionToSendMessage: Boolean) {
|
private fun swipeTest(userHasPermissionToSendMessage: Boolean) {
|
||||||
val eventsRecorder = EventsRecorder<MessagesEvents>()
|
val eventsRecorder = EventsRecorder<MessagesEvents>()
|
||||||
|
val canBeRepliedEvent = aTimelineItemEvent(canBeRepliedTo = true)
|
||||||
|
val cannotBeRepliedEvent = aTimelineItemEvent(canBeRepliedTo = false)
|
||||||
val state = aMessagesState(
|
val state = aMessagesState(
|
||||||
timelineState = aTimelineState(
|
timelineState = aTimelineState(
|
||||||
timelineItems = aTimelineItemList(aTimelineItemTextContent()),
|
timelineItems = persistentListOf(canBeRepliedEvent, cannotBeRepliedEvent),
|
||||||
timelineRoomInfo = aTimelineRoomInfo(
|
timelineRoomInfo = aTimelineRoomInfo(
|
||||||
userHasPermissionToSendMessage = userHasPermissionToSendMessage
|
userHasPermissionToSendMessage = userHasPermissionToSendMessage
|
||||||
),
|
),
|
||||||
|
|
@ -249,10 +251,12 @@ class MessagesViewTest {
|
||||||
rule.setMessagesView(
|
rule.setMessagesView(
|
||||||
state = state,
|
state = state,
|
||||||
)
|
)
|
||||||
rule.onAllNodesWithTag(TestTags.messageBubble.value).onFirst().performTouchInput { swipeRight(endX = 200f) }
|
rule.onAllNodesWithTag(TestTags.messageBubble.value).apply {
|
||||||
|
onFirst().performTouchInput { swipeRight(endX = 200f) }
|
||||||
|
onLast().performTouchInput { swipeRight(endX = 200f) }
|
||||||
|
}
|
||||||
if (userHasPermissionToSendMessage) {
|
if (userHasPermissionToSendMessage) {
|
||||||
val timelineItem = state.timelineState.timelineItems.first() as TimelineItem.Event
|
eventsRecorder.assertSingle(MessagesEvents.HandleAction(TimelineItemAction.Reply, canBeRepliedEvent))
|
||||||
eventsRecorder.assertSingle(MessagesEvents.HandleAction(TimelineItemAction.Reply, timelineItem))
|
|
||||||
} else {
|
} else {
|
||||||
eventsRecorder.assertEmpty()
|
eventsRecorder.assertEmpty()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import app.cash.molecule.RecompositionMode
|
||||||
import app.cash.molecule.moleculeFlow
|
import app.cash.molecule.moleculeFlow
|
||||||
import app.cash.turbine.test
|
import app.cash.turbine.test
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import io.element.android.features.messages.impl.aUserEventPermissions
|
||||||
import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction
|
import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction
|
||||||
import io.element.android.features.messages.impl.fixtures.aMessageEvent
|
import io.element.android.features.messages.impl.fixtures.aMessageEvent
|
||||||
import io.element.android.features.messages.impl.timeline.aTimelineItemEvent
|
import io.element.android.features.messages.impl.timeline.aTimelineItemEvent
|
||||||
|
|
@ -31,7 +32,13 @@ import io.element.android.features.messages.impl.timeline.model.event.aTimelineI
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemStateEventContent
|
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemStateEventContent
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemVoiceContent
|
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemVoiceContent
|
||||||
import io.element.android.features.poll.api.pollcontent.aPollAnswerItemList
|
import io.element.android.features.poll.api.pollcontent.aPollAnswerItemList
|
||||||
|
import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||||
|
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
|
||||||
|
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||||
|
import io.element.android.libraries.matrix.test.AN_EVENT_ID
|
||||||
import io.element.android.libraries.matrix.test.A_MESSAGE
|
import io.element.android.libraries.matrix.test.A_MESSAGE
|
||||||
|
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
|
||||||
|
import io.element.android.libraries.matrix.test.room.aRoomInfo
|
||||||
import io.element.android.libraries.preferences.test.InMemoryAppPreferencesStore
|
import io.element.android.libraries.preferences.test.InMemoryAppPreferencesStore
|
||||||
import io.element.android.tests.testutils.WarmUpRule
|
import io.element.android.tests.testutils.WarmUpRule
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
|
@ -46,7 +53,7 @@ class ActionListPresenterTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - initial state`() = runTest {
|
fun `present - initial state`() = runTest {
|
||||||
val presenter = createActionListPresenter(isDeveloperModeEnabled = true)
|
val presenter = createActionListPresenter(isDeveloperModeEnabled = true, isPinFeatureEnabled = true)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
|
|
@ -57,7 +64,7 @@ class ActionListPresenterTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - compute for message from me redacted`() = runTest {
|
fun `present - compute for message from me redacted`() = runTest {
|
||||||
val presenter = createActionListPresenter(isDeveloperModeEnabled = true)
|
val presenter = createActionListPresenter(isDeveloperModeEnabled = true, isPinFeatureEnabled = true)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
|
|
@ -66,10 +73,13 @@ class ActionListPresenterTest {
|
||||||
initialState.eventSink.invoke(
|
initialState.eventSink.invoke(
|
||||||
ActionListEvents.ComputeForMessage(
|
ActionListEvents.ComputeForMessage(
|
||||||
event = messageEvent,
|
event = messageEvent,
|
||||||
canRedactOwn = false,
|
userEventPermissions = aUserEventPermissions(
|
||||||
canRedactOther = false,
|
canRedactOwn = false,
|
||||||
canSendMessage = true,
|
canRedactOther = false,
|
||||||
canSendReaction = true,
|
canSendMessage = true,
|
||||||
|
canSendReaction = true,
|
||||||
|
canPinUnpin = true,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
// val loadingState = awaitItem()
|
// val loadingState = awaitItem()
|
||||||
|
|
@ -91,7 +101,7 @@ class ActionListPresenterTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - compute for message from others redacted`() = runTest {
|
fun `present - compute for message from others redacted`() = runTest {
|
||||||
val presenter = createActionListPresenter(isDeveloperModeEnabled = true)
|
val presenter = createActionListPresenter(isDeveloperModeEnabled = true, isPinFeatureEnabled = true)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
|
|
@ -104,10 +114,13 @@ class ActionListPresenterTest {
|
||||||
initialState.eventSink.invoke(
|
initialState.eventSink.invoke(
|
||||||
ActionListEvents.ComputeForMessage(
|
ActionListEvents.ComputeForMessage(
|
||||||
event = messageEvent,
|
event = messageEvent,
|
||||||
canRedactOwn = false,
|
userEventPermissions = aUserEventPermissions(
|
||||||
canRedactOther = false,
|
canRedactOwn = false,
|
||||||
canSendMessage = true,
|
canRedactOther = false,
|
||||||
canSendReaction = true,
|
canSendMessage = true,
|
||||||
|
canSendReaction = true,
|
||||||
|
canPinUnpin = true,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
// val loadingState = awaitItem()
|
// val loadingState = awaitItem()
|
||||||
|
|
@ -129,7 +142,7 @@ class ActionListPresenterTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - compute for others message`() = runTest {
|
fun `present - compute for others message`() = runTest {
|
||||||
val presenter = createActionListPresenter(isDeveloperModeEnabled = true)
|
val presenter = createActionListPresenter(isDeveloperModeEnabled = true, isPinFeatureEnabled = true)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
|
|
@ -142,10 +155,13 @@ class ActionListPresenterTest {
|
||||||
initialState.eventSink.invoke(
|
initialState.eventSink.invoke(
|
||||||
ActionListEvents.ComputeForMessage(
|
ActionListEvents.ComputeForMessage(
|
||||||
event = messageEvent,
|
event = messageEvent,
|
||||||
canRedactOwn = false,
|
userEventPermissions = aUserEventPermissions(
|
||||||
canRedactOther = false,
|
canRedactOwn = false,
|
||||||
canSendMessage = true,
|
canRedactOther = false,
|
||||||
canSendReaction = true,
|
canSendMessage = true,
|
||||||
|
canSendReaction = true,
|
||||||
|
canPinUnpin = true,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
// val loadingState = awaitItem()
|
// val loadingState = awaitItem()
|
||||||
|
|
@ -158,6 +174,7 @@ class ActionListPresenterTest {
|
||||||
actions = persistentListOf(
|
actions = persistentListOf(
|
||||||
TimelineItemAction.Reply,
|
TimelineItemAction.Reply,
|
||||||
TimelineItemAction.Forward,
|
TimelineItemAction.Forward,
|
||||||
|
TimelineItemAction.Pin,
|
||||||
TimelineItemAction.Copy,
|
TimelineItemAction.Copy,
|
||||||
TimelineItemAction.CopyLink,
|
TimelineItemAction.CopyLink,
|
||||||
TimelineItemAction.ViewSource,
|
TimelineItemAction.ViewSource,
|
||||||
|
|
@ -172,7 +189,7 @@ class ActionListPresenterTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - compute for others message cannot sent message`() = runTest {
|
fun `present - compute for others message cannot sent message`() = runTest {
|
||||||
val presenter = createActionListPresenter(isDeveloperModeEnabled = true)
|
val presenter = createActionListPresenter(isDeveloperModeEnabled = true, isPinFeatureEnabled = true)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
|
|
@ -185,10 +202,13 @@ class ActionListPresenterTest {
|
||||||
initialState.eventSink.invoke(
|
initialState.eventSink.invoke(
|
||||||
ActionListEvents.ComputeForMessage(
|
ActionListEvents.ComputeForMessage(
|
||||||
event = messageEvent,
|
event = messageEvent,
|
||||||
canRedactOwn = true,
|
userEventPermissions = aUserEventPermissions(
|
||||||
canRedactOther = false,
|
canRedactOwn = true,
|
||||||
canSendMessage = false,
|
canRedactOther = false,
|
||||||
canSendReaction = true
|
canSendMessage = false,
|
||||||
|
canSendReaction = true,
|
||||||
|
canPinUnpin = true,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
// val loadingState = awaitItem()
|
// val loadingState = awaitItem()
|
||||||
|
|
@ -200,6 +220,7 @@ class ActionListPresenterTest {
|
||||||
displayEmojiReactions = true,
|
displayEmojiReactions = true,
|
||||||
actions = persistentListOf(
|
actions = persistentListOf(
|
||||||
TimelineItemAction.Forward,
|
TimelineItemAction.Forward,
|
||||||
|
TimelineItemAction.Pin,
|
||||||
TimelineItemAction.Copy,
|
TimelineItemAction.Copy,
|
||||||
TimelineItemAction.CopyLink,
|
TimelineItemAction.CopyLink,
|
||||||
TimelineItemAction.ViewSource,
|
TimelineItemAction.ViewSource,
|
||||||
|
|
@ -214,7 +235,7 @@ class ActionListPresenterTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - compute for others message and can redact`() = runTest {
|
fun `present - compute for others message and can redact`() = runTest {
|
||||||
val presenter = createActionListPresenter(isDeveloperModeEnabled = true)
|
val presenter = createActionListPresenter(isDeveloperModeEnabled = true, isPinFeatureEnabled = true)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
|
|
@ -227,10 +248,13 @@ class ActionListPresenterTest {
|
||||||
initialState.eventSink.invoke(
|
initialState.eventSink.invoke(
|
||||||
ActionListEvents.ComputeForMessage(
|
ActionListEvents.ComputeForMessage(
|
||||||
event = messageEvent,
|
event = messageEvent,
|
||||||
canRedactOwn = false,
|
userEventPermissions = aUserEventPermissions(
|
||||||
canRedactOther = true,
|
canRedactOwn = false,
|
||||||
canSendMessage = true,
|
canRedactOther = true,
|
||||||
canSendReaction = true,
|
canSendMessage = true,
|
||||||
|
canSendReaction = true,
|
||||||
|
canPinUnpin = true,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
val successState = awaitItem()
|
val successState = awaitItem()
|
||||||
|
|
@ -241,6 +265,7 @@ class ActionListPresenterTest {
|
||||||
actions = persistentListOf(
|
actions = persistentListOf(
|
||||||
TimelineItemAction.Reply,
|
TimelineItemAction.Reply,
|
||||||
TimelineItemAction.Forward,
|
TimelineItemAction.Forward,
|
||||||
|
TimelineItemAction.Pin,
|
||||||
TimelineItemAction.Copy,
|
TimelineItemAction.Copy,
|
||||||
TimelineItemAction.CopyLink,
|
TimelineItemAction.CopyLink,
|
||||||
TimelineItemAction.ViewSource,
|
TimelineItemAction.ViewSource,
|
||||||
|
|
@ -256,7 +281,7 @@ class ActionListPresenterTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - compute for others message and cannot send reaction`() = runTest {
|
fun `present - compute for others message and cannot send reaction`() = runTest {
|
||||||
val presenter = createActionListPresenter(isDeveloperModeEnabled = true)
|
val presenter = createActionListPresenter(isDeveloperModeEnabled = true, isPinFeatureEnabled = true)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
|
|
@ -269,10 +294,13 @@ class ActionListPresenterTest {
|
||||||
initialState.eventSink.invoke(
|
initialState.eventSink.invoke(
|
||||||
ActionListEvents.ComputeForMessage(
|
ActionListEvents.ComputeForMessage(
|
||||||
event = messageEvent,
|
event = messageEvent,
|
||||||
canRedactOwn = false,
|
userEventPermissions = aUserEventPermissions(
|
||||||
canRedactOther = true,
|
canRedactOwn = false,
|
||||||
canSendMessage = true,
|
canRedactOther = true,
|
||||||
canSendReaction = false
|
canSendMessage = true,
|
||||||
|
canSendReaction = false,
|
||||||
|
canPinUnpin = true,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
val successState = awaitItem()
|
val successState = awaitItem()
|
||||||
|
|
@ -283,6 +311,7 @@ class ActionListPresenterTest {
|
||||||
actions = persistentListOf(
|
actions = persistentListOf(
|
||||||
TimelineItemAction.Reply,
|
TimelineItemAction.Reply,
|
||||||
TimelineItemAction.Forward,
|
TimelineItemAction.Forward,
|
||||||
|
TimelineItemAction.Pin,
|
||||||
TimelineItemAction.Copy,
|
TimelineItemAction.Copy,
|
||||||
TimelineItemAction.CopyLink,
|
TimelineItemAction.CopyLink,
|
||||||
TimelineItemAction.ViewSource,
|
TimelineItemAction.ViewSource,
|
||||||
|
|
@ -298,7 +327,7 @@ class ActionListPresenterTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - compute for my message`() = runTest {
|
fun `present - compute for my message`() = runTest {
|
||||||
val presenter = createActionListPresenter(isDeveloperModeEnabled = true)
|
val presenter = createActionListPresenter(isDeveloperModeEnabled = true, isPinFeatureEnabled = true)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
|
|
@ -310,10 +339,13 @@ class ActionListPresenterTest {
|
||||||
initialState.eventSink.invoke(
|
initialState.eventSink.invoke(
|
||||||
ActionListEvents.ComputeForMessage(
|
ActionListEvents.ComputeForMessage(
|
||||||
event = messageEvent,
|
event = messageEvent,
|
||||||
canRedactOwn = true,
|
userEventPermissions = aUserEventPermissions(
|
||||||
canRedactOther = false,
|
canRedactOwn = true,
|
||||||
canSendMessage = true,
|
canRedactOther = false,
|
||||||
canSendReaction = true,
|
canSendMessage = true,
|
||||||
|
canSendReaction = true,
|
||||||
|
canPinUnpin = true,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
// val loadingState = awaitItem()
|
// val loadingState = awaitItem()
|
||||||
|
|
@ -327,6 +359,7 @@ class ActionListPresenterTest {
|
||||||
TimelineItemAction.Reply,
|
TimelineItemAction.Reply,
|
||||||
TimelineItemAction.Forward,
|
TimelineItemAction.Forward,
|
||||||
TimelineItemAction.Edit,
|
TimelineItemAction.Edit,
|
||||||
|
TimelineItemAction.Pin,
|
||||||
TimelineItemAction.Copy,
|
TimelineItemAction.Copy,
|
||||||
TimelineItemAction.CopyLink,
|
TimelineItemAction.CopyLink,
|
||||||
TimelineItemAction.ViewSource,
|
TimelineItemAction.ViewSource,
|
||||||
|
|
@ -341,7 +374,7 @@ class ActionListPresenterTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - compute for my message cannot redact`() = runTest {
|
fun `present - compute for my message cannot redact`() = runTest {
|
||||||
val presenter = createActionListPresenter(isDeveloperModeEnabled = true)
|
val presenter = createActionListPresenter(isDeveloperModeEnabled = true, isPinFeatureEnabled = true)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
|
|
@ -353,10 +386,13 @@ class ActionListPresenterTest {
|
||||||
initialState.eventSink.invoke(
|
initialState.eventSink.invoke(
|
||||||
ActionListEvents.ComputeForMessage(
|
ActionListEvents.ComputeForMessage(
|
||||||
event = messageEvent,
|
event = messageEvent,
|
||||||
canRedactOwn = false,
|
userEventPermissions = aUserEventPermissions(
|
||||||
canRedactOther = false,
|
canRedactOwn = false,
|
||||||
canSendMessage = true,
|
canRedactOther = false,
|
||||||
canSendReaction = true,
|
canSendMessage = true,
|
||||||
|
canSendReaction = true,
|
||||||
|
canPinUnpin = true,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
// val loadingState = awaitItem()
|
// val loadingState = awaitItem()
|
||||||
|
|
@ -370,6 +406,7 @@ class ActionListPresenterTest {
|
||||||
TimelineItemAction.Reply,
|
TimelineItemAction.Reply,
|
||||||
TimelineItemAction.Forward,
|
TimelineItemAction.Forward,
|
||||||
TimelineItemAction.Edit,
|
TimelineItemAction.Edit,
|
||||||
|
TimelineItemAction.Pin,
|
||||||
TimelineItemAction.Copy,
|
TimelineItemAction.Copy,
|
||||||
TimelineItemAction.CopyLink,
|
TimelineItemAction.CopyLink,
|
||||||
TimelineItemAction.ViewSource,
|
TimelineItemAction.ViewSource,
|
||||||
|
|
@ -383,7 +420,7 @@ class ActionListPresenterTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - compute for a media item`() = runTest {
|
fun `present - compute for a media item`() = runTest {
|
||||||
val presenter = createActionListPresenter(isDeveloperModeEnabled = true)
|
val presenter = createActionListPresenter(isDeveloperModeEnabled = true, isPinFeatureEnabled = true)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
|
|
@ -396,10 +433,13 @@ class ActionListPresenterTest {
|
||||||
initialState.eventSink.invoke(
|
initialState.eventSink.invoke(
|
||||||
ActionListEvents.ComputeForMessage(
|
ActionListEvents.ComputeForMessage(
|
||||||
event = messageEvent,
|
event = messageEvent,
|
||||||
canRedactOwn = true,
|
userEventPermissions = aUserEventPermissions(
|
||||||
canRedactOther = false,
|
canRedactOwn = true,
|
||||||
canSendMessage = true,
|
canRedactOther = false,
|
||||||
canSendReaction = true,
|
canSendMessage = true,
|
||||||
|
canSendReaction = true,
|
||||||
|
canPinUnpin = true,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
// val loadingState = awaitItem()
|
// val loadingState = awaitItem()
|
||||||
|
|
@ -412,6 +452,7 @@ class ActionListPresenterTest {
|
||||||
actions = persistentListOf(
|
actions = persistentListOf(
|
||||||
TimelineItemAction.Reply,
|
TimelineItemAction.Reply,
|
||||||
TimelineItemAction.Forward,
|
TimelineItemAction.Forward,
|
||||||
|
TimelineItemAction.Pin,
|
||||||
TimelineItemAction.CopyLink,
|
TimelineItemAction.CopyLink,
|
||||||
TimelineItemAction.ViewSource,
|
TimelineItemAction.ViewSource,
|
||||||
TimelineItemAction.Redact,
|
TimelineItemAction.Redact,
|
||||||
|
|
@ -425,7 +466,7 @@ class ActionListPresenterTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - compute for a state item in debug build`() = runTest {
|
fun `present - compute for a state item in debug build`() = runTest {
|
||||||
val presenter = createActionListPresenter(isDeveloperModeEnabled = true)
|
val presenter = createActionListPresenter(isDeveloperModeEnabled = true, isPinFeatureEnabled = true)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
|
|
@ -437,10 +478,13 @@ class ActionListPresenterTest {
|
||||||
initialState.eventSink.invoke(
|
initialState.eventSink.invoke(
|
||||||
ActionListEvents.ComputeForMessage(
|
ActionListEvents.ComputeForMessage(
|
||||||
event = stateEvent,
|
event = stateEvent,
|
||||||
canRedactOwn = false,
|
userEventPermissions = aUserEventPermissions(
|
||||||
canRedactOther = false,
|
canRedactOwn = false,
|
||||||
canSendMessage = true,
|
canRedactOther = false,
|
||||||
canSendReaction = true,
|
canSendMessage = true,
|
||||||
|
canSendReaction = true,
|
||||||
|
canPinUnpin = true,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
// val loadingState = awaitItem()
|
// val loadingState = awaitItem()
|
||||||
|
|
@ -451,8 +495,6 @@ class ActionListPresenterTest {
|
||||||
event = stateEvent,
|
event = stateEvent,
|
||||||
displayEmojiReactions = false,
|
displayEmojiReactions = false,
|
||||||
actions = persistentListOf(
|
actions = persistentListOf(
|
||||||
TimelineItemAction.Copy,
|
|
||||||
TimelineItemAction.CopyLink,
|
|
||||||
TimelineItemAction.ViewSource,
|
TimelineItemAction.ViewSource,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -464,7 +506,7 @@ class ActionListPresenterTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - compute for a state item in non-debuggable build`() = runTest {
|
fun `present - compute for a state item in non-debuggable build`() = runTest {
|
||||||
val presenter = createActionListPresenter(isDeveloperModeEnabled = false)
|
val presenter = createActionListPresenter(isDeveloperModeEnabled = false, isPinFeatureEnabled = true)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
|
|
@ -476,33 +518,24 @@ class ActionListPresenterTest {
|
||||||
initialState.eventSink.invoke(
|
initialState.eventSink.invoke(
|
||||||
ActionListEvents.ComputeForMessage(
|
ActionListEvents.ComputeForMessage(
|
||||||
event = stateEvent,
|
event = stateEvent,
|
||||||
canRedactOwn = false,
|
userEventPermissions = aUserEventPermissions(
|
||||||
canRedactOther = false,
|
canRedactOwn = false,
|
||||||
canSendMessage = true,
|
canRedactOther = false,
|
||||||
canSendReaction = true,
|
canSendMessage = true,
|
||||||
|
canSendReaction = true,
|
||||||
|
canPinUnpin = true,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
// val loadingState = awaitItem()
|
// val loadingState = awaitItem()
|
||||||
// assertThat(loadingState.target).isEqualTo(ActionListState.Target.Loading(messageEvent))
|
// assertThat(loadingState.target).isEqualTo(ActionListState.Target.Loading(messageEvent))
|
||||||
val successState = awaitItem()
|
|
||||||
assertThat(successState.target).isEqualTo(
|
|
||||||
ActionListState.Target.Success(
|
|
||||||
event = stateEvent,
|
|
||||||
displayEmojiReactions = false,
|
|
||||||
actions = persistentListOf(
|
|
||||||
TimelineItemAction.Copy,
|
|
||||||
TimelineItemAction.CopyLink,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
initialState.eventSink.invoke(ActionListEvents.Clear)
|
|
||||||
assertThat(awaitItem().target).isEqualTo(ActionListState.Target.None)
|
assertThat(awaitItem().target).isEqualTo(ActionListState.Target.None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - compute message in non-debuggable build`() = runTest {
|
fun `present - compute message in non-debuggable build`() = runTest {
|
||||||
val presenter = createActionListPresenter(isDeveloperModeEnabled = false)
|
val presenter = createActionListPresenter(isDeveloperModeEnabled = false, isPinFeatureEnabled = true)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
|
|
@ -514,10 +547,59 @@ class ActionListPresenterTest {
|
||||||
initialState.eventSink.invoke(
|
initialState.eventSink.invoke(
|
||||||
ActionListEvents.ComputeForMessage(
|
ActionListEvents.ComputeForMessage(
|
||||||
event = messageEvent,
|
event = messageEvent,
|
||||||
canRedactOwn = true,
|
userEventPermissions = aUserEventPermissions(
|
||||||
canRedactOther = false,
|
canRedactOwn = true,
|
||||||
canSendMessage = true,
|
canRedactOther = false,
|
||||||
canSendReaction = true,
|
canSendMessage = true,
|
||||||
|
canSendReaction = true,
|
||||||
|
canPinUnpin = true,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
// val loadingState = awaitItem()
|
||||||
|
// assertThat(loadingState.target).isEqualTo(ActionListState.Target.Loading(messageEvent))
|
||||||
|
val successState = awaitItem()
|
||||||
|
assertThat(successState.target).isEqualTo(
|
||||||
|
ActionListState.Target.Success(
|
||||||
|
event = messageEvent,
|
||||||
|
displayEmojiReactions = true,
|
||||||
|
actions = persistentListOf(
|
||||||
|
TimelineItemAction.Reply,
|
||||||
|
TimelineItemAction.Forward,
|
||||||
|
TimelineItemAction.Edit,
|
||||||
|
TimelineItemAction.Pin,
|
||||||
|
TimelineItemAction.Copy,
|
||||||
|
TimelineItemAction.CopyLink,
|
||||||
|
TimelineItemAction.Redact,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
initialState.eventSink.invoke(ActionListEvents.Clear)
|
||||||
|
assertThat(awaitItem().target).isEqualTo(ActionListState.Target.None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - compute message when user can't pin`() = runTest {
|
||||||
|
val presenter = createActionListPresenter(isDeveloperModeEnabled = true, isPinFeatureEnabled = true)
|
||||||
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
|
presenter.present()
|
||||||
|
}.test {
|
||||||
|
val initialState = awaitItem()
|
||||||
|
val messageEvent = aMessageEvent(
|
||||||
|
isMine = true,
|
||||||
|
content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = null)
|
||||||
|
)
|
||||||
|
initialState.eventSink.invoke(
|
||||||
|
ActionListEvents.ComputeForMessage(
|
||||||
|
event = messageEvent,
|
||||||
|
userEventPermissions = aUserEventPermissions(
|
||||||
|
canRedactOwn = true,
|
||||||
|
canRedactOther = false,
|
||||||
|
canSendMessage = true,
|
||||||
|
canSendReaction = true,
|
||||||
|
canPinUnpin = false,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
// val loadingState = awaitItem()
|
// val loadingState = awaitItem()
|
||||||
|
|
@ -533,6 +615,61 @@ class ActionListPresenterTest {
|
||||||
TimelineItemAction.Edit,
|
TimelineItemAction.Edit,
|
||||||
TimelineItemAction.Copy,
|
TimelineItemAction.Copy,
|
||||||
TimelineItemAction.CopyLink,
|
TimelineItemAction.CopyLink,
|
||||||
|
TimelineItemAction.ViewSource,
|
||||||
|
TimelineItemAction.Redact,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
initialState.eventSink.invoke(ActionListEvents.Clear)
|
||||||
|
assertThat(awaitItem().target).isEqualTo(ActionListState.Target.None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - compute message when event is already pinned`() = runTest {
|
||||||
|
val room = FakeMatrixRoom().apply {
|
||||||
|
givenRoomInfo(aRoomInfo(pinnedEventIds = listOf(AN_EVENT_ID)))
|
||||||
|
}
|
||||||
|
val presenter = createActionListPresenter(
|
||||||
|
isDeveloperModeEnabled = true,
|
||||||
|
isPinFeatureEnabled = true,
|
||||||
|
room = room
|
||||||
|
)
|
||||||
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
|
presenter.present()
|
||||||
|
}.test {
|
||||||
|
val initialState = awaitItem()
|
||||||
|
val messageEvent = aMessageEvent(
|
||||||
|
isMine = true,
|
||||||
|
content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = null)
|
||||||
|
)
|
||||||
|
initialState.eventSink.invoke(
|
||||||
|
ActionListEvents.ComputeForMessage(
|
||||||
|
event = messageEvent,
|
||||||
|
userEventPermissions = aUserEventPermissions(
|
||||||
|
canRedactOwn = true,
|
||||||
|
canRedactOther = false,
|
||||||
|
canSendMessage = true,
|
||||||
|
canSendReaction = true,
|
||||||
|
canPinUnpin = true,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
// val loadingState = awaitItem()
|
||||||
|
// assertThat(loadingState.target).isEqualTo(ActionListState.Target.Loading(messageEvent))
|
||||||
|
val successState = awaitItem()
|
||||||
|
assertThat(successState.target).isEqualTo(
|
||||||
|
ActionListState.Target.Success(
|
||||||
|
event = messageEvent,
|
||||||
|
displayEmojiReactions = true,
|
||||||
|
actions = persistentListOf(
|
||||||
|
TimelineItemAction.Reply,
|
||||||
|
TimelineItemAction.Forward,
|
||||||
|
TimelineItemAction.Edit,
|
||||||
|
TimelineItemAction.Unpin,
|
||||||
|
TimelineItemAction.Copy,
|
||||||
|
TimelineItemAction.CopyLink,
|
||||||
|
TimelineItemAction.ViewSource,
|
||||||
TimelineItemAction.Redact,
|
TimelineItemAction.Redact,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -544,7 +681,7 @@ class ActionListPresenterTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - compute message with no actions`() = runTest {
|
fun `present - compute message with no actions`() = runTest {
|
||||||
val presenter = createActionListPresenter(isDeveloperModeEnabled = false)
|
val presenter = createActionListPresenter(isDeveloperModeEnabled = false, isPinFeatureEnabled = true)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
|
|
@ -561,10 +698,13 @@ class ActionListPresenterTest {
|
||||||
initialState.eventSink.invoke(
|
initialState.eventSink.invoke(
|
||||||
ActionListEvents.ComputeForMessage(
|
ActionListEvents.ComputeForMessage(
|
||||||
event = messageEvent,
|
event = messageEvent,
|
||||||
canRedactOwn = false,
|
userEventPermissions = aUserEventPermissions(
|
||||||
canRedactOther = false,
|
canRedactOwn = false,
|
||||||
canSendMessage = true,
|
canRedactOther = false,
|
||||||
canSendReaction = true,
|
canSendMessage = true,
|
||||||
|
canSendReaction = true,
|
||||||
|
canPinUnpin = true,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
assertThat(awaitItem().target).isInstanceOf(ActionListState.Target.Success::class.java)
|
assertThat(awaitItem().target).isInstanceOf(ActionListState.Target.Success::class.java)
|
||||||
|
|
@ -572,10 +712,12 @@ class ActionListPresenterTest {
|
||||||
initialState.eventSink.invoke(
|
initialState.eventSink.invoke(
|
||||||
ActionListEvents.ComputeForMessage(
|
ActionListEvents.ComputeForMessage(
|
||||||
event = redactedEvent,
|
event = redactedEvent,
|
||||||
canRedactOwn = false,
|
userEventPermissions = aUserEventPermissions(
|
||||||
canRedactOther = false,
|
canRedactOwn = false,
|
||||||
canSendMessage = true,
|
canRedactOther = false,
|
||||||
canSendReaction = true,
|
canSendMessage = true,
|
||||||
|
canSendReaction = true,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
awaitItem().run {
|
awaitItem().run {
|
||||||
|
|
@ -586,7 +728,7 @@ class ActionListPresenterTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - compute not sent message`() = runTest {
|
fun `present - compute not sent message`() = runTest {
|
||||||
val presenter = createActionListPresenter(isDeveloperModeEnabled = false)
|
val presenter = createActionListPresenter(isDeveloperModeEnabled = false, isPinFeatureEnabled = true)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
|
|
@ -595,16 +737,20 @@ class ActionListPresenterTest {
|
||||||
// No event id, so it's not sent yet
|
// No event id, so it's not sent yet
|
||||||
eventId = null,
|
eventId = null,
|
||||||
isMine = true,
|
isMine = true,
|
||||||
|
canBeRepliedTo = false,
|
||||||
content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = null),
|
content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = null),
|
||||||
)
|
)
|
||||||
|
|
||||||
initialState.eventSink.invoke(
|
initialState.eventSink.invoke(
|
||||||
ActionListEvents.ComputeForMessage(
|
ActionListEvents.ComputeForMessage(
|
||||||
event = messageEvent,
|
event = messageEvent,
|
||||||
canRedactOwn = true,
|
userEventPermissions = aUserEventPermissions(
|
||||||
canRedactOther = false,
|
canRedactOwn = true,
|
||||||
canSendMessage = true,
|
canRedactOther = false,
|
||||||
canSendReaction = true,
|
canSendMessage = true,
|
||||||
|
canSendReaction = true,
|
||||||
|
canPinUnpin = true,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
val successState = awaitItem()
|
val successState = awaitItem()
|
||||||
|
|
@ -624,7 +770,7 @@ class ActionListPresenterTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - compute for editable poll message`() = runTest {
|
fun `present - compute for editable poll message`() = runTest {
|
||||||
val presenter = createActionListPresenter(isDeveloperModeEnabled = false)
|
val presenter = createActionListPresenter(isDeveloperModeEnabled = false, isPinFeatureEnabled = true)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
|
|
@ -637,10 +783,13 @@ class ActionListPresenterTest {
|
||||||
initialState.eventSink.invoke(
|
initialState.eventSink.invoke(
|
||||||
ActionListEvents.ComputeForMessage(
|
ActionListEvents.ComputeForMessage(
|
||||||
event = messageEvent,
|
event = messageEvent,
|
||||||
canRedactOwn = true,
|
userEventPermissions = aUserEventPermissions(
|
||||||
canRedactOther = false,
|
canRedactOwn = true,
|
||||||
canSendMessage = true,
|
canRedactOther = false,
|
||||||
canSendReaction = true,
|
canSendMessage = true,
|
||||||
|
canSendReaction = true,
|
||||||
|
canPinUnpin = true,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
val successState = awaitItem()
|
val successState = awaitItem()
|
||||||
|
|
@ -652,6 +801,7 @@ class ActionListPresenterTest {
|
||||||
TimelineItemAction.Reply,
|
TimelineItemAction.Reply,
|
||||||
TimelineItemAction.Edit,
|
TimelineItemAction.Edit,
|
||||||
TimelineItemAction.EndPoll,
|
TimelineItemAction.EndPoll,
|
||||||
|
TimelineItemAction.Pin,
|
||||||
TimelineItemAction.CopyLink,
|
TimelineItemAction.CopyLink,
|
||||||
TimelineItemAction.Redact,
|
TimelineItemAction.Redact,
|
||||||
)
|
)
|
||||||
|
|
@ -662,7 +812,7 @@ class ActionListPresenterTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - compute for non-editable poll message`() = runTest {
|
fun `present - compute for non-editable poll message`() = runTest {
|
||||||
val presenter = createActionListPresenter(isDeveloperModeEnabled = false)
|
val presenter = createActionListPresenter(isDeveloperModeEnabled = false, isPinFeatureEnabled = true)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
|
|
@ -675,10 +825,13 @@ class ActionListPresenterTest {
|
||||||
initialState.eventSink.invoke(
|
initialState.eventSink.invoke(
|
||||||
ActionListEvents.ComputeForMessage(
|
ActionListEvents.ComputeForMessage(
|
||||||
event = messageEvent,
|
event = messageEvent,
|
||||||
canRedactOwn = true,
|
userEventPermissions = aUserEventPermissions(
|
||||||
canRedactOther = false,
|
canRedactOwn = true,
|
||||||
canSendMessage = true,
|
canRedactOther = false,
|
||||||
canSendReaction = true,
|
canSendMessage = true,
|
||||||
|
canSendReaction = true,
|
||||||
|
canPinUnpin = true
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
val successState = awaitItem()
|
val successState = awaitItem()
|
||||||
|
|
@ -689,6 +842,7 @@ class ActionListPresenterTest {
|
||||||
actions = persistentListOf(
|
actions = persistentListOf(
|
||||||
TimelineItemAction.Reply,
|
TimelineItemAction.Reply,
|
||||||
TimelineItemAction.EndPoll,
|
TimelineItemAction.EndPoll,
|
||||||
|
TimelineItemAction.Pin,
|
||||||
TimelineItemAction.CopyLink,
|
TimelineItemAction.CopyLink,
|
||||||
TimelineItemAction.Redact,
|
TimelineItemAction.Redact,
|
||||||
)
|
)
|
||||||
|
|
@ -699,7 +853,7 @@ class ActionListPresenterTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - compute for ended poll message`() = runTest {
|
fun `present - compute for ended poll message`() = runTest {
|
||||||
val presenter = createActionListPresenter(isDeveloperModeEnabled = false)
|
val presenter = createActionListPresenter(isDeveloperModeEnabled = false, isPinFeatureEnabled = true)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
|
|
@ -712,10 +866,13 @@ class ActionListPresenterTest {
|
||||||
initialState.eventSink.invoke(
|
initialState.eventSink.invoke(
|
||||||
ActionListEvents.ComputeForMessage(
|
ActionListEvents.ComputeForMessage(
|
||||||
event = messageEvent,
|
event = messageEvent,
|
||||||
canRedactOwn = true,
|
userEventPermissions = aUserEventPermissions(
|
||||||
canRedactOther = false,
|
canRedactOwn = true,
|
||||||
canSendMessage = true,
|
canRedactOther = false,
|
||||||
canSendReaction = true,
|
canSendMessage = true,
|
||||||
|
canSendReaction = true,
|
||||||
|
canPinUnpin = true,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
val successState = awaitItem()
|
val successState = awaitItem()
|
||||||
|
|
@ -725,6 +882,7 @@ class ActionListPresenterTest {
|
||||||
displayEmojiReactions = true,
|
displayEmojiReactions = true,
|
||||||
actions = persistentListOf(
|
actions = persistentListOf(
|
||||||
TimelineItemAction.Reply,
|
TimelineItemAction.Reply,
|
||||||
|
TimelineItemAction.Pin,
|
||||||
TimelineItemAction.CopyLink,
|
TimelineItemAction.CopyLink,
|
||||||
TimelineItemAction.Redact,
|
TimelineItemAction.Redact,
|
||||||
)
|
)
|
||||||
|
|
@ -735,22 +893,26 @@ class ActionListPresenterTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - compute for voice message`() = runTest {
|
fun `present - compute for voice message`() = runTest {
|
||||||
val presenter = createActionListPresenter(isDeveloperModeEnabled = false)
|
val presenter = createActionListPresenter(isDeveloperModeEnabled = false, isPinFeatureEnabled = true)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
val messageEvent = aMessageEvent(
|
val messageEvent = aMessageEvent(
|
||||||
isMine = true,
|
isMine = true,
|
||||||
|
isEditable = false,
|
||||||
content = aTimelineItemVoiceContent(),
|
content = aTimelineItemVoiceContent(),
|
||||||
)
|
)
|
||||||
initialState.eventSink.invoke(
|
initialState.eventSink.invoke(
|
||||||
ActionListEvents.ComputeForMessage(
|
ActionListEvents.ComputeForMessage(
|
||||||
event = messageEvent,
|
event = messageEvent,
|
||||||
canRedactOwn = true,
|
userEventPermissions = aUserEventPermissions(
|
||||||
canRedactOther = false,
|
canRedactOwn = true,
|
||||||
canSendMessage = true,
|
canRedactOther = false,
|
||||||
canSendReaction = true,
|
canSendMessage = true,
|
||||||
|
canSendReaction = true,
|
||||||
|
canPinUnpin = true
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
val successState = awaitItem()
|
val successState = awaitItem()
|
||||||
|
|
@ -761,6 +923,7 @@ class ActionListPresenterTest {
|
||||||
actions = persistentListOf(
|
actions = persistentListOf(
|
||||||
TimelineItemAction.Reply,
|
TimelineItemAction.Reply,
|
||||||
TimelineItemAction.Forward,
|
TimelineItemAction.Forward,
|
||||||
|
TimelineItemAction.Pin,
|
||||||
TimelineItemAction.CopyLink,
|
TimelineItemAction.CopyLink,
|
||||||
TimelineItemAction.Redact,
|
TimelineItemAction.Redact,
|
||||||
)
|
)
|
||||||
|
|
@ -771,7 +934,7 @@ class ActionListPresenterTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - compute for call notify`() = runTest {
|
fun `present - compute for call notify`() = runTest {
|
||||||
val presenter = createActionListPresenter(isDeveloperModeEnabled = true)
|
val presenter = createActionListPresenter(isDeveloperModeEnabled = true, isPinFeatureEnabled = true)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
|
|
@ -783,10 +946,12 @@ class ActionListPresenterTest {
|
||||||
initialState.eventSink.invoke(
|
initialState.eventSink.invoke(
|
||||||
ActionListEvents.ComputeForMessage(
|
ActionListEvents.ComputeForMessage(
|
||||||
event = messageEvent,
|
event = messageEvent,
|
||||||
canRedactOwn = true,
|
userEventPermissions = aUserEventPermissions(
|
||||||
canRedactOther = false,
|
canRedactOwn = true,
|
||||||
canSendMessage = true,
|
canRedactOther = false,
|
||||||
canSendReaction = true,
|
canSendMessage = true,
|
||||||
|
canSendReaction = true,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
val successState = awaitItem()
|
val successState = awaitItem()
|
||||||
|
|
@ -803,7 +968,20 @@ class ActionListPresenterTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createActionListPresenter(isDeveloperModeEnabled: Boolean): ActionListPresenter {
|
private fun createActionListPresenter(
|
||||||
|
isDeveloperModeEnabled: Boolean,
|
||||||
|
isPinFeatureEnabled: Boolean,
|
||||||
|
room: MatrixRoom = FakeMatrixRoom(),
|
||||||
|
): ActionListPresenter {
|
||||||
val preferencesStore = InMemoryAppPreferencesStore(isDeveloperModeEnabled = isDeveloperModeEnabled)
|
val preferencesStore = InMemoryAppPreferencesStore(isDeveloperModeEnabled = isDeveloperModeEnabled)
|
||||||
return ActionListPresenter(appPreferencesStore = preferencesStore)
|
val featureFlagsService = FakeFeatureFlagService(
|
||||||
|
initialState = mapOf(
|
||||||
|
FeatureFlags.PinnedEvents.key to isPinFeatureEnabled,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return ActionListPresenter(
|
||||||
|
appPreferencesStore = preferencesStore,
|
||||||
|
featureFlagsService = featureFlagsService,
|
||||||
|
room = room
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@ internal fun aMessageEvent(
|
||||||
transactionId: TransactionId? = null,
|
transactionId: TransactionId? = null,
|
||||||
isMine: Boolean = true,
|
isMine: Boolean = true,
|
||||||
isEditable: Boolean = true,
|
isEditable: Boolean = true,
|
||||||
|
canBeRepliedTo: Boolean = true,
|
||||||
content: TimelineItemEventContent = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, formattedBody = null, isEdited = false),
|
content: TimelineItemEventContent = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, formattedBody = null, isEdited = false),
|
||||||
inReplyTo: InReplyToDetails? = null,
|
inReplyTo: InReplyToDetails? = null,
|
||||||
isThreaded: Boolean = false,
|
isThreaded: Boolean = false,
|
||||||
|
|
@ -58,6 +59,7 @@ internal fun aMessageEvent(
|
||||||
sentTime = "",
|
sentTime = "",
|
||||||
isMine = isMine,
|
isMine = isMine,
|
||||||
isEditable = isEditable,
|
isEditable = isEditable,
|
||||||
|
canBeRepliedTo = canBeRepliedTo,
|
||||||
reactionsState = aTimelineItemReactions(count = 0),
|
reactionsState = aTimelineItemReactions(count = 0),
|
||||||
readReceiptState = TimelineItemReadReceipts(emptyList<ReadReceiptData>().toImmutableList()),
|
readReceiptState = TimelineItemReadReceipts(emptyList<ReadReceiptData>().toImmutableList()),
|
||||||
localSendState = sendState,
|
localSendState = sendState,
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,7 @@ class TimelineItemGrouperTest {
|
||||||
readReceiptState = TimelineItemReadReceipts(emptyList<ReadReceiptData>().toImmutableList()),
|
readReceiptState = TimelineItemReadReceipts(emptyList<ReadReceiptData>().toImmutableList()),
|
||||||
localSendState = LocalEventSendState.Sent(AN_EVENT_ID),
|
localSendState = LocalEventSendState.Sent(AN_EVENT_ID),
|
||||||
isEditable = false,
|
isEditable = false,
|
||||||
|
canBeRepliedTo = false,
|
||||||
inReplyTo = null,
|
inReplyTo = null,
|
||||||
isThreaded = false,
|
isThreaded = false,
|
||||||
debugInfo = aTimelineItemDebugInfo(),
|
debugInfo = aTimelineItemDebugInfo(),
|
||||||
|
|
|
||||||
|
|
@ -85,6 +85,7 @@ fun aRedactedMatrixTimeline(eventId: EventId) = listOf<MatrixTimelineItem>(
|
||||||
eventId = eventId,
|
eventId = eventId,
|
||||||
transactionId = null,
|
transactionId = null,
|
||||||
isEditable = false,
|
isEditable = false,
|
||||||
|
canBeRepliedTo = false,
|
||||||
isLocal = false,
|
isLocal = false,
|
||||||
isOwn = false,
|
isOwn = false,
|
||||||
isRemote = false,
|
isRemote = false,
|
||||||
|
|
|
||||||
|
|
@ -120,6 +120,13 @@ enum class FeatureFlags(
|
||||||
defaultValue = { it.buildType != BuildType.RELEASE },
|
defaultValue = { it.buildType != BuildType.RELEASE },
|
||||||
isFinished = false,
|
isFinished = false,
|
||||||
),
|
),
|
||||||
|
PinnedEvents(
|
||||||
|
key = "feature.pinnedEvents",
|
||||||
|
title = "Pinned Events",
|
||||||
|
description = "Allow user to pin events in a room",
|
||||||
|
defaultValue = { false },
|
||||||
|
isFinished = false,
|
||||||
|
),
|
||||||
SyncOnPush(
|
SyncOnPush(
|
||||||
key = "feature.syncOnPush",
|
key = "feature.syncOnPush",
|
||||||
title = "Sync on push",
|
title = "Sync on push",
|
||||||
|
|
|
||||||
|
|
@ -180,6 +180,8 @@ interface MatrixRoom : Closeable {
|
||||||
|
|
||||||
suspend fun canUserTriggerRoomNotification(userId: UserId): Result<Boolean>
|
suspend fun canUserTriggerRoomNotification(userId: UserId): Result<Boolean>
|
||||||
|
|
||||||
|
suspend fun canUserPinUnpin(userId: UserId): Result<Boolean>
|
||||||
|
|
||||||
suspend fun canUserJoinCall(userId: UserId): Result<Boolean> =
|
suspend fun canUserJoinCall(userId: UserId): Result<Boolean> =
|
||||||
canUserSendState(userId, StateEventType.CALL_MEMBER)
|
canUserSendState(userId, StateEventType.CALL_MEMBER)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
package io.element.android.libraries.matrix.api.room
|
package io.element.android.libraries.matrix.api.room
|
||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
|
import io.element.android.libraries.matrix.api.core.EventId
|
||||||
import io.element.android.libraries.matrix.api.core.RoomAlias
|
import io.element.android.libraries.matrix.api.core.RoomAlias
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import io.element.android.libraries.matrix.api.core.UserId
|
import io.element.android.libraries.matrix.api.core.UserId
|
||||||
|
|
@ -52,4 +53,5 @@ data class MatrixRoomInfo(
|
||||||
val hasRoomCall: Boolean,
|
val hasRoomCall: Boolean,
|
||||||
val activeRoomCallParticipants: ImmutableList<String>,
|
val activeRoomCallParticipants: ImmutableList<String>,
|
||||||
val heroes: ImmutableList<MatrixUser>,
|
val heroes: ImmutableList<MatrixUser>,
|
||||||
|
val pinnedEventIds: ImmutableList<EventId>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -65,3 +65,8 @@ suspend fun MatrixRoom.canRedactOwn(): Result<Boolean> = canUserRedactOwn(sessio
|
||||||
* Shortcut for calling [MatrixRoom.canRedactOther] with our own user.
|
* Shortcut for calling [MatrixRoom.canRedactOther] with our own user.
|
||||||
*/
|
*/
|
||||||
suspend fun MatrixRoom.canRedactOther(): Result<Boolean> = canUserRedactOther(sessionId)
|
suspend fun MatrixRoom.canRedactOther(): Result<Boolean> = canUserRedactOther(sessionId)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shortcut for calling [MatrixRoom.canUserPinUnpin] with our own user.
|
||||||
|
*/
|
||||||
|
suspend fun MatrixRoom.canPinUnpin(): Result<Boolean> = canUserPinUnpin(sessionId)
|
||||||
|
|
|
||||||
|
|
@ -169,4 +169,22 @@ interface Timeline : AutoCloseable {
|
||||||
): Result<MediaUploadHandler>
|
): Result<MediaUploadHandler>
|
||||||
|
|
||||||
suspend fun loadReplyDetails(eventId: EventId): InReplyTo
|
suspend fun loadReplyDetails(eventId: EventId): InReplyTo
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a new pinned event by sending an updated `m.room.pinned_events`
|
||||||
|
* event containing the new event id.
|
||||||
|
*
|
||||||
|
* Returns `true` if we sent the request, `false` if the event was already
|
||||||
|
* pinned.
|
||||||
|
*/
|
||||||
|
suspend fun pinEvent(eventId: EventId): Result<Boolean>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a new pinned event by sending an updated `m.room.pinned_events`
|
||||||
|
* event without the event id we want to remove.
|
||||||
|
*
|
||||||
|
* Returns `true` if we sent the request, `false` if the event wasn't
|
||||||
|
* pinned
|
||||||
|
*/
|
||||||
|
suspend fun unpinEvent(eventId: EventId): Result<Boolean>
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ data class EventTimelineItem(
|
||||||
val eventId: EventId?,
|
val eventId: EventId?,
|
||||||
val transactionId: TransactionId?,
|
val transactionId: TransactionId?,
|
||||||
val isEditable: Boolean,
|
val isEditable: Boolean,
|
||||||
|
val canBeRepliedTo: Boolean,
|
||||||
val isLocal: Boolean,
|
val isLocal: Boolean,
|
||||||
val isOwn: Boolean,
|
val isOwn: Boolean,
|
||||||
val isRemote: Boolean,
|
val isRemote: Boolean,
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
package io.element.android.libraries.matrix.impl.room
|
package io.element.android.libraries.matrix.impl.room
|
||||||
|
|
||||||
|
import io.element.android.libraries.matrix.api.core.EventId
|
||||||
import io.element.android.libraries.matrix.api.core.RoomAlias
|
import io.element.android.libraries.matrix.api.core.RoomAlias
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import io.element.android.libraries.matrix.api.core.UserId
|
import io.element.android.libraries.matrix.api.core.UserId
|
||||||
|
|
@ -58,7 +59,8 @@ class MatrixRoomInfoMapper {
|
||||||
userDefinedNotificationMode = it.userDefinedNotificationMode?.map(),
|
userDefinedNotificationMode = it.userDefinedNotificationMode?.map(),
|
||||||
hasRoomCall = it.hasRoomCall,
|
hasRoomCall = it.hasRoomCall,
|
||||||
activeRoomCallParticipants = it.activeRoomCallParticipants.toImmutableList(),
|
activeRoomCallParticipants = it.activeRoomCallParticipants.toImmutableList(),
|
||||||
heroes = it.elementHeroes().toImmutableList()
|
heroes = it.elementHeroes().toImmutableList(),
|
||||||
|
pinnedEventIds = it.pinnedEventIds.map(::EventId).toImmutableList(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -403,6 +403,12 @@ class RustMatrixRoom(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun canUserPinUnpin(userId: UserId): Result<Boolean> {
|
||||||
|
return runCatching {
|
||||||
|
innerRoom.canUserPinUnpin(userId.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun sendImage(
|
override suspend fun sendImage(
|
||||||
file: File,
|
file: File,
|
||||||
thumbnailFile: File?,
|
thumbnailFile: File?,
|
||||||
|
|
|
||||||
|
|
@ -525,6 +525,18 @@ class RustTimeline(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun pinEvent(eventId: EventId): Result<Boolean> = withContext(dispatcher) {
|
||||||
|
runCatching {
|
||||||
|
inner.pinEvent(eventId = eventId.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun unpinEvent(eventId: EventId): Result<Boolean> = withContext(dispatcher) {
|
||||||
|
runCatching {
|
||||||
|
inner.unpinEvent(eventId = eventId.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun fetchDetailsForEvent(eventId: EventId): Result<Unit> {
|
private suspend fun fetchDetailsForEvent(eventId: EventId): Result<Unit> {
|
||||||
return runCatching {
|
return runCatching {
|
||||||
inner.fetchDetailsForEvent(eventId.value)
|
inner.fetchDetailsForEvent(eventId.value)
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@ class EventTimelineItemMapper(private val contentMapper: TimelineEventContentMap
|
||||||
eventId = it.eventId()?.let(::EventId),
|
eventId = it.eventId()?.let(::EventId),
|
||||||
transactionId = it.transactionId()?.let(::TransactionId),
|
transactionId = it.transactionId()?.let(::TransactionId),
|
||||||
isEditable = it.isEditable(),
|
isEditable = it.isEditable(),
|
||||||
|
canBeRepliedTo = it.canBeRepliedTo(),
|
||||||
isLocal = it.isLocal(),
|
isLocal = it.isLocal(),
|
||||||
isOwn = it.isOwn(),
|
isOwn = it.isOwn(),
|
||||||
isRemote = it.isRemote(),
|
isRemote = it.isRemote(),
|
||||||
|
|
|
||||||
|
|
@ -125,6 +125,7 @@ class FakeMatrixRoom(
|
||||||
private val getWidgetDriverResult: (MatrixWidgetSettings) -> Result<MatrixWidgetDriver> = { lambdaError() },
|
private val getWidgetDriverResult: (MatrixWidgetSettings) -> Result<MatrixWidgetDriver> = { lambdaError() },
|
||||||
private val canUserTriggerRoomNotificationResult: (UserId) -> Result<Boolean> = { lambdaError() },
|
private val canUserTriggerRoomNotificationResult: (UserId) -> Result<Boolean> = { lambdaError() },
|
||||||
private val canUserJoinCallResult: (UserId) -> Result<Boolean> = { lambdaError() },
|
private val canUserJoinCallResult: (UserId) -> Result<Boolean> = { lambdaError() },
|
||||||
|
private val canUserPinUnpinResult: (UserId) -> Result<Boolean> = { lambdaError() },
|
||||||
private val setIsFavoriteResult: (Boolean) -> Result<Unit> = { lambdaError() },
|
private val setIsFavoriteResult: (Boolean) -> Result<Unit> = { lambdaError() },
|
||||||
private val powerLevelsResult: () -> Result<MatrixRoomPowerLevels> = { lambdaError() },
|
private val powerLevelsResult: () -> Result<MatrixRoomPowerLevels> = { lambdaError() },
|
||||||
private val updatePowerLevelsResult: () -> Result<Unit> = { lambdaError() },
|
private val updatePowerLevelsResult: () -> Result<Unit> = { lambdaError() },
|
||||||
|
|
@ -289,6 +290,10 @@ class FakeMatrixRoom(
|
||||||
return canUserJoinCallResult(userId)
|
return canUserJoinCallResult(userId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun canUserPinUnpin(userId: UserId): Result<Boolean> {
|
||||||
|
return canUserPinUnpinResult(userId)
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun sendImage(
|
override suspend fun sendImage(
|
||||||
file: File,
|
file: File,
|
||||||
thumbnailFile: File?,
|
thumbnailFile: File?,
|
||||||
|
|
@ -517,6 +522,7 @@ fun aRoomInfo(
|
||||||
userPowerLevels: ImmutableMap<UserId, Long> = persistentMapOf(),
|
userPowerLevels: ImmutableMap<UserId, Long> = persistentMapOf(),
|
||||||
activeRoomCallParticipants: List<String> = emptyList(),
|
activeRoomCallParticipants: List<String> = emptyList(),
|
||||||
heroes: List<MatrixUser> = emptyList(),
|
heroes: List<MatrixUser> = emptyList(),
|
||||||
|
pinnedEventIds: List<EventId> = emptyList(),
|
||||||
) = MatrixRoomInfo(
|
) = MatrixRoomInfo(
|
||||||
id = id,
|
id = id,
|
||||||
name = name,
|
name = name,
|
||||||
|
|
@ -542,6 +548,7 @@ fun aRoomInfo(
|
||||||
userPowerLevels = userPowerLevels,
|
userPowerLevels = userPowerLevels,
|
||||||
activeRoomCallParticipants = activeRoomCallParticipants.toImmutableList(),
|
activeRoomCallParticipants = activeRoomCallParticipants.toImmutableList(),
|
||||||
heroes = heroes.toImmutableList(),
|
heroes = heroes.toImmutableList(),
|
||||||
|
pinnedEventIds = pinnedEventIds.toImmutableList(),
|
||||||
)
|
)
|
||||||
|
|
||||||
fun defaultRoomPowerLevels() = MatrixRoomPowerLevels(
|
fun defaultRoomPowerLevels() = MatrixRoomPowerLevels(
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ import io.element.android.libraries.matrix.api.timeline.ReceiptType
|
||||||
import io.element.android.libraries.matrix.api.timeline.Timeline
|
import io.element.android.libraries.matrix.api.timeline.Timeline
|
||||||
import io.element.android.libraries.matrix.api.timeline.item.event.InReplyTo
|
import io.element.android.libraries.matrix.api.timeline.item.event.InReplyTo
|
||||||
import io.element.android.libraries.matrix.test.media.FakeMediaUploadHandler
|
import io.element.android.libraries.matrix.test.media.FakeMediaUploadHandler
|
||||||
|
import io.element.android.tests.testutils.lambda.lambdaError
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
|
@ -371,6 +372,16 @@ class FakeTimeline(
|
||||||
|
|
||||||
override suspend fun loadReplyDetails(eventId: EventId) = loadReplyDetailsLambda(eventId)
|
override suspend fun loadReplyDetails(eventId: EventId) = loadReplyDetailsLambda(eventId)
|
||||||
|
|
||||||
|
var pinEventLambda: (eventId: EventId) -> Result<Boolean> = { lambdaError() }
|
||||||
|
override suspend fun pinEvent(eventId: EventId): Result<Boolean> {
|
||||||
|
return pinEventLambda(eventId)
|
||||||
|
}
|
||||||
|
|
||||||
|
var unpinEventLambda: (eventId: EventId) -> Result<Boolean> = { lambdaError() }
|
||||||
|
override suspend fun unpinEvent(eventId: EventId): Result<Boolean> {
|
||||||
|
return unpinEventLambda(eventId)
|
||||||
|
}
|
||||||
|
|
||||||
var closeCounter = 0
|
var closeCounter = 0
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,7 @@ fun anEventTimelineItem(
|
||||||
eventId: EventId = AN_EVENT_ID,
|
eventId: EventId = AN_EVENT_ID,
|
||||||
transactionId: TransactionId? = null,
|
transactionId: TransactionId? = null,
|
||||||
isEditable: Boolean = false,
|
isEditable: Boolean = false,
|
||||||
|
canBeRepliedTo: Boolean = false,
|
||||||
isLocal: Boolean = false,
|
isLocal: Boolean = false,
|
||||||
isOwn: Boolean = false,
|
isOwn: Boolean = false,
|
||||||
isRemote: Boolean = false,
|
isRemote: Boolean = false,
|
||||||
|
|
@ -61,6 +62,7 @@ fun anEventTimelineItem(
|
||||||
eventId = eventId,
|
eventId = eventId,
|
||||||
transactionId = transactionId,
|
transactionId = transactionId,
|
||||||
isEditable = isEditable,
|
isEditable = isEditable,
|
||||||
|
canBeRepliedTo = canBeRepliedTo,
|
||||||
isLocal = isLocal,
|
isLocal = isLocal,
|
||||||
isOwn = isOwn,
|
isOwn = isOwn,
|
||||||
isRemote = isRemote,
|
isRemote = isRemote,
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,13 @@ fun MatrixRoom.canCall(updateKey: Long): State<Boolean> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun MatrixRoom.canPinUnpin(updateKey: Long): State<Boolean> {
|
||||||
|
return produceState(initialValue = false, key1 = updateKey) {
|
||||||
|
value = canUserPinUnpin(sessionId).getOrElse { false }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MatrixRoom.isOwnUserAdmin(): Boolean {
|
fun MatrixRoom.isOwnUserAdmin(): Boolean {
|
||||||
val roomInfo by roomInfoFlow.collectAsState(initial = null)
|
val roomInfo by roomInfoFlow.collectAsState(initial = null)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue