Merge pull request #3963 from element-hq/feature/bma/copyCaption
Add timeline action item to copy caption
This commit is contained in:
commit
2f7ecebe30
36 changed files with 291 additions and 129 deletions
|
|
@ -28,6 +28,8 @@ import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedInject
|
import dagger.assisted.AssistedInject
|
||||||
import io.element.android.anvilannotations.ContributesNode
|
import io.element.android.anvilannotations.ContributesNode
|
||||||
import io.element.android.compound.theme.ElementTheme
|
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.attachments.Attachment
|
||||||
import io.element.android.features.messages.impl.messagecomposer.MessageComposerEvents
|
import io.element.android.features.messages.impl.messagecomposer.MessageComposerEvents
|
||||||
import io.element.android.features.messages.impl.messagecomposer.MessageComposerPresenter
|
import io.element.android.features.messages.impl.messagecomposer.MessageComposerPresenter
|
||||||
|
|
@ -65,6 +67,7 @@ class MessagesNode @AssistedInject constructor(
|
||||||
messageComposerPresenterFactory: MessageComposerPresenter.Factory,
|
messageComposerPresenterFactory: MessageComposerPresenter.Factory,
|
||||||
timelinePresenterFactory: TimelinePresenter.Factory,
|
timelinePresenterFactory: TimelinePresenter.Factory,
|
||||||
presenterFactory: MessagesPresenter.Factory,
|
presenterFactory: MessagesPresenter.Factory,
|
||||||
|
actionListPresenterFactory: ActionListPresenter.Factory,
|
||||||
private val timelineItemPresenterFactories: TimelineItemPresenterFactories,
|
private val timelineItemPresenterFactories: TimelineItemPresenterFactories,
|
||||||
private val mediaPlayer: MediaPlayer,
|
private val mediaPlayer: MediaPlayer,
|
||||||
private val permalinkParser: PermalinkParser,
|
private val permalinkParser: PermalinkParser,
|
||||||
|
|
@ -73,6 +76,7 @@ class MessagesNode @AssistedInject constructor(
|
||||||
navigator = this,
|
navigator = this,
|
||||||
composerPresenter = messageComposerPresenterFactory.create(this),
|
composerPresenter = messageComposerPresenterFactory.create(this),
|
||||||
timelinePresenter = timelinePresenterFactory.create(this),
|
timelinePresenter = timelinePresenterFactory.create(this),
|
||||||
|
actionListPresenter = actionListPresenterFactory.create(TimelineItemActionPostProcessor.Default)
|
||||||
)
|
)
|
||||||
private val callbacks = plugins<Callback>()
|
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.appconfig.MessageComposerConfig
|
||||||
import io.element.android.features.messages.api.timeline.HtmlConverterProvider
|
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.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.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.crypto.identity.IdentityChangeState
|
||||||
import io.element.android.features.messages.impl.messagecomposer.MessageComposerEvents
|
import io.element.android.features.messages.impl.messagecomposer.MessageComposerEvents
|
||||||
import io.element.android.features.messages.impl.messagecomposer.MessageComposerState
|
import io.element.android.features.messages.impl.messagecomposer.MessageComposerState
|
||||||
|
|
@ -93,7 +92,7 @@ class MessagesPresenter @AssistedInject constructor(
|
||||||
@Assisted private val timelinePresenter: Presenter<TimelineState>,
|
@Assisted private val timelinePresenter: Presenter<TimelineState>,
|
||||||
private val timelineProtectionPresenter: Presenter<TimelineProtectionState>,
|
private val timelineProtectionPresenter: Presenter<TimelineProtectionState>,
|
||||||
private val identityChangeStatePresenter: Presenter<IdentityChangeState>,
|
private val identityChangeStatePresenter: Presenter<IdentityChangeState>,
|
||||||
actionListPresenterFactory: ActionListPresenter.Factory,
|
@Assisted private val actionListPresenter: Presenter<ActionListState>,
|
||||||
private val customReactionPresenter: Presenter<CustomReactionState>,
|
private val customReactionPresenter: Presenter<CustomReactionState>,
|
||||||
private val reactionSummaryPresenter: Presenter<ReactionSummaryState>,
|
private val reactionSummaryPresenter: Presenter<ReactionSummaryState>,
|
||||||
private val readReceiptBottomSheetPresenter: Presenter<ReadReceiptBottomSheetState>,
|
private val readReceiptBottomSheetPresenter: Presenter<ReadReceiptBottomSheetState>,
|
||||||
|
|
@ -110,14 +109,13 @@ class MessagesPresenter @AssistedInject constructor(
|
||||||
private val permalinkParser: PermalinkParser,
|
private val permalinkParser: PermalinkParser,
|
||||||
private val analyticsService: AnalyticsService,
|
private val analyticsService: AnalyticsService,
|
||||||
) : Presenter<MessagesState> {
|
) : Presenter<MessagesState> {
|
||||||
private val actionListPresenter = actionListPresenterFactory.create(TimelineItemActionPostProcessor.Default)
|
|
||||||
|
|
||||||
@AssistedFactory
|
@AssistedFactory
|
||||||
interface Factory {
|
interface Factory {
|
||||||
fun create(
|
fun create(
|
||||||
navigator: MessagesNavigator,
|
navigator: MessagesNavigator,
|
||||||
composerPresenter: Presenter<MessageComposerState>,
|
composerPresenter: Presenter<MessageComposerState>,
|
||||||
timelinePresenter: Presenter<TimelineState>,
|
timelinePresenter: Presenter<TimelineState>,
|
||||||
|
actionListPresenter: Presenter<ActionListState>,
|
||||||
): MessagesPresenter
|
): MessagesPresenter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -272,7 +270,8 @@ class MessagesPresenter @AssistedInject constructor(
|
||||||
timelineState: TimelineState,
|
timelineState: TimelineState,
|
||||||
) = launch {
|
) = launch {
|
||||||
when (action) {
|
when (action) {
|
||||||
TimelineItemAction.Copy -> handleCopyContents(targetEvent)
|
TimelineItemAction.CopyText -> handleCopyContents(targetEvent)
|
||||||
|
TimelineItemAction.CopyCaption -> handleCopyCaption(targetEvent)
|
||||||
TimelineItemAction.CopyLink -> handleCopyLink(targetEvent)
|
TimelineItemAction.CopyLink -> handleCopyLink(targetEvent)
|
||||||
TimelineItemAction.Redact -> handleActionRedact(targetEvent)
|
TimelineItemAction.Redact -> handleActionRedact(targetEvent)
|
||||||
TimelineItemAction.Edit -> handleActionEdit(targetEvent, composerState, enableTextFormatting)
|
TimelineItemAction.Edit -> handleActionEdit(targetEvent, composerState, enableTextFormatting)
|
||||||
|
|
@ -488,11 +487,17 @@ class MessagesPresenter @AssistedInject constructor(
|
||||||
is TimelineItemStateContent -> event.content.body
|
is TimelineItemStateContent -> event.content.body
|
||||||
else -> return
|
else -> return
|
||||||
}
|
}
|
||||||
|
|
||||||
clipboardHelper.copyPlainText(content)
|
clipboardHelper.copyPlainText(content)
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
|
||||||
snackbarDispatcher.post(SnackbarMessage(R.string.screen_room_timeline_message_copied))
|
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.api.pinned.IsPinnedMessagesFeatureEnabled
|
||||||
import io.element.android.features.messages.impl.UserEventPermissions
|
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.actionlist.model.TimelineItemActionComparator
|
||||||
import io.element.android.features.messages.impl.actionlist.model.TimelineItemActionPostProcessor
|
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.VerifiedUserSendFailure
|
||||||
import io.element.android.features.messages.impl.crypto.sendfailure.VerifiedUserSendFailureFactory
|
import io.element.android.features.messages.impl.crypto.sendfailure.VerifiedUserSendFailureFactory
|
||||||
|
|
@ -70,6 +71,8 @@ class DefaultActionListPresenter @AssistedInject constructor(
|
||||||
override fun create(postProcessor: TimelineItemActionPostProcessor): DefaultActionListPresenter
|
override fun create(postProcessor: TimelineItemActionPostProcessor): DefaultActionListPresenter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val comparator = TimelineItemActionComparator()
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun present(): ActionListState {
|
override fun present(): ActionListState {
|
||||||
val localCoroutineScope = rememberCoroutineScope()
|
val localCoroutineScope = rememberCoroutineScope()
|
||||||
|
|
@ -145,7 +148,7 @@ class DefaultActionListPresenter @AssistedInject constructor(
|
||||||
isEventPinned: Boolean,
|
isEventPinned: Boolean,
|
||||||
): List<TimelineItemAction> {
|
): List<TimelineItemAction> {
|
||||||
val canRedact = timelineItem.isMine && usersEventPermissions.canRedactOwn || !timelineItem.isMine && usersEventPermissions.canRedactOther
|
val canRedact = timelineItem.isMine && usersEventPermissions.canRedactOwn || !timelineItem.isMine && usersEventPermissions.canRedactOther
|
||||||
return buildList {
|
return buildSet {
|
||||||
if (timelineItem.canBeRepliedTo && usersEventPermissions.canSendMessage) {
|
if (timelineItem.canBeRepliedTo && usersEventPermissions.canSendMessage) {
|
||||||
if (timelineItem.isThreaded) {
|
if (timelineItem.isThreaded) {
|
||||||
add(TimelineItemAction.ReplyInThread)
|
add(TimelineItemAction.ReplyInThread)
|
||||||
|
|
@ -183,7 +186,9 @@ class DefaultActionListPresenter @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (timelineItem.content.canBeCopied()) {
|
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) {
|
if (timelineItem.isRemote) {
|
||||||
add(TimelineItemAction.CopyLink)
|
add(TimelineItemAction.CopyLink)
|
||||||
|
|
@ -199,6 +204,7 @@ class DefaultActionListPresenter @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.postFilter(timelineItem.content)
|
.postFilter(timelineItem.content)
|
||||||
|
.sortedWith(comparator)
|
||||||
.let(postProcessor::process)
|
.let(postProcessor::process)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -206,7 +212,7 @@ class DefaultActionListPresenter @AssistedInject constructor(
|
||||||
/**
|
/**
|
||||||
* Post filter the actions based on the content of the event.
|
* 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 ->
|
return filter { action ->
|
||||||
when (content) {
|
when (content) {
|
||||||
is TimelineItemCallNotifyContent,
|
is TimelineItemCallNotifyContent,
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ package io.element.android.features.messages.impl.actionlist
|
||||||
|
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
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.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.VerifiedUserSendFailure
|
||||||
import io.element.android.features.messages.impl.crypto.sendfailure.resolve.anUnsignedDeviceSendFailure
|
import io.element.android.features.messages.impl.crypto.sendfailure.resolve.anUnsignedDeviceSendFailure
|
||||||
import io.element.android.features.messages.impl.timeline.aTimelineItemEvent
|
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.features.messages.impl.timeline.model.event.aTimelineItemVoiceContent
|
||||||
import io.element.android.libraries.matrix.api.timeline.item.event.MessageShield
|
import io.element.android.libraries.matrix.api.timeline.item.event.MessageShield
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.toPersistentList
|
||||||
|
|
||||||
open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
|
open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
|
||||||
override val values: Sequence<ActionListState>
|
override val values: Sequence<ActionListState>
|
||||||
|
|
@ -50,7 +51,9 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
|
||||||
),
|
),
|
||||||
displayEmojiReactions = true,
|
displayEmojiReactions = true,
|
||||||
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
||||||
actions = aTimelineItemActionList(),
|
actions = aTimelineItemActionList(
|
||||||
|
copyAction = TimelineItemAction.CopyCaption,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
anActionListState(
|
anActionListState(
|
||||||
|
|
@ -61,7 +64,9 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
|
||||||
),
|
),
|
||||||
displayEmojiReactions = true,
|
displayEmojiReactions = true,
|
||||||
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
||||||
actions = aTimelineItemActionList(),
|
actions = aTimelineItemActionList(
|
||||||
|
copyAction = TimelineItemAction.CopyCaption,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
anActionListState(
|
anActionListState(
|
||||||
|
|
@ -72,7 +77,9 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
|
||||||
),
|
),
|
||||||
displayEmojiReactions = true,
|
displayEmojiReactions = true,
|
||||||
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
||||||
actions = aTimelineItemActionList(),
|
actions = aTimelineItemActionList(
|
||||||
|
copyAction = null,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
anActionListState(
|
anActionListState(
|
||||||
|
|
@ -83,18 +90,22 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
|
||||||
),
|
),
|
||||||
displayEmojiReactions = true,
|
displayEmojiReactions = true,
|
||||||
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
||||||
actions = aTimelineItemActionList(),
|
actions = aTimelineItemActionList(
|
||||||
|
copyAction = TimelineItemAction.CopyCaption,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
anActionListState(
|
anActionListState(
|
||||||
target = ActionListState.Target.Success(
|
target = ActionListState.Target.Success(
|
||||||
event = aTimelineItemEvent(
|
event = aTimelineItemEvent(
|
||||||
content = aTimelineItemVoiceContent(),
|
content = aTimelineItemVoiceContent(caption = null),
|
||||||
timelineItemReactions = reactionsState
|
timelineItemReactions = reactionsState
|
||||||
),
|
),
|
||||||
displayEmojiReactions = true,
|
displayEmojiReactions = true,
|
||||||
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
||||||
actions = aTimelineItemActionList(),
|
actions = aTimelineItemActionList(
|
||||||
|
copyAction = null,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
anActionListState(
|
anActionListState(
|
||||||
|
|
@ -161,27 +172,31 @@ fun anActionListState(
|
||||||
eventSink = eventSink
|
eventSink = eventSink
|
||||||
)
|
)
|
||||||
|
|
||||||
fun aTimelineItemActionList(): ImmutableList<TimelineItemAction> {
|
fun aTimelineItemActionList(
|
||||||
return persistentListOf(
|
copyAction: TimelineItemAction? = TimelineItemAction.CopyText
|
||||||
|
): ImmutableList<TimelineItemAction> {
|
||||||
|
return setOfNotNull(
|
||||||
TimelineItemAction.Reply,
|
TimelineItemAction.Reply,
|
||||||
TimelineItemAction.Forward,
|
TimelineItemAction.Forward,
|
||||||
TimelineItemAction.Copy,
|
copyAction,
|
||||||
TimelineItemAction.CopyLink,
|
TimelineItemAction.CopyLink,
|
||||||
TimelineItemAction.Edit,
|
TimelineItemAction.Edit,
|
||||||
TimelineItemAction.Redact,
|
TimelineItemAction.Redact,
|
||||||
TimelineItemAction.ReportContent,
|
TimelineItemAction.ReportContent,
|
||||||
TimelineItemAction.ViewSource,
|
TimelineItemAction.ViewSource,
|
||||||
)
|
)
|
||||||
|
.sortedWith(TimelineItemActionComparator())
|
||||||
|
.toPersistentList()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun aTimelineItemPollActionList(): ImmutableList<TimelineItemAction> {
|
fun aTimelineItemPollActionList(): ImmutableList<TimelineItemAction> {
|
||||||
return persistentListOf(
|
return setOf(
|
||||||
TimelineItemAction.EndPoll,
|
TimelineItemAction.EndPoll,
|
||||||
TimelineItemAction.Reply,
|
TimelineItemAction.Reply,
|
||||||
TimelineItemAction.Copy,
|
TimelineItemAction.Pin,
|
||||||
TimelineItemAction.CopyLink,
|
TimelineItemAction.CopyLink,
|
||||||
TimelineItemAction.ViewSource,
|
|
||||||
TimelineItemAction.ReportContent,
|
|
||||||
TimelineItemAction.Redact,
|
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 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 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 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 Redact : TimelineItemAction(CommonStrings.action_remove, CompoundDrawables.ic_compound_delete, destructive = true)
|
||||||
data object Reply : TimelineItemAction(CommonStrings.action_reply, CompoundDrawables.ic_compound_reply)
|
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.Assisted
|
||||||
import dagger.assisted.AssistedInject
|
import dagger.assisted.AssistedInject
|
||||||
import io.element.android.anvilannotations.ContributesNode
|
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.LocalTimelineItemPresenterFactories
|
||||||
import io.element.android.features.messages.impl.timeline.di.TimelineItemPresenterFactories
|
import io.element.android.features.messages.impl.timeline.di.TimelineItemPresenterFactories
|
||||||
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
||||||
|
|
@ -35,6 +36,7 @@ class PinnedMessagesListNode @AssistedInject constructor(
|
||||||
@Assisted buildContext: BuildContext,
|
@Assisted buildContext: BuildContext,
|
||||||
@Assisted plugins: List<Plugin>,
|
@Assisted plugins: List<Plugin>,
|
||||||
presenterFactory: PinnedMessagesListPresenter.Factory,
|
presenterFactory: PinnedMessagesListPresenter.Factory,
|
||||||
|
actionListPresenterFactory: ActionListPresenter.Factory,
|
||||||
private val timelineItemPresenterFactories: TimelineItemPresenterFactories,
|
private val timelineItemPresenterFactories: TimelineItemPresenterFactories,
|
||||||
private val permalinkParser: PermalinkParser,
|
private val permalinkParser: PermalinkParser,
|
||||||
) : Node(buildContext, plugins = plugins), PinnedMessagesListNavigator {
|
) : Node(buildContext, plugins = plugins), PinnedMessagesListNavigator {
|
||||||
|
|
@ -47,7 +49,10 @@ class PinnedMessagesListNode @AssistedInject constructor(
|
||||||
fun onForwardEventClick(eventId: EventId)
|
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 val callbacks = plugins<Callback>()
|
||||||
|
|
||||||
private fun onEventClick(event: TimelineItem.Event) {
|
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.Interaction
|
||||||
import im.vector.app.features.analytics.plan.PinUnpinAction
|
import im.vector.app.features.analytics.plan.PinUnpinAction
|
||||||
import io.element.android.features.messages.impl.UserEventPermissions
|
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.actionlist.model.TimelineItemAction
|
||||||
import io.element.android.features.messages.impl.pinned.PinnedEventsTimelineProvider
|
import io.element.android.features.messages.impl.pinned.PinnedEventsTimelineProvider
|
||||||
import io.element.android.features.messages.impl.timeline.TimelineRoomInfo
|
import io.element.android.features.messages.impl.timeline.TimelineRoomInfo
|
||||||
|
|
@ -64,13 +64,16 @@ class PinnedMessagesListPresenter @AssistedInject constructor(
|
||||||
private val timelineProvider: PinnedEventsTimelineProvider,
|
private val timelineProvider: PinnedEventsTimelineProvider,
|
||||||
private val timelineProtectionPresenter: Presenter<TimelineProtectionState>,
|
private val timelineProtectionPresenter: Presenter<TimelineProtectionState>,
|
||||||
private val snackbarDispatcher: SnackbarDispatcher,
|
private val snackbarDispatcher: SnackbarDispatcher,
|
||||||
actionListPresenterFactory: ActionListPresenter.Factory,
|
@Assisted private val actionListPresenter: Presenter<ActionListState>,
|
||||||
private val appCoroutineScope: CoroutineScope,
|
private val appCoroutineScope: CoroutineScope,
|
||||||
private val analyticsService: AnalyticsService,
|
private val analyticsService: AnalyticsService,
|
||||||
) : Presenter<PinnedMessagesListState> {
|
) : Presenter<PinnedMessagesListState> {
|
||||||
@AssistedFactory
|
@AssistedFactory
|
||||||
interface Factory {
|
interface Factory {
|
||||||
fun create(navigator: PinnedMessagesListNavigator): PinnedMessagesListPresenter
|
fun create(
|
||||||
|
navigator: PinnedMessagesListNavigator,
|
||||||
|
actionListPresenter: Presenter<ActionListState>,
|
||||||
|
): PinnedMessagesListPresenter
|
||||||
}
|
}
|
||||||
|
|
||||||
private val timelineItemsFactory: TimelineItemsFactory = timelineItemsFactoryCreator.create(
|
private val timelineItemsFactory: TimelineItemsFactory = timelineItemsFactoryCreator.create(
|
||||||
|
|
@ -79,7 +82,6 @@ class PinnedMessagesListPresenter @AssistedInject constructor(
|
||||||
computeReactions = false,
|
computeReactions = false,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
private val actionListPresenter = actionListPresenterFactory.create(PinnedMessagesListTimelineActionPostProcessor())
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun present(): PinnedMessagesListState {
|
override fun present(): PinnedMessagesListState {
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ import com.google.common.truth.Truth.assertThat
|
||||||
import im.vector.app.features.analytics.plan.PinUnpinAction
|
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.ActionListEvents
|
||||||
import io.element.android.features.messages.impl.actionlist.ActionListState
|
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.actionlist.model.TimelineItemAction
|
||||||
import io.element.android.features.messages.impl.crypto.identity.anIdentityChangeState
|
import io.element.android.features.messages.impl.crypto.identity.anIdentityChangeState
|
||||||
import io.element.android.features.messages.impl.fixtures.aMessageEvent
|
import io.element.android.features.messages.impl.fixtures.aMessageEvent
|
||||||
|
|
@ -242,7 +242,7 @@ class MessagesPresenterTest {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Copy, event))
|
initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.CopyText, event))
|
||||||
skipItems(2)
|
skipItems(2)
|
||||||
assertThat(clipboardHelper.clipboardContents).isEqualTo((event.content as TimelineItemTextContent).body)
|
assertThat(clipboardHelper.clipboardContents).isEqualTo((event.content as TimelineItemTextContent).body)
|
||||||
}
|
}
|
||||||
|
|
@ -1116,7 +1116,7 @@ class MessagesPresenterTest {
|
||||||
voiceMessageComposerPresenter = { aVoiceMessageComposerState() },
|
voiceMessageComposerPresenter = { aVoiceMessageComposerState() },
|
||||||
timelinePresenter = { aTimelineState(eventSink = timelineEventSink) },
|
timelinePresenter = { aTimelineState(eventSink = timelineEventSink) },
|
||||||
timelineProtectionPresenter = { aTimelineProtectionState() },
|
timelineProtectionPresenter = { aTimelineProtectionState() },
|
||||||
actionListPresenterFactory = FakeActionListPresenter.Factory(actionListEventSink),
|
actionListPresenter = { anActionListState(eventSink = actionListEventSink) },
|
||||||
customReactionPresenter = { aCustomReactionState() },
|
customReactionPresenter = { aCustomReactionState() },
|
||||||
reactionSummaryPresenter = { aReactionSummaryState() },
|
reactionSummaryPresenter = { aReactionSummaryState() },
|
||||||
readReceiptBottomSheetPresenter = { aReadReceiptBottomSheetState() },
|
readReceiptBottomSheetPresenter = { aReadReceiptBottomSheetState() },
|
||||||
|
|
|
||||||
|
|
@ -176,8 +176,8 @@ class ActionListPresenterTest {
|
||||||
TimelineItemAction.Reply,
|
TimelineItemAction.Reply,
|
||||||
TimelineItemAction.Forward,
|
TimelineItemAction.Forward,
|
||||||
TimelineItemAction.Pin,
|
TimelineItemAction.Pin,
|
||||||
TimelineItemAction.Copy,
|
|
||||||
TimelineItemAction.CopyLink,
|
TimelineItemAction.CopyLink,
|
||||||
|
TimelineItemAction.CopyText,
|
||||||
TimelineItemAction.ViewSource,
|
TimelineItemAction.ViewSource,
|
||||||
TimelineItemAction.ReportContent,
|
TimelineItemAction.ReportContent,
|
||||||
)
|
)
|
||||||
|
|
@ -221,8 +221,8 @@ class ActionListPresenterTest {
|
||||||
TimelineItemAction.ReplyInThread,
|
TimelineItemAction.ReplyInThread,
|
||||||
TimelineItemAction.Forward,
|
TimelineItemAction.Forward,
|
||||||
TimelineItemAction.Pin,
|
TimelineItemAction.Pin,
|
||||||
TimelineItemAction.Copy,
|
|
||||||
TimelineItemAction.CopyLink,
|
TimelineItemAction.CopyLink,
|
||||||
|
TimelineItemAction.CopyText,
|
||||||
TimelineItemAction.ViewSource,
|
TimelineItemAction.ViewSource,
|
||||||
TimelineItemAction.ReportContent,
|
TimelineItemAction.ReportContent,
|
||||||
)
|
)
|
||||||
|
|
@ -268,8 +268,8 @@ class ActionListPresenterTest {
|
||||||
actions = persistentListOf(
|
actions = persistentListOf(
|
||||||
TimelineItemAction.Forward,
|
TimelineItemAction.Forward,
|
||||||
TimelineItemAction.Pin,
|
TimelineItemAction.Pin,
|
||||||
TimelineItemAction.Copy,
|
|
||||||
TimelineItemAction.CopyLink,
|
TimelineItemAction.CopyLink,
|
||||||
|
TimelineItemAction.CopyText,
|
||||||
TimelineItemAction.ViewSource,
|
TimelineItemAction.ViewSource,
|
||||||
TimelineItemAction.ReportContent,
|
TimelineItemAction.ReportContent,
|
||||||
)
|
)
|
||||||
|
|
@ -314,8 +314,8 @@ class ActionListPresenterTest {
|
||||||
TimelineItemAction.Reply,
|
TimelineItemAction.Reply,
|
||||||
TimelineItemAction.Forward,
|
TimelineItemAction.Forward,
|
||||||
TimelineItemAction.Pin,
|
TimelineItemAction.Pin,
|
||||||
TimelineItemAction.Copy,
|
|
||||||
TimelineItemAction.CopyLink,
|
TimelineItemAction.CopyLink,
|
||||||
|
TimelineItemAction.CopyText,
|
||||||
TimelineItemAction.ViewSource,
|
TimelineItemAction.ViewSource,
|
||||||
TimelineItemAction.ReportContent,
|
TimelineItemAction.ReportContent,
|
||||||
TimelineItemAction.Redact,
|
TimelineItemAction.Redact,
|
||||||
|
|
@ -361,8 +361,8 @@ class ActionListPresenterTest {
|
||||||
TimelineItemAction.Reply,
|
TimelineItemAction.Reply,
|
||||||
TimelineItemAction.Forward,
|
TimelineItemAction.Forward,
|
||||||
TimelineItemAction.Pin,
|
TimelineItemAction.Pin,
|
||||||
TimelineItemAction.Copy,
|
|
||||||
TimelineItemAction.CopyLink,
|
TimelineItemAction.CopyLink,
|
||||||
|
TimelineItemAction.CopyText,
|
||||||
TimelineItemAction.ViewSource,
|
TimelineItemAction.ViewSource,
|
||||||
TimelineItemAction.ReportContent,
|
TimelineItemAction.ReportContent,
|
||||||
TimelineItemAction.Redact,
|
TimelineItemAction.Redact,
|
||||||
|
|
@ -408,10 +408,10 @@ class ActionListPresenterTest {
|
||||||
actions = persistentListOf(
|
actions = persistentListOf(
|
||||||
TimelineItemAction.Reply,
|
TimelineItemAction.Reply,
|
||||||
TimelineItemAction.Forward,
|
TimelineItemAction.Forward,
|
||||||
TimelineItemAction.Edit,
|
|
||||||
TimelineItemAction.Pin,
|
TimelineItemAction.Pin,
|
||||||
TimelineItemAction.Copy,
|
|
||||||
TimelineItemAction.CopyLink,
|
TimelineItemAction.CopyLink,
|
||||||
|
TimelineItemAction.Edit,
|
||||||
|
TimelineItemAction.CopyText,
|
||||||
TimelineItemAction.ViewSource,
|
TimelineItemAction.ViewSource,
|
||||||
TimelineItemAction.Redact,
|
TimelineItemAction.Redact,
|
||||||
)
|
)
|
||||||
|
|
@ -453,10 +453,10 @@ class ActionListPresenterTest {
|
||||||
actions = persistentListOf(
|
actions = persistentListOf(
|
||||||
TimelineItemAction.ReplyInThread,
|
TimelineItemAction.ReplyInThread,
|
||||||
TimelineItemAction.Forward,
|
TimelineItemAction.Forward,
|
||||||
TimelineItemAction.Edit,
|
|
||||||
TimelineItemAction.Pin,
|
TimelineItemAction.Pin,
|
||||||
TimelineItemAction.Copy,
|
|
||||||
TimelineItemAction.CopyLink,
|
TimelineItemAction.CopyLink,
|
||||||
|
TimelineItemAction.Edit,
|
||||||
|
TimelineItemAction.CopyText,
|
||||||
TimelineItemAction.ViewSource,
|
TimelineItemAction.ViewSource,
|
||||||
TimelineItemAction.Redact,
|
TimelineItemAction.Redact,
|
||||||
)
|
)
|
||||||
|
|
@ -501,10 +501,10 @@ class ActionListPresenterTest {
|
||||||
actions = persistentListOf(
|
actions = persistentListOf(
|
||||||
TimelineItemAction.Reply,
|
TimelineItemAction.Reply,
|
||||||
TimelineItemAction.Forward,
|
TimelineItemAction.Forward,
|
||||||
TimelineItemAction.Edit,
|
|
||||||
TimelineItemAction.Pin,
|
TimelineItemAction.Pin,
|
||||||
TimelineItemAction.Copy,
|
|
||||||
TimelineItemAction.CopyLink,
|
TimelineItemAction.CopyLink,
|
||||||
|
TimelineItemAction.Edit,
|
||||||
|
TimelineItemAction.CopyText,
|
||||||
TimelineItemAction.ViewSource,
|
TimelineItemAction.ViewSource,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -547,9 +547,9 @@ class ActionListPresenterTest {
|
||||||
actions = persistentListOf(
|
actions = persistentListOf(
|
||||||
TimelineItemAction.Reply,
|
TimelineItemAction.Reply,
|
||||||
TimelineItemAction.Forward,
|
TimelineItemAction.Forward,
|
||||||
TimelineItemAction.AddCaption,
|
|
||||||
TimelineItemAction.Pin,
|
TimelineItemAction.Pin,
|
||||||
TimelineItemAction.CopyLink,
|
TimelineItemAction.CopyLink,
|
||||||
|
TimelineItemAction.AddCaption,
|
||||||
TimelineItemAction.ViewSource,
|
TimelineItemAction.ViewSource,
|
||||||
TimelineItemAction.Redact,
|
TimelineItemAction.Redact,
|
||||||
)
|
)
|
||||||
|
|
@ -646,10 +646,11 @@ class ActionListPresenterTest {
|
||||||
actions = persistentListOf(
|
actions = persistentListOf(
|
||||||
TimelineItemAction.Reply,
|
TimelineItemAction.Reply,
|
||||||
TimelineItemAction.Forward,
|
TimelineItemAction.Forward,
|
||||||
TimelineItemAction.EditCaption,
|
|
||||||
TimelineItemAction.RemoveCaption,
|
|
||||||
TimelineItemAction.Pin,
|
TimelineItemAction.Pin,
|
||||||
TimelineItemAction.CopyLink,
|
TimelineItemAction.CopyLink,
|
||||||
|
TimelineItemAction.EditCaption,
|
||||||
|
TimelineItemAction.CopyCaption,
|
||||||
|
TimelineItemAction.RemoveCaption,
|
||||||
TimelineItemAction.ViewSource,
|
TimelineItemAction.ViewSource,
|
||||||
TimelineItemAction.Redact,
|
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
|
@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, isPinFeatureEnabled = true)
|
val presenter = createActionListPresenter(isDeveloperModeEnabled = true, isPinFeatureEnabled = true)
|
||||||
|
|
@ -764,10 +813,10 @@ class ActionListPresenterTest {
|
||||||
actions = persistentListOf(
|
actions = persistentListOf(
|
||||||
TimelineItemAction.Reply,
|
TimelineItemAction.Reply,
|
||||||
TimelineItemAction.Forward,
|
TimelineItemAction.Forward,
|
||||||
TimelineItemAction.Edit,
|
|
||||||
TimelineItemAction.Pin,
|
TimelineItemAction.Pin,
|
||||||
TimelineItemAction.Copy,
|
|
||||||
TimelineItemAction.CopyLink,
|
TimelineItemAction.CopyLink,
|
||||||
|
TimelineItemAction.Edit,
|
||||||
|
TimelineItemAction.CopyText,
|
||||||
TimelineItemAction.Redact,
|
TimelineItemAction.Redact,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -811,9 +860,9 @@ class ActionListPresenterTest {
|
||||||
actions = persistentListOf(
|
actions = persistentListOf(
|
||||||
TimelineItemAction.Reply,
|
TimelineItemAction.Reply,
|
||||||
TimelineItemAction.Forward,
|
TimelineItemAction.Forward,
|
||||||
TimelineItemAction.Edit,
|
|
||||||
TimelineItemAction.Copy,
|
|
||||||
TimelineItemAction.CopyLink,
|
TimelineItemAction.CopyLink,
|
||||||
|
TimelineItemAction.Edit,
|
||||||
|
TimelineItemAction.CopyText,
|
||||||
TimelineItemAction.ViewSource,
|
TimelineItemAction.ViewSource,
|
||||||
TimelineItemAction.Redact,
|
TimelineItemAction.Redact,
|
||||||
)
|
)
|
||||||
|
|
@ -865,10 +914,10 @@ class ActionListPresenterTest {
|
||||||
actions = persistentListOf(
|
actions = persistentListOf(
|
||||||
TimelineItemAction.Reply,
|
TimelineItemAction.Reply,
|
||||||
TimelineItemAction.Forward,
|
TimelineItemAction.Forward,
|
||||||
TimelineItemAction.Edit,
|
|
||||||
TimelineItemAction.Unpin,
|
TimelineItemAction.Unpin,
|
||||||
TimelineItemAction.Copy,
|
|
||||||
TimelineItemAction.CopyLink,
|
TimelineItemAction.CopyLink,
|
||||||
|
TimelineItemAction.Edit,
|
||||||
|
TimelineItemAction.CopyText,
|
||||||
TimelineItemAction.ViewSource,
|
TimelineItemAction.ViewSource,
|
||||||
TimelineItemAction.Redact,
|
TimelineItemAction.Redact,
|
||||||
)
|
)
|
||||||
|
|
@ -961,7 +1010,7 @@ class ActionListPresenterTest {
|
||||||
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
||||||
actions = persistentListOf(
|
actions = persistentListOf(
|
||||||
TimelineItemAction.Edit,
|
TimelineItemAction.Edit,
|
||||||
TimelineItemAction.Copy,
|
TimelineItemAction.CopyText,
|
||||||
TimelineItemAction.Redact,
|
TimelineItemAction.Redact,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -1000,11 +1049,11 @@ class ActionListPresenterTest {
|
||||||
displayEmojiReactions = true,
|
displayEmojiReactions = true,
|
||||||
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
||||||
actions = persistentListOf(
|
actions = persistentListOf(
|
||||||
TimelineItemAction.Reply,
|
|
||||||
TimelineItemAction.Edit,
|
|
||||||
TimelineItemAction.EndPoll,
|
TimelineItemAction.EndPoll,
|
||||||
|
TimelineItemAction.Reply,
|
||||||
TimelineItemAction.Pin,
|
TimelineItemAction.Pin,
|
||||||
TimelineItemAction.CopyLink,
|
TimelineItemAction.CopyLink,
|
||||||
|
TimelineItemAction.Edit,
|
||||||
TimelineItemAction.Redact,
|
TimelineItemAction.Redact,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -1043,8 +1092,8 @@ class ActionListPresenterTest {
|
||||||
displayEmojiReactions = true,
|
displayEmojiReactions = true,
|
||||||
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
verifiedUserSendFailure = VerifiedUserSendFailure.None,
|
||||||
actions = persistentListOf(
|
actions = persistentListOf(
|
||||||
TimelineItemAction.Reply,
|
|
||||||
TimelineItemAction.EndPoll,
|
TimelineItemAction.EndPoll,
|
||||||
|
TimelineItemAction.Reply,
|
||||||
TimelineItemAction.Pin,
|
TimelineItemAction.Pin,
|
||||||
TimelineItemAction.CopyLink,
|
TimelineItemAction.CopyLink,
|
||||||
TimelineItemAction.Redact,
|
TimelineItemAction.Redact,
|
||||||
|
|
@ -1105,7 +1154,9 @@ class ActionListPresenterTest {
|
||||||
val messageEvent = aMessageEvent(
|
val messageEvent = aMessageEvent(
|
||||||
isMine = true,
|
isMine = true,
|
||||||
isEditable = false,
|
isEditable = false,
|
||||||
content = aTimelineItemVoiceContent(),
|
content = aTimelineItemVoiceContent(
|
||||||
|
caption = null,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
initialState.eventSink.invoke(
|
initialState.eventSink.invoke(
|
||||||
ActionListEvents.ComputeForMessage(
|
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 com.google.common.truth.Truth.assertThat
|
||||||
import im.vector.app.features.analytics.plan.PinUnpinAction
|
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.actionlist.model.TimelineItemAction
|
||||||
import io.element.android.features.messages.impl.fixtures.aTimelineItemsFactoryCreator
|
import io.element.android.features.messages.impl.fixtures.aTimelineItemsFactoryCreator
|
||||||
import io.element.android.features.messages.impl.pinned.PinnedEventsTimelineProvider
|
import io.element.android.features.messages.impl.pinned.PinnedEventsTimelineProvider
|
||||||
|
|
@ -312,7 +312,7 @@ class PinnedMessagesListPresenterTest {
|
||||||
timelineProvider = timelineProvider,
|
timelineProvider = timelineProvider,
|
||||||
timelineProtectionPresenter = { aTimelineProtectionState() },
|
timelineProtectionPresenter = { aTimelineProtectionState() },
|
||||||
snackbarDispatcher = SnackbarDispatcher(),
|
snackbarDispatcher = SnackbarDispatcher(),
|
||||||
actionListPresenterFactory = FakeActionListPresenter.Factory(),
|
actionListPresenter = { anActionListState() },
|
||||||
analyticsService = analyticsService,
|
analyticsService = analyticsService,
|
||||||
appCoroutineScope = this,
|
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_confirm_password">"Confirm password"</string>
|
||||||
<string name="action_continue">"Continue"</string>
|
<string name="action_continue">"Continue"</string>
|
||||||
<string name="action_copy">"Copy"</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">"Copy link"</string>
|
||||||
<string name="action_copy_link_to_message">"Copy link to message"</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">"Create"</string>
|
||||||
<string name="action_create_a_room">"Create a room"</string>
|
<string name="action_create_a_room">"Create a room"</string>
|
||||||
<string name="action_deactivate">"Deactivate"</string>
|
<string name="action_deactivate">"Deactivate"</string>
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:ab41e6b8fe6c235a4915d88a3d42e29cc84678a2b41bafbe38f55111632d1eec
|
oid sha256:e67a540966100272311381e87011149cdb15c8191a6f2bbc40d1febff999c431
|
||||||
size 31740
|
size 24067
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:2fb57605dad290919daa4c331442d69651e26db88235f77e4912ffb9631a36c0
|
oid sha256:65023f7233f112547ccd0850c321b2d6610cfb6a1371c494f68722e75874f871
|
||||||
size 45151
|
size 45915
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:baadc64d3b4f9464bc6063cc9cc10adfa256d25308c83f647e508f42fd45ef1c
|
oid sha256:e2c2e01bd133c7b84e381141b6baf22783cb585cb3af9f790785dbf8aeaeeed6
|
||||||
size 46852
|
size 47644
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:1009af703a11348dac63dc071333e077f4bba76c73bd64bf9e532381c7607c1d
|
oid sha256:9bd6fb63059cebc7dc7b850b5aa73549ab9846eda68b1b6d9a4f8e0716d42c3c
|
||||||
size 39624
|
size 40419
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:0f5adfb435286a84587a66d6970b1a7f03a8cb53eac86956f61092d97ea16951
|
oid sha256:6b4e4a075bcb4b95455c27ce2e5f9f1bdac4a1aca22ad944dd53fdbaeb6ca970
|
||||||
size 43121
|
size 44550
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:d3843655f4b2f55b5eb2cdb014e1ca03f9570e312e725fe074a5191e0350c079
|
oid sha256:1a2bc7e9098301d17e72a61a7baf01e52aa11566e1da4ffe3b43a66fa37652b2
|
||||||
size 40677
|
size 42103
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:ce1c3d6c04316fd9f43b86edb04ba209eb6df450f40438f98b8503fb116d4ea4
|
oid sha256:f097af2773ff2ecf70a69e4292dc118b4c8616f1c8f979e7d121e86a16ef3072
|
||||||
size 40271
|
size 38506
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:4839112543c6ab57848c1dd30440b12703da1a35a6457bf9fa5f5aabd1dd08b9
|
oid sha256:8f49870a5333cbaf1c6c3ac5ddcb85290d04efc79ffec93d8dc39b59f9712820
|
||||||
size 40963
|
size 42391
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:41528c85820cbe666c719536022fb52ce510055c8c8ef3f1ce36da32731f0a0b
|
oid sha256:883f160afa3fc3d2c0be5098205ea616875084ee0f122ebf994d0fee53a8ecd1
|
||||||
size 41615
|
size 39847
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:ac14f7921bedaf7135655775b524fde596120d6ea9b708ba5653b3805f567067
|
oid sha256:7790c892daeee8910ce17caac2c957d09fffec0d41a51b3adc1d5bef8dcee1a1
|
||||||
size 41469
|
size 42261
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:ce9244eb3b531a4c2816a32c8f806476cd0776f6394f32cebcb82f8a48cd64d7
|
oid sha256:bc7b733680a0d86ee1231345e58641fb3a208da21de62a50f624d9ae04c6e140
|
||||||
size 30837
|
size 31661
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:4aa886aa0f70c7c85fc25e9366792bd6e2c9e1a9b6c8864282834d9f72b3fcb2
|
oid sha256:20504466241e36817557812f6b378aceab9c2e271a596bd3d037c6be41af7c54
|
||||||
size 30809
|
size 23624
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:756255463c3747888878fef57ec4c3204075993d3fb5bdaf3d1bbe436648862c
|
oid sha256:48028e4c3ca7e9b871683165f029430bcd4e0fc2411e4ffc83a93abb641d96a2
|
||||||
size 44345
|
size 45132
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:2bdc999259dabe20157611f27925d6f0c8f9f77eebc1914fa8f793b62aa8c299
|
oid sha256:b040fe47521f5d9d76d7892412bb2b5cf7e2b93b951f5de5f772503797fb6b24
|
||||||
size 45864
|
size 46548
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:ffb565c4d790e24a2f395b12d8dbbfa1ba4abadd7cf9951deae049696e04b206
|
oid sha256:6db046a97f14f2df3db2415d71a02ffd6e706fe73def67c21f0d844be279da59
|
||||||
size 38843
|
size 39617
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:87d68af6f69c67de710145b3fabbff40bc6c9a767542e5a62be80d084972cb28
|
oid sha256:7ee53243923d7b5adcc2094d3fe6bb87be00a179cae11966dcb230f2c3e8246e
|
||||||
size 42221
|
size 43681
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:08c6685bec4751c852ce2440c5b3dd006d46b80dfd0f5986a3392278f486e181
|
oid sha256:b88e54ba4743e1e245b5a4c7d6b4045f816241f6cfc8716b8cede0b62666222b
|
||||||
size 39863
|
size 41286
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:04a1cbb1d1e98f87446ad911b61b002e34ceb2725ec24dfd884561d3585b9d83
|
oid sha256:d88b4d82bb0d416aff7b32647479181ac9702c36c08370e7671d6e5ef80687c7
|
||||||
size 39518
|
size 37825
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:d5ba01dbb6e8d1d238fcfdecbd67ee74bf833a3592087ec5ffc24b8ddeb85178
|
oid sha256:b3d4dd1766a3f46acd86bdd5ec920c56a5e5f897c81c1477df10ba59dcd3d5b7
|
||||||
size 40124
|
size 41554
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:dd84e6907dc39e1ad7e6db95d8453aa51e18b14c881499c05a233678fa0e7094
|
oid sha256:842fbea74e53c8804fd0a3e9fe96dcb21b5c91a099e1de33d8ec983fd9a8a80a
|
||||||
size 40762
|
size 39112
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:b5c35af3e85d906c6ed24fbb454cf32c7b25f87471e631af8b6ccc1d8465de00
|
oid sha256:1311ec5e008b44d81e6556164f780c57dea3e4721cc47caccdbf337dd4027eb7
|
||||||
size 40631
|
size 41402
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:486f9e142212d3864dfb936905feb37421fd4e8c9b5734c5f9ee4f29ec1166f6
|
oid sha256:d2cf27d2f053f33c67d944a8f45e0206165b2db2f3b959eb9e0bb943f84fe6a1
|
||||||
size 29964
|
size 30657
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue