Merge pull request #3963 from element-hq/feature/bma/copyCaption
Add timeline action item to copy caption
This commit is contained in:
@@ -28,6 +28,8 @@ import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import io.element.android.anvilannotations.ContributesNode
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.features.messages.impl.actionlist.ActionListPresenter
|
||||
import io.element.android.features.messages.impl.actionlist.model.TimelineItemActionPostProcessor
|
||||
import io.element.android.features.messages.impl.attachments.Attachment
|
||||
import io.element.android.features.messages.impl.messagecomposer.MessageComposerEvents
|
||||
import io.element.android.features.messages.impl.messagecomposer.MessageComposerPresenter
|
||||
@@ -65,6 +67,7 @@ class MessagesNode @AssistedInject constructor(
|
||||
messageComposerPresenterFactory: MessageComposerPresenter.Factory,
|
||||
timelinePresenterFactory: TimelinePresenter.Factory,
|
||||
presenterFactory: MessagesPresenter.Factory,
|
||||
actionListPresenterFactory: ActionListPresenter.Factory,
|
||||
private val timelineItemPresenterFactories: TimelineItemPresenterFactories,
|
||||
private val mediaPlayer: MediaPlayer,
|
||||
private val permalinkParser: PermalinkParser,
|
||||
@@ -73,6 +76,7 @@ class MessagesNode @AssistedInject constructor(
|
||||
navigator = this,
|
||||
composerPresenter = messageComposerPresenterFactory.create(this),
|
||||
timelinePresenter = timelinePresenterFactory.create(this),
|
||||
actionListPresenter = actionListPresenterFactory.create(TimelineItemActionPostProcessor.Default)
|
||||
)
|
||||
private val callbacks = plugins<Callback>()
|
||||
|
||||
|
||||
@@ -28,9 +28,8 @@ import im.vector.app.features.analytics.plan.PinUnpinAction
|
||||
import io.element.android.appconfig.MessageComposerConfig
|
||||
import io.element.android.features.messages.api.timeline.HtmlConverterProvider
|
||||
import io.element.android.features.messages.impl.actionlist.ActionListEvents
|
||||
import io.element.android.features.messages.impl.actionlist.ActionListPresenter
|
||||
import io.element.android.features.messages.impl.actionlist.ActionListState
|
||||
import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction
|
||||
import io.element.android.features.messages.impl.actionlist.model.TimelineItemActionPostProcessor
|
||||
import io.element.android.features.messages.impl.crypto.identity.IdentityChangeState
|
||||
import io.element.android.features.messages.impl.messagecomposer.MessageComposerEvents
|
||||
import io.element.android.features.messages.impl.messagecomposer.MessageComposerState
|
||||
@@ -93,7 +92,7 @@ class MessagesPresenter @AssistedInject constructor(
|
||||
@Assisted private val timelinePresenter: Presenter<TimelineState>,
|
||||
private val timelineProtectionPresenter: Presenter<TimelineProtectionState>,
|
||||
private val identityChangeStatePresenter: Presenter<IdentityChangeState>,
|
||||
actionListPresenterFactory: ActionListPresenter.Factory,
|
||||
@Assisted private val actionListPresenter: Presenter<ActionListState>,
|
||||
private val customReactionPresenter: Presenter<CustomReactionState>,
|
||||
private val reactionSummaryPresenter: Presenter<ReactionSummaryState>,
|
||||
private val readReceiptBottomSheetPresenter: Presenter<ReadReceiptBottomSheetState>,
|
||||
@@ -110,14 +109,13 @@ class MessagesPresenter @AssistedInject constructor(
|
||||
private val permalinkParser: PermalinkParser,
|
||||
private val analyticsService: AnalyticsService,
|
||||
) : Presenter<MessagesState> {
|
||||
private val actionListPresenter = actionListPresenterFactory.create(TimelineItemActionPostProcessor.Default)
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
fun create(
|
||||
navigator: MessagesNavigator,
|
||||
composerPresenter: Presenter<MessageComposerState>,
|
||||
timelinePresenter: Presenter<TimelineState>,
|
||||
actionListPresenter: Presenter<ActionListState>,
|
||||
): MessagesPresenter
|
||||
}
|
||||
|
||||
@@ -272,7 +270,8 @@ class MessagesPresenter @AssistedInject constructor(
|
||||
timelineState: TimelineState,
|
||||
) = launch {
|
||||
when (action) {
|
||||
TimelineItemAction.Copy -> handleCopyContents(targetEvent)
|
||||
TimelineItemAction.CopyText -> handleCopyContents(targetEvent)
|
||||
TimelineItemAction.CopyCaption -> handleCopyCaption(targetEvent)
|
||||
TimelineItemAction.CopyLink -> handleCopyLink(targetEvent)
|
||||
TimelineItemAction.Redact -> handleActionRedact(targetEvent)
|
||||
TimelineItemAction.Edit -> handleActionEdit(targetEvent, composerState, enableTextFormatting)
|
||||
@@ -488,11 +487,17 @@ class MessagesPresenter @AssistedInject constructor(
|
||||
is TimelineItemStateContent -> event.content.body
|
||||
else -> return
|
||||
}
|
||||
|
||||
clipboardHelper.copyPlainText(content)
|
||||
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
|
||||
snackbarDispatcher.post(SnackbarMessage(R.string.screen_room_timeline_message_copied))
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun handleCopyCaption(event: TimelineItem.Event) {
|
||||
val content = (event.content as? TimelineItemEventContentWithAttachment)?.caption ?: return
|
||||
clipboardHelper.copyPlainText(content)
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
|
||||
snackbarDispatcher.post(SnackbarMessage(CommonStrings.common_copied_to_clipboard))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import dagger.assisted.AssistedInject
|
||||
import io.element.android.features.messages.api.pinned.IsPinnedMessagesFeatureEnabled
|
||||
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.TimelineItemActionComparator
|
||||
import io.element.android.features.messages.impl.actionlist.model.TimelineItemActionPostProcessor
|
||||
import io.element.android.features.messages.impl.crypto.sendfailure.VerifiedUserSendFailure
|
||||
import io.element.android.features.messages.impl.crypto.sendfailure.VerifiedUserSendFailureFactory
|
||||
@@ -70,6 +71,8 @@ class DefaultActionListPresenter @AssistedInject constructor(
|
||||
override fun create(postProcessor: TimelineItemActionPostProcessor): DefaultActionListPresenter
|
||||
}
|
||||
|
||||
private val comparator = TimelineItemActionComparator()
|
||||
|
||||
@Composable
|
||||
override fun present(): ActionListState {
|
||||
val localCoroutineScope = rememberCoroutineScope()
|
||||
@@ -145,7 +148,7 @@ class DefaultActionListPresenter @AssistedInject constructor(
|
||||
isEventPinned: Boolean,
|
||||
): List<TimelineItemAction> {
|
||||
val canRedact = timelineItem.isMine && usersEventPermissions.canRedactOwn || !timelineItem.isMine && usersEventPermissions.canRedactOther
|
||||
return buildList {
|
||||
return buildSet {
|
||||
if (timelineItem.canBeRepliedTo && usersEventPermissions.canSendMessage) {
|
||||
if (timelineItem.isThreaded) {
|
||||
add(TimelineItemAction.ReplyInThread)
|
||||
@@ -183,7 +186,9 @@ class DefaultActionListPresenter @AssistedInject constructor(
|
||||
}
|
||||
}
|
||||
if (timelineItem.content.canBeCopied()) {
|
||||
add(TimelineItemAction.Copy)
|
||||
add(TimelineItemAction.CopyText)
|
||||
} else if ((timelineItem.content as? TimelineItemEventContentWithAttachment)?.caption.isNullOrBlank().not()) {
|
||||
add(TimelineItemAction.CopyCaption)
|
||||
}
|
||||
if (timelineItem.isRemote) {
|
||||
add(TimelineItemAction.CopyLink)
|
||||
@@ -199,6 +204,7 @@ class DefaultActionListPresenter @AssistedInject constructor(
|
||||
}
|
||||
}
|
||||
.postFilter(timelineItem.content)
|
||||
.sortedWith(comparator)
|
||||
.let(postProcessor::process)
|
||||
}
|
||||
}
|
||||
@@ -206,7 +212,7 @@ class DefaultActionListPresenter @AssistedInject constructor(
|
||||
/**
|
||||
* Post filter the actions based on the content of the event.
|
||||
*/
|
||||
private fun List<TimelineItemAction>.postFilter(content: TimelineItemEventContent): List<TimelineItemAction> {
|
||||
private fun Iterable<TimelineItemAction>.postFilter(content: TimelineItemEventContent): Iterable<TimelineItemAction> {
|
||||
return filter { action ->
|
||||
when (content) {
|
||||
is TimelineItemCallNotifyContent,
|
||||
|
||||
@@ -9,6 +9,7 @@ package io.element.android.features.messages.impl.actionlist
|
||||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction
|
||||
import io.element.android.features.messages.impl.actionlist.model.TimelineItemActionComparator
|
||||
import io.element.android.features.messages.impl.crypto.sendfailure.VerifiedUserSendFailure
|
||||
import io.element.android.features.messages.impl.crypto.sendfailure.resolve.anUnsignedDeviceSendFailure
|
||||
import io.element.android.features.messages.impl.timeline.aTimelineItemEvent
|
||||
@@ -22,7 +23,7 @@ import io.element.android.features.messages.impl.timeline.model.event.aTimelineI
|
||||
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemVoiceContent
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.MessageShield
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
|
||||
open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
|
||||
override val values: Sequence<ActionListState>
|
||||
@@ -50,7 +51,9 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
|
||||
),
|
||||
displayEmojiReactions = true,
|
||||
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
||||
actions = aTimelineItemActionList(),
|
||||
actions = aTimelineItemActionList(
|
||||
copyAction = TimelineItemAction.CopyCaption,
|
||||
),
|
||||
)
|
||||
),
|
||||
anActionListState(
|
||||
@@ -61,7 +64,9 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
|
||||
),
|
||||
displayEmojiReactions = true,
|
||||
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
||||
actions = aTimelineItemActionList(),
|
||||
actions = aTimelineItemActionList(
|
||||
copyAction = TimelineItemAction.CopyCaption,
|
||||
),
|
||||
)
|
||||
),
|
||||
anActionListState(
|
||||
@@ -72,7 +77,9 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
|
||||
),
|
||||
displayEmojiReactions = true,
|
||||
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
||||
actions = aTimelineItemActionList(),
|
||||
actions = aTimelineItemActionList(
|
||||
copyAction = null,
|
||||
),
|
||||
)
|
||||
),
|
||||
anActionListState(
|
||||
@@ -83,18 +90,22 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
|
||||
),
|
||||
displayEmojiReactions = true,
|
||||
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
||||
actions = aTimelineItemActionList(),
|
||||
actions = aTimelineItemActionList(
|
||||
copyAction = TimelineItemAction.CopyCaption,
|
||||
),
|
||||
)
|
||||
),
|
||||
anActionListState(
|
||||
target = ActionListState.Target.Success(
|
||||
event = aTimelineItemEvent(
|
||||
content = aTimelineItemVoiceContent(),
|
||||
content = aTimelineItemVoiceContent(caption = null),
|
||||
timelineItemReactions = reactionsState
|
||||
),
|
||||
displayEmojiReactions = true,
|
||||
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
||||
actions = aTimelineItemActionList(),
|
||||
actions = aTimelineItemActionList(
|
||||
copyAction = null,
|
||||
),
|
||||
)
|
||||
),
|
||||
anActionListState(
|
||||
@@ -161,27 +172,31 @@ fun anActionListState(
|
||||
eventSink = eventSink
|
||||
)
|
||||
|
||||
fun aTimelineItemActionList(): ImmutableList<TimelineItemAction> {
|
||||
return persistentListOf(
|
||||
fun aTimelineItemActionList(
|
||||
copyAction: TimelineItemAction? = TimelineItemAction.CopyText
|
||||
): ImmutableList<TimelineItemAction> {
|
||||
return setOfNotNull(
|
||||
TimelineItemAction.Reply,
|
||||
TimelineItemAction.Forward,
|
||||
TimelineItemAction.Copy,
|
||||
copyAction,
|
||||
TimelineItemAction.CopyLink,
|
||||
TimelineItemAction.Edit,
|
||||
TimelineItemAction.Redact,
|
||||
TimelineItemAction.ReportContent,
|
||||
TimelineItemAction.ViewSource,
|
||||
)
|
||||
.sortedWith(TimelineItemActionComparator())
|
||||
.toPersistentList()
|
||||
}
|
||||
|
||||
fun aTimelineItemPollActionList(): ImmutableList<TimelineItemAction> {
|
||||
return persistentListOf(
|
||||
return setOf(
|
||||
TimelineItemAction.EndPoll,
|
||||
TimelineItemAction.Reply,
|
||||
TimelineItemAction.Copy,
|
||||
TimelineItemAction.Pin,
|
||||
TimelineItemAction.CopyLink,
|
||||
TimelineItemAction.ViewSource,
|
||||
TimelineItemAction.ReportContent,
|
||||
TimelineItemAction.Redact,
|
||||
)
|
||||
.sortedWith(TimelineItemActionComparator())
|
||||
.toPersistentList()
|
||||
}
|
||||
|
||||
@@ -22,7 +22,8 @@ sealed class TimelineItemAction(
|
||||
) {
|
||||
data object ViewInTimeline : TimelineItemAction(CommonStrings.action_view_in_timeline, CompoundDrawables.ic_compound_visibility_on)
|
||||
data object Forward : TimelineItemAction(CommonStrings.action_forward, CompoundDrawables.ic_compound_forward)
|
||||
data object Copy : TimelineItemAction(CommonStrings.action_copy, CompoundDrawables.ic_compound_copy)
|
||||
data object CopyText : TimelineItemAction(CommonStrings.action_copy_text, CompoundDrawables.ic_compound_copy)
|
||||
data object CopyCaption : TimelineItemAction(CommonStrings.action_copy_caption, CompoundDrawables.ic_compound_copy)
|
||||
data object CopyLink : TimelineItemAction(CommonStrings.action_copy_link_to_message, CompoundDrawables.ic_compound_link)
|
||||
data object Redact : TimelineItemAction(CommonStrings.action_remove, CompoundDrawables.ic_compound_delete, destructive = true)
|
||||
data object Reply : TimelineItemAction(CommonStrings.action_reply, CompoundDrawables.ic_compound_reply)
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2024 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.messages.impl.actionlist.model
|
||||
|
||||
class TimelineItemActionComparator : Comparator<TimelineItemAction> {
|
||||
// See order in https://www.figma.com/design/ux3tYoZV9WghC7hHT9Fhk0/Compound-iOS-Components?node-id=2946-2392
|
||||
private val orderedList = listOf(
|
||||
TimelineItemAction.EndPoll,
|
||||
TimelineItemAction.ViewInTimeline,
|
||||
TimelineItemAction.Reply,
|
||||
TimelineItemAction.ReplyInThread,
|
||||
TimelineItemAction.Forward,
|
||||
TimelineItemAction.Pin,
|
||||
TimelineItemAction.Unpin,
|
||||
TimelineItemAction.CopyLink,
|
||||
TimelineItemAction.Edit,
|
||||
TimelineItemAction.CopyText,
|
||||
TimelineItemAction.AddCaption,
|
||||
TimelineItemAction.EditCaption,
|
||||
TimelineItemAction.CopyCaption,
|
||||
TimelineItemAction.RemoveCaption,
|
||||
TimelineItemAction.ViewSource,
|
||||
TimelineItemAction.ReportContent,
|
||||
TimelineItemAction.Redact,
|
||||
)
|
||||
|
||||
override fun compare(o1: TimelineItemAction, o2: TimelineItemAction): Int {
|
||||
val index1 = orderedList.indexOf(o1)
|
||||
val index2 = orderedList.indexOf(o2)
|
||||
return index1.compareTo(index2)
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ import com.bumble.appyx.core.plugin.plugins
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import io.element.android.anvilannotations.ContributesNode
|
||||
import io.element.android.features.messages.impl.actionlist.ActionListPresenter
|
||||
import io.element.android.features.messages.impl.timeline.di.LocalTimelineItemPresenterFactories
|
||||
import io.element.android.features.messages.impl.timeline.di.TimelineItemPresenterFactories
|
||||
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
||||
@@ -35,6 +36,7 @@ class PinnedMessagesListNode @AssistedInject constructor(
|
||||
@Assisted buildContext: BuildContext,
|
||||
@Assisted plugins: List<Plugin>,
|
||||
presenterFactory: PinnedMessagesListPresenter.Factory,
|
||||
actionListPresenterFactory: ActionListPresenter.Factory,
|
||||
private val timelineItemPresenterFactories: TimelineItemPresenterFactories,
|
||||
private val permalinkParser: PermalinkParser,
|
||||
) : Node(buildContext, plugins = plugins), PinnedMessagesListNavigator {
|
||||
@@ -47,7 +49,10 @@ class PinnedMessagesListNode @AssistedInject constructor(
|
||||
fun onForwardEventClick(eventId: EventId)
|
||||
}
|
||||
|
||||
private val presenter = presenterFactory.create(this)
|
||||
private val presenter = presenterFactory.create(
|
||||
navigator = this,
|
||||
actionListPresenter = actionListPresenterFactory.create(PinnedMessagesListTimelineActionPostProcessor())
|
||||
)
|
||||
private val callbacks = plugins<Callback>()
|
||||
|
||||
private fun onEventClick(event: TimelineItem.Event) {
|
||||
|
||||
@@ -23,7 +23,7 @@ import dagger.assisted.AssistedInject
|
||||
import im.vector.app.features.analytics.plan.Interaction
|
||||
import im.vector.app.features.analytics.plan.PinUnpinAction
|
||||
import io.element.android.features.messages.impl.UserEventPermissions
|
||||
import io.element.android.features.messages.impl.actionlist.ActionListPresenter
|
||||
import io.element.android.features.messages.impl.actionlist.ActionListState
|
||||
import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction
|
||||
import io.element.android.features.messages.impl.pinned.PinnedEventsTimelineProvider
|
||||
import io.element.android.features.messages.impl.timeline.TimelineRoomInfo
|
||||
@@ -64,13 +64,16 @@ class PinnedMessagesListPresenter @AssistedInject constructor(
|
||||
private val timelineProvider: PinnedEventsTimelineProvider,
|
||||
private val timelineProtectionPresenter: Presenter<TimelineProtectionState>,
|
||||
private val snackbarDispatcher: SnackbarDispatcher,
|
||||
actionListPresenterFactory: ActionListPresenter.Factory,
|
||||
@Assisted private val actionListPresenter: Presenter<ActionListState>,
|
||||
private val appCoroutineScope: CoroutineScope,
|
||||
private val analyticsService: AnalyticsService,
|
||||
) : Presenter<PinnedMessagesListState> {
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
fun create(navigator: PinnedMessagesListNavigator): PinnedMessagesListPresenter
|
||||
fun create(
|
||||
navigator: PinnedMessagesListNavigator,
|
||||
actionListPresenter: Presenter<ActionListState>,
|
||||
): PinnedMessagesListPresenter
|
||||
}
|
||||
|
||||
private val timelineItemsFactory: TimelineItemsFactory = timelineItemsFactoryCreator.create(
|
||||
@@ -79,7 +82,6 @@ class PinnedMessagesListPresenter @AssistedInject constructor(
|
||||
computeReactions = false,
|
||||
)
|
||||
)
|
||||
private val actionListPresenter = actionListPresenterFactory.create(PinnedMessagesListTimelineActionPostProcessor())
|
||||
|
||||
@Composable
|
||||
override fun present(): PinnedMessagesListState {
|
||||
|
||||
@@ -14,7 +14,7 @@ import com.google.common.truth.Truth.assertThat
|
||||
import im.vector.app.features.analytics.plan.PinUnpinAction
|
||||
import io.element.android.features.messages.impl.actionlist.ActionListEvents
|
||||
import io.element.android.features.messages.impl.actionlist.ActionListState
|
||||
import io.element.android.features.messages.impl.actionlist.FakeActionListPresenter
|
||||
import io.element.android.features.messages.impl.actionlist.anActionListState
|
||||
import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction
|
||||
import io.element.android.features.messages.impl.crypto.identity.anIdentityChangeState
|
||||
import io.element.android.features.messages.impl.fixtures.aMessageEvent
|
||||
@@ -242,7 +242,7 @@ class MessagesPresenterTest {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Copy, event))
|
||||
initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.CopyText, event))
|
||||
skipItems(2)
|
||||
assertThat(clipboardHelper.clipboardContents).isEqualTo((event.content as TimelineItemTextContent).body)
|
||||
}
|
||||
@@ -1116,7 +1116,7 @@ class MessagesPresenterTest {
|
||||
voiceMessageComposerPresenter = { aVoiceMessageComposerState() },
|
||||
timelinePresenter = { aTimelineState(eventSink = timelineEventSink) },
|
||||
timelineProtectionPresenter = { aTimelineProtectionState() },
|
||||
actionListPresenterFactory = FakeActionListPresenter.Factory(actionListEventSink),
|
||||
actionListPresenter = { anActionListState(eventSink = actionListEventSink) },
|
||||
customReactionPresenter = { aCustomReactionState() },
|
||||
reactionSummaryPresenter = { aReactionSummaryState() },
|
||||
readReceiptBottomSheetPresenter = { aReadReceiptBottomSheetState() },
|
||||
|
||||
@@ -176,8 +176,8 @@ class ActionListPresenterTest {
|
||||
TimelineItemAction.Reply,
|
||||
TimelineItemAction.Forward,
|
||||
TimelineItemAction.Pin,
|
||||
TimelineItemAction.Copy,
|
||||
TimelineItemAction.CopyLink,
|
||||
TimelineItemAction.CopyText,
|
||||
TimelineItemAction.ViewSource,
|
||||
TimelineItemAction.ReportContent,
|
||||
)
|
||||
@@ -221,8 +221,8 @@ class ActionListPresenterTest {
|
||||
TimelineItemAction.ReplyInThread,
|
||||
TimelineItemAction.Forward,
|
||||
TimelineItemAction.Pin,
|
||||
TimelineItemAction.Copy,
|
||||
TimelineItemAction.CopyLink,
|
||||
TimelineItemAction.CopyText,
|
||||
TimelineItemAction.ViewSource,
|
||||
TimelineItemAction.ReportContent,
|
||||
)
|
||||
@@ -268,8 +268,8 @@ class ActionListPresenterTest {
|
||||
actions = persistentListOf(
|
||||
TimelineItemAction.Forward,
|
||||
TimelineItemAction.Pin,
|
||||
TimelineItemAction.Copy,
|
||||
TimelineItemAction.CopyLink,
|
||||
TimelineItemAction.CopyText,
|
||||
TimelineItemAction.ViewSource,
|
||||
TimelineItemAction.ReportContent,
|
||||
)
|
||||
@@ -314,8 +314,8 @@ class ActionListPresenterTest {
|
||||
TimelineItemAction.Reply,
|
||||
TimelineItemAction.Forward,
|
||||
TimelineItemAction.Pin,
|
||||
TimelineItemAction.Copy,
|
||||
TimelineItemAction.CopyLink,
|
||||
TimelineItemAction.CopyText,
|
||||
TimelineItemAction.ViewSource,
|
||||
TimelineItemAction.ReportContent,
|
||||
TimelineItemAction.Redact,
|
||||
@@ -361,8 +361,8 @@ class ActionListPresenterTest {
|
||||
TimelineItemAction.Reply,
|
||||
TimelineItemAction.Forward,
|
||||
TimelineItemAction.Pin,
|
||||
TimelineItemAction.Copy,
|
||||
TimelineItemAction.CopyLink,
|
||||
TimelineItemAction.CopyText,
|
||||
TimelineItemAction.ViewSource,
|
||||
TimelineItemAction.ReportContent,
|
||||
TimelineItemAction.Redact,
|
||||
@@ -408,10 +408,10 @@ class ActionListPresenterTest {
|
||||
actions = persistentListOf(
|
||||
TimelineItemAction.Reply,
|
||||
TimelineItemAction.Forward,
|
||||
TimelineItemAction.Edit,
|
||||
TimelineItemAction.Pin,
|
||||
TimelineItemAction.Copy,
|
||||
TimelineItemAction.CopyLink,
|
||||
TimelineItemAction.Edit,
|
||||
TimelineItemAction.CopyText,
|
||||
TimelineItemAction.ViewSource,
|
||||
TimelineItemAction.Redact,
|
||||
)
|
||||
@@ -453,10 +453,10 @@ class ActionListPresenterTest {
|
||||
actions = persistentListOf(
|
||||
TimelineItemAction.ReplyInThread,
|
||||
TimelineItemAction.Forward,
|
||||
TimelineItemAction.Edit,
|
||||
TimelineItemAction.Pin,
|
||||
TimelineItemAction.Copy,
|
||||
TimelineItemAction.CopyLink,
|
||||
TimelineItemAction.Edit,
|
||||
TimelineItemAction.CopyText,
|
||||
TimelineItemAction.ViewSource,
|
||||
TimelineItemAction.Redact,
|
||||
)
|
||||
@@ -501,10 +501,10 @@ class ActionListPresenterTest {
|
||||
actions = persistentListOf(
|
||||
TimelineItemAction.Reply,
|
||||
TimelineItemAction.Forward,
|
||||
TimelineItemAction.Edit,
|
||||
TimelineItemAction.Pin,
|
||||
TimelineItemAction.Copy,
|
||||
TimelineItemAction.CopyLink,
|
||||
TimelineItemAction.Edit,
|
||||
TimelineItemAction.CopyText,
|
||||
TimelineItemAction.ViewSource,
|
||||
)
|
||||
)
|
||||
@@ -547,9 +547,9 @@ class ActionListPresenterTest {
|
||||
actions = persistentListOf(
|
||||
TimelineItemAction.Reply,
|
||||
TimelineItemAction.Forward,
|
||||
TimelineItemAction.AddCaption,
|
||||
TimelineItemAction.Pin,
|
||||
TimelineItemAction.CopyLink,
|
||||
TimelineItemAction.AddCaption,
|
||||
TimelineItemAction.ViewSource,
|
||||
TimelineItemAction.Redact,
|
||||
)
|
||||
@@ -646,10 +646,11 @@ class ActionListPresenterTest {
|
||||
actions = persistentListOf(
|
||||
TimelineItemAction.Reply,
|
||||
TimelineItemAction.Forward,
|
||||
TimelineItemAction.EditCaption,
|
||||
TimelineItemAction.RemoveCaption,
|
||||
TimelineItemAction.Pin,
|
||||
TimelineItemAction.CopyLink,
|
||||
TimelineItemAction.EditCaption,
|
||||
TimelineItemAction.CopyCaption,
|
||||
TimelineItemAction.RemoveCaption,
|
||||
TimelineItemAction.ViewSource,
|
||||
TimelineItemAction.Redact,
|
||||
)
|
||||
@@ -660,6 +661,54 @@ class ActionListPresenterTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - compute for a media with caption item - other user event`() = runTest {
|
||||
val presenter = createActionListPresenter(isDeveloperModeEnabled = true, isPinFeatureEnabled = true)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
val messageEvent = aMessageEvent(
|
||||
isMine = false,
|
||||
isEditable = false,
|
||||
content = aTimelineItemImageContent(
|
||||
caption = A_CAPTION,
|
||||
),
|
||||
)
|
||||
initialState.eventSink.invoke(
|
||||
ActionListEvents.ComputeForMessage(
|
||||
event = messageEvent,
|
||||
userEventPermissions = aUserEventPermissions(
|
||||
canRedactOwn = true,
|
||||
canRedactOther = false,
|
||||
canSendMessage = true,
|
||||
canSendReaction = true,
|
||||
canPinUnpin = true,
|
||||
),
|
||||
)
|
||||
)
|
||||
val successState = awaitItem()
|
||||
assertThat(successState.target).isEqualTo(
|
||||
ActionListState.Target.Success(
|
||||
event = messageEvent,
|
||||
displayEmojiReactions = true,
|
||||
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
||||
actions = persistentListOf(
|
||||
TimelineItemAction.Reply,
|
||||
TimelineItemAction.Forward,
|
||||
TimelineItemAction.Pin,
|
||||
TimelineItemAction.CopyLink,
|
||||
TimelineItemAction.CopyCaption,
|
||||
TimelineItemAction.ViewSource,
|
||||
TimelineItemAction.ReportContent,
|
||||
)
|
||||
)
|
||||
)
|
||||
initialState.eventSink.invoke(ActionListEvents.Clear)
|
||||
assertThat(awaitItem().target).isEqualTo(ActionListState.Target.None)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - compute for a state item in debug build`() = runTest {
|
||||
val presenter = createActionListPresenter(isDeveloperModeEnabled = true, isPinFeatureEnabled = true)
|
||||
@@ -764,10 +813,10 @@ class ActionListPresenterTest {
|
||||
actions = persistentListOf(
|
||||
TimelineItemAction.Reply,
|
||||
TimelineItemAction.Forward,
|
||||
TimelineItemAction.Edit,
|
||||
TimelineItemAction.Pin,
|
||||
TimelineItemAction.Copy,
|
||||
TimelineItemAction.CopyLink,
|
||||
TimelineItemAction.Edit,
|
||||
TimelineItemAction.CopyText,
|
||||
TimelineItemAction.Redact,
|
||||
)
|
||||
)
|
||||
@@ -811,9 +860,9 @@ class ActionListPresenterTest {
|
||||
actions = persistentListOf(
|
||||
TimelineItemAction.Reply,
|
||||
TimelineItemAction.Forward,
|
||||
TimelineItemAction.Edit,
|
||||
TimelineItemAction.Copy,
|
||||
TimelineItemAction.CopyLink,
|
||||
TimelineItemAction.Edit,
|
||||
TimelineItemAction.CopyText,
|
||||
TimelineItemAction.ViewSource,
|
||||
TimelineItemAction.Redact,
|
||||
)
|
||||
@@ -865,10 +914,10 @@ class ActionListPresenterTest {
|
||||
actions = persistentListOf(
|
||||
TimelineItemAction.Reply,
|
||||
TimelineItemAction.Forward,
|
||||
TimelineItemAction.Edit,
|
||||
TimelineItemAction.Unpin,
|
||||
TimelineItemAction.Copy,
|
||||
TimelineItemAction.CopyLink,
|
||||
TimelineItemAction.Edit,
|
||||
TimelineItemAction.CopyText,
|
||||
TimelineItemAction.ViewSource,
|
||||
TimelineItemAction.Redact,
|
||||
)
|
||||
@@ -961,7 +1010,7 @@ class ActionListPresenterTest {
|
||||
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
||||
actions = persistentListOf(
|
||||
TimelineItemAction.Edit,
|
||||
TimelineItemAction.Copy,
|
||||
TimelineItemAction.CopyText,
|
||||
TimelineItemAction.Redact,
|
||||
)
|
||||
)
|
||||
@@ -1000,11 +1049,11 @@ class ActionListPresenterTest {
|
||||
displayEmojiReactions = true,
|
||||
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
||||
actions = persistentListOf(
|
||||
TimelineItemAction.Reply,
|
||||
TimelineItemAction.Edit,
|
||||
TimelineItemAction.EndPoll,
|
||||
TimelineItemAction.Reply,
|
||||
TimelineItemAction.Pin,
|
||||
TimelineItemAction.CopyLink,
|
||||
TimelineItemAction.Edit,
|
||||
TimelineItemAction.Redact,
|
||||
)
|
||||
)
|
||||
@@ -1043,8 +1092,8 @@ class ActionListPresenterTest {
|
||||
displayEmojiReactions = true,
|
||||
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
||||
actions = persistentListOf(
|
||||
TimelineItemAction.Reply,
|
||||
TimelineItemAction.EndPoll,
|
||||
TimelineItemAction.Reply,
|
||||
TimelineItemAction.Pin,
|
||||
TimelineItemAction.CopyLink,
|
||||
TimelineItemAction.Redact,
|
||||
@@ -1105,7 +1154,9 @@ class ActionListPresenterTest {
|
||||
val messageEvent = aMessageEvent(
|
||||
isMine = true,
|
||||
isEditable = false,
|
||||
content = aTimelineItemVoiceContent(),
|
||||
content = aTimelineItemVoiceContent(
|
||||
caption = null,
|
||||
),
|
||||
)
|
||||
initialState.eventSink.invoke(
|
||||
ActionListEvents.ComputeForMessage(
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
/*
|
||||
* Copyright 2024 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.messages.impl.actionlist
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import io.element.android.features.messages.impl.actionlist.model.TimelineItemActionPostProcessor
|
||||
|
||||
class FakeActionListPresenter(private val eventSink: (ActionListEvents) -> Unit = {}) : ActionListPresenter {
|
||||
class Factory(private val eventSink: (ActionListEvents) -> Unit = {}) : ActionListPresenter.Factory {
|
||||
override fun create(postProcessor: TimelineItemActionPostProcessor): ActionListPresenter {
|
||||
return FakeActionListPresenter(eventSink)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun present(): ActionListState {
|
||||
return anActionListState(eventSink = eventSink)
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ package io.element.android.features.messages.impl.pinned.list
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import im.vector.app.features.analytics.plan.PinUnpinAction
|
||||
import io.element.android.features.messages.impl.actionlist.FakeActionListPresenter
|
||||
import io.element.android.features.messages.impl.actionlist.anActionListState
|
||||
import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction
|
||||
import io.element.android.features.messages.impl.fixtures.aTimelineItemsFactoryCreator
|
||||
import io.element.android.features.messages.impl.pinned.PinnedEventsTimelineProvider
|
||||
@@ -312,7 +312,7 @@ class PinnedMessagesListPresenterTest {
|
||||
timelineProvider = timelineProvider,
|
||||
timelineProtectionPresenter = { aTimelineProtectionState() },
|
||||
snackbarDispatcher = SnackbarDispatcher(),
|
||||
actionListPresenterFactory = FakeActionListPresenter.Factory(),
|
||||
actionListPresenter = { anActionListState() },
|
||||
analyticsService = analyticsService,
|
||||
appCoroutineScope = this,
|
||||
)
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2024 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.messages.impl.pinned.list
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction
|
||||
import org.junit.Test
|
||||
|
||||
class PinnedMessagesListTimelineActionPostProcessorTest {
|
||||
@Test
|
||||
fun `ensure that ViewInTimeline is added`() {
|
||||
val sut = PinnedMessagesListTimelineActionPostProcessor()
|
||||
val result = sut.process(
|
||||
listOf()
|
||||
)
|
||||
assertThat(result).isEqualTo(
|
||||
listOf(TimelineItemAction.ViewInTimeline)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ensure that some actions are kept and some other are filtered out`() {
|
||||
val sut = PinnedMessagesListTimelineActionPostProcessor()
|
||||
val result = sut.process(
|
||||
listOf(
|
||||
TimelineItemAction.Forward,
|
||||
TimelineItemAction.CopyText,
|
||||
TimelineItemAction.CopyCaption,
|
||||
TimelineItemAction.CopyLink,
|
||||
TimelineItemAction.Redact,
|
||||
TimelineItemAction.Reply,
|
||||
TimelineItemAction.ReplyInThread,
|
||||
TimelineItemAction.Edit,
|
||||
TimelineItemAction.EditCaption,
|
||||
TimelineItemAction.AddCaption,
|
||||
TimelineItemAction.RemoveCaption,
|
||||
TimelineItemAction.ViewSource,
|
||||
TimelineItemAction.ReportContent,
|
||||
TimelineItemAction.EndPoll,
|
||||
TimelineItemAction.Pin,
|
||||
TimelineItemAction.Unpin,
|
||||
)
|
||||
)
|
||||
assertThat(result).isEqualTo(
|
||||
listOf(
|
||||
TimelineItemAction.ViewInTimeline,
|
||||
TimelineItemAction.Unpin,
|
||||
TimelineItemAction.Forward,
|
||||
TimelineItemAction.ViewSource,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -46,8 +46,10 @@
|
||||
<string name="action_confirm_password">"Confirm password"</string>
|
||||
<string name="action_continue">"Continue"</string>
|
||||
<string name="action_copy">"Copy"</string>
|
||||
<string name="action_copy_caption">"Copy caption"</string>
|
||||
<string name="action_copy_link">"Copy link"</string>
|
||||
<string name="action_copy_link_to_message">"Copy link to message"</string>
|
||||
<string name="action_copy_text">"Copy text"</string>
|
||||
<string name="action_create">"Create"</string>
|
||||
<string name="action_create_a_room">"Create a room"</string>
|
||||
<string name="action_deactivate">"Deactivate"</string>
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user