Add action to copy permalink #2650
This commit is contained in:
parent
9d820798c9
commit
90a03cbd4c
10 changed files with 86 additions and 0 deletions
1
changelog.d/2650.feature
Normal file
1
changelog.d/2650.feature
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Add action to copy permalink
|
||||||
|
|
@ -89,6 +89,7 @@ import io.element.android.libraries.matrix.ui.room.canRedactOtherAsState
|
||||||
import io.element.android.libraries.matrix.ui.room.canRedactOwnAsState
|
import io.element.android.libraries.matrix.ui.room.canRedactOwnAsState
|
||||||
import io.element.android.libraries.matrix.ui.room.canSendMessageAsState
|
import io.element.android.libraries.matrix.ui.room.canSendMessageAsState
|
||||||
import io.element.android.libraries.textcomposer.model.MessageComposerMode
|
import io.element.android.libraries.textcomposer.model.MessageComposerMode
|
||||||
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
@ -273,6 +274,7 @@ class MessagesPresenter @AssistedInject constructor(
|
||||||
) = launch {
|
) = launch {
|
||||||
when (action) {
|
when (action) {
|
||||||
TimelineItemAction.Copy -> handleCopyContents(targetEvent)
|
TimelineItemAction.Copy -> handleCopyContents(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)
|
||||||
TimelineItemAction.Reply,
|
TimelineItemAction.Reply,
|
||||||
|
|
@ -435,6 +437,20 @@ class MessagesPresenter @AssistedInject constructor(
|
||||||
event.eventId?.let { timelineState.eventSink(TimelineEvents.PollEndClicked(it)) }
|
event.eventId?.let { timelineState.eventSink(TimelineEvents.PollEndClicked(it)) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun handleCopyLink(event: TimelineItem.Event) {
|
||||||
|
event.eventId ?: return
|
||||||
|
room.getPermalinkFor(event.eventId).fold(
|
||||||
|
onSuccess = { permalink ->
|
||||||
|
clipboardHelper.copyPlainText(permalink)
|
||||||
|
snackbarDispatcher.post(SnackbarMessage(CommonStrings.common_link_copied_to_clipboard))
|
||||||
|
},
|
||||||
|
onFailure = {
|
||||||
|
Timber.e(it, "Failed to get permalink for event ${event.eventId}")
|
||||||
|
snackbarDispatcher.post(SnackbarMessage(CommonStrings.common_error))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun handleCopyContents(event: TimelineItem.Event) {
|
private suspend fun handleCopyContents(event: TimelineItem.Event) {
|
||||||
val content = when (event.content) {
|
val content = when (event.content) {
|
||||||
is TimelineItemTextBasedContent -> event.content.body
|
is TimelineItemTextBasedContent -> event.content.body
|
||||||
|
|
|
||||||
|
|
@ -96,6 +96,7 @@ class ActionListPresenter @Inject constructor(
|
||||||
is TimelineItemStateContent -> {
|
is TimelineItemStateContent -> {
|
||||||
buildList {
|
buildList {
|
||||||
add(TimelineItemAction.Copy)
|
add(TimelineItemAction.Copy)
|
||||||
|
add(TimelineItemAction.CopyLink)
|
||||||
if (isDeveloperModeEnabled) {
|
if (isDeveloperModeEnabled) {
|
||||||
add(TimelineItemAction.ViewSource)
|
add(TimelineItemAction.ViewSource)
|
||||||
}
|
}
|
||||||
|
|
@ -119,6 +120,7 @@ class ActionListPresenter @Inject constructor(
|
||||||
if (timelineItem.content.canBeCopied()) {
|
if (timelineItem.content.canBeCopied()) {
|
||||||
add(TimelineItemAction.Copy)
|
add(TimelineItemAction.Copy)
|
||||||
}
|
}
|
||||||
|
add(TimelineItemAction.CopyLink)
|
||||||
if (isDeveloperModeEnabled) {
|
if (isDeveloperModeEnabled) {
|
||||||
add(TimelineItemAction.ViewSource)
|
add(TimelineItemAction.ViewSource)
|
||||||
}
|
}
|
||||||
|
|
@ -136,6 +138,7 @@ class ActionListPresenter @Inject constructor(
|
||||||
add(TimelineItemAction.Reply)
|
add(TimelineItemAction.Reply)
|
||||||
add(TimelineItemAction.Forward)
|
add(TimelineItemAction.Forward)
|
||||||
}
|
}
|
||||||
|
add(TimelineItemAction.CopyLink)
|
||||||
if (isDeveloperModeEnabled) {
|
if (isDeveloperModeEnabled) {
|
||||||
add(TimelineItemAction.ViewSource)
|
add(TimelineItemAction.ViewSource)
|
||||||
}
|
}
|
||||||
|
|
@ -176,6 +179,7 @@ class ActionListPresenter @Inject constructor(
|
||||||
if (timelineItem.content.canBeCopied()) {
|
if (timelineItem.content.canBeCopied()) {
|
||||||
add(TimelineItemAction.Copy)
|
add(TimelineItemAction.Copy)
|
||||||
}
|
}
|
||||||
|
add(TimelineItemAction.CopyLink)
|
||||||
if (isDeveloperModeEnabled) {
|
if (isDeveloperModeEnabled) {
|
||||||
add(TimelineItemAction.ViewSource)
|
add(TimelineItemAction.ViewSource)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -135,6 +135,7 @@ fun aTimelineItemActionList(): ImmutableList<TimelineItemAction> {
|
||||||
TimelineItemAction.Reply,
|
TimelineItemAction.Reply,
|
||||||
TimelineItemAction.Forward,
|
TimelineItemAction.Forward,
|
||||||
TimelineItemAction.Copy,
|
TimelineItemAction.Copy,
|
||||||
|
TimelineItemAction.CopyLink,
|
||||||
TimelineItemAction.Edit,
|
TimelineItemAction.Edit,
|
||||||
TimelineItemAction.Redact,
|
TimelineItemAction.Redact,
|
||||||
TimelineItemAction.ReportContent,
|
TimelineItemAction.ReportContent,
|
||||||
|
|
@ -146,6 +147,7 @@ fun aTimelineItemPollActionList(): ImmutableList<TimelineItemAction> {
|
||||||
TimelineItemAction.EndPoll,
|
TimelineItemAction.EndPoll,
|
||||||
TimelineItemAction.Reply,
|
TimelineItemAction.Reply,
|
||||||
TimelineItemAction.Copy,
|
TimelineItemAction.Copy,
|
||||||
|
TimelineItemAction.CopyLink,
|
||||||
TimelineItemAction.ViewSource,
|
TimelineItemAction.ViewSource,
|
||||||
TimelineItemAction.ReportContent,
|
TimelineItemAction.ReportContent,
|
||||||
TimelineItemAction.Redact,
|
TimelineItemAction.Redact,
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ sealed class TimelineItemAction(
|
||||||
) {
|
) {
|
||||||
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 Copy : TimelineItemAction(CommonStrings.action_copy, 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 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)
|
||||||
data object ReplyInThread : TimelineItemAction(CommonStrings.action_reply_in_thread, CompoundDrawables.ic_compound_reply)
|
data object ReplyInThread : TimelineItemAction(CommonStrings.action_reply_in_thread, CompoundDrawables.ic_compound_reply)
|
||||||
|
|
|
||||||
|
|
@ -233,6 +233,27 @@ class MessagesPresenterTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - handle action copy link`() = runTest {
|
||||||
|
val clipboardHelper = FakeClipboardHelper()
|
||||||
|
val event = aMessageEvent()
|
||||||
|
val matrixRoom = FakeMatrixRoom(
|
||||||
|
permalinkResult = { Result.success("a link") },
|
||||||
|
)
|
||||||
|
val presenter = createMessagesPresenter(
|
||||||
|
clipboardHelper = clipboardHelper,
|
||||||
|
matrixRoom = matrixRoom,
|
||||||
|
)
|
||||||
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
|
presenter.present()
|
||||||
|
}.test {
|
||||||
|
val initialState = awaitFirstItem()
|
||||||
|
initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.CopyLink, event))
|
||||||
|
assertThat(awaitItem().actionListState.target).isEqualTo(ActionListState.Target.None)
|
||||||
|
assertThat(clipboardHelper.clipboardContents).isEqualTo("a link")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - handle action reply`() = runTest {
|
fun `present - handle action reply`() = runTest {
|
||||||
val presenter = createMessagesPresenter()
|
val presenter = createMessagesPresenter()
|
||||||
|
|
|
||||||
|
|
@ -153,6 +153,7 @@ class ActionListPresenterTest {
|
||||||
TimelineItemAction.Reply,
|
TimelineItemAction.Reply,
|
||||||
TimelineItemAction.Forward,
|
TimelineItemAction.Forward,
|
||||||
TimelineItemAction.Copy,
|
TimelineItemAction.Copy,
|
||||||
|
TimelineItemAction.CopyLink,
|
||||||
TimelineItemAction.ViewSource,
|
TimelineItemAction.ViewSource,
|
||||||
TimelineItemAction.ReportContent,
|
TimelineItemAction.ReportContent,
|
||||||
)
|
)
|
||||||
|
|
@ -193,6 +194,7 @@ class ActionListPresenterTest {
|
||||||
actions = persistentListOf(
|
actions = persistentListOf(
|
||||||
TimelineItemAction.Forward,
|
TimelineItemAction.Forward,
|
||||||
TimelineItemAction.Copy,
|
TimelineItemAction.Copy,
|
||||||
|
TimelineItemAction.CopyLink,
|
||||||
TimelineItemAction.ViewSource,
|
TimelineItemAction.ViewSource,
|
||||||
TimelineItemAction.ReportContent,
|
TimelineItemAction.ReportContent,
|
||||||
)
|
)
|
||||||
|
|
@ -232,6 +234,7 @@ class ActionListPresenterTest {
|
||||||
TimelineItemAction.Reply,
|
TimelineItemAction.Reply,
|
||||||
TimelineItemAction.Forward,
|
TimelineItemAction.Forward,
|
||||||
TimelineItemAction.Copy,
|
TimelineItemAction.Copy,
|
||||||
|
TimelineItemAction.CopyLink,
|
||||||
TimelineItemAction.ViewSource,
|
TimelineItemAction.ViewSource,
|
||||||
TimelineItemAction.ReportContent,
|
TimelineItemAction.ReportContent,
|
||||||
TimelineItemAction.Redact,
|
TimelineItemAction.Redact,
|
||||||
|
|
@ -272,6 +275,7 @@ class ActionListPresenterTest {
|
||||||
TimelineItemAction.Reply,
|
TimelineItemAction.Reply,
|
||||||
TimelineItemAction.Forward,
|
TimelineItemAction.Forward,
|
||||||
TimelineItemAction.Copy,
|
TimelineItemAction.Copy,
|
||||||
|
TimelineItemAction.CopyLink,
|
||||||
TimelineItemAction.ViewSource,
|
TimelineItemAction.ViewSource,
|
||||||
TimelineItemAction.ReportContent,
|
TimelineItemAction.ReportContent,
|
||||||
TimelineItemAction.Redact,
|
TimelineItemAction.Redact,
|
||||||
|
|
@ -315,6 +319,7 @@ class ActionListPresenterTest {
|
||||||
TimelineItemAction.Forward,
|
TimelineItemAction.Forward,
|
||||||
TimelineItemAction.Edit,
|
TimelineItemAction.Edit,
|
||||||
TimelineItemAction.Copy,
|
TimelineItemAction.Copy,
|
||||||
|
TimelineItemAction.CopyLink,
|
||||||
TimelineItemAction.ViewSource,
|
TimelineItemAction.ViewSource,
|
||||||
TimelineItemAction.Redact,
|
TimelineItemAction.Redact,
|
||||||
)
|
)
|
||||||
|
|
@ -357,6 +362,7 @@ class ActionListPresenterTest {
|
||||||
TimelineItemAction.Forward,
|
TimelineItemAction.Forward,
|
||||||
TimelineItemAction.Edit,
|
TimelineItemAction.Edit,
|
||||||
TimelineItemAction.Copy,
|
TimelineItemAction.Copy,
|
||||||
|
TimelineItemAction.CopyLink,
|
||||||
TimelineItemAction.ViewSource,
|
TimelineItemAction.ViewSource,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -396,6 +402,7 @@ class ActionListPresenterTest {
|
||||||
actions = persistentListOf(
|
actions = persistentListOf(
|
||||||
TimelineItemAction.Reply,
|
TimelineItemAction.Reply,
|
||||||
TimelineItemAction.Forward,
|
TimelineItemAction.Forward,
|
||||||
|
TimelineItemAction.CopyLink,
|
||||||
TimelineItemAction.ViewSource,
|
TimelineItemAction.ViewSource,
|
||||||
TimelineItemAction.Redact,
|
TimelineItemAction.Redact,
|
||||||
)
|
)
|
||||||
|
|
@ -435,6 +442,7 @@ class ActionListPresenterTest {
|
||||||
displayEmojiReactions = false,
|
displayEmojiReactions = false,
|
||||||
actions = persistentListOf(
|
actions = persistentListOf(
|
||||||
TimelineItemAction.Copy,
|
TimelineItemAction.Copy,
|
||||||
|
TimelineItemAction.CopyLink,
|
||||||
TimelineItemAction.ViewSource,
|
TimelineItemAction.ViewSource,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -473,6 +481,7 @@ class ActionListPresenterTest {
|
||||||
displayEmojiReactions = false,
|
displayEmojiReactions = false,
|
||||||
actions = persistentListOf(
|
actions = persistentListOf(
|
||||||
TimelineItemAction.Copy,
|
TimelineItemAction.Copy,
|
||||||
|
TimelineItemAction.CopyLink,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -513,6 +522,7 @@ class ActionListPresenterTest {
|
||||||
TimelineItemAction.Forward,
|
TimelineItemAction.Forward,
|
||||||
TimelineItemAction.Edit,
|
TimelineItemAction.Edit,
|
||||||
TimelineItemAction.Copy,
|
TimelineItemAction.Copy,
|
||||||
|
TimelineItemAction.CopyLink,
|
||||||
TimelineItemAction.Redact,
|
TimelineItemAction.Redact,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -595,6 +605,7 @@ class ActionListPresenterTest {
|
||||||
actions = persistentListOf(
|
actions = persistentListOf(
|
||||||
TimelineItemAction.Edit,
|
TimelineItemAction.Edit,
|
||||||
TimelineItemAction.Copy,
|
TimelineItemAction.Copy,
|
||||||
|
TimelineItemAction.CopyLink,
|
||||||
TimelineItemAction.Redact,
|
TimelineItemAction.Redact,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -632,6 +643,7 @@ class ActionListPresenterTest {
|
||||||
TimelineItemAction.Reply,
|
TimelineItemAction.Reply,
|
||||||
TimelineItemAction.Edit,
|
TimelineItemAction.Edit,
|
||||||
TimelineItemAction.EndPoll,
|
TimelineItemAction.EndPoll,
|
||||||
|
TimelineItemAction.CopyLink,
|
||||||
TimelineItemAction.Redact,
|
TimelineItemAction.Redact,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -668,6 +680,7 @@ class ActionListPresenterTest {
|
||||||
actions = persistentListOf(
|
actions = persistentListOf(
|
||||||
TimelineItemAction.Reply,
|
TimelineItemAction.Reply,
|
||||||
TimelineItemAction.EndPoll,
|
TimelineItemAction.EndPoll,
|
||||||
|
TimelineItemAction.CopyLink,
|
||||||
TimelineItemAction.Redact,
|
TimelineItemAction.Redact,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -703,6 +716,7 @@ class ActionListPresenterTest {
|
||||||
displayEmojiReactions = true,
|
displayEmojiReactions = true,
|
||||||
actions = persistentListOf(
|
actions = persistentListOf(
|
||||||
TimelineItemAction.Reply,
|
TimelineItemAction.Reply,
|
||||||
|
TimelineItemAction.CopyLink,
|
||||||
TimelineItemAction.Redact,
|
TimelineItemAction.Redact,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -738,6 +752,7 @@ class ActionListPresenterTest {
|
||||||
actions = persistentListOf(
|
actions = persistentListOf(
|
||||||
TimelineItemAction.Reply,
|
TimelineItemAction.Reply,
|
||||||
TimelineItemAction.Forward,
|
TimelineItemAction.Forward,
|
||||||
|
TimelineItemAction.CopyLink,
|
||||||
TimelineItemAction.Redact,
|
TimelineItemAction.Redact,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -328,5 +328,12 @@ interface MatrixRoom : Closeable {
|
||||||
*/
|
*/
|
||||||
fun getWidgetDriver(widgetSettings: MatrixWidgetSettings): Result<MatrixWidgetDriver>
|
fun getWidgetDriver(widgetSettings: MatrixWidgetSettings): Result<MatrixWidgetDriver>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the permalink for the provided [eventId].
|
||||||
|
* @param eventId The event id to get the permalink for.
|
||||||
|
* @return The permalink, or a failure.
|
||||||
|
*/
|
||||||
|
suspend fun getPermalinkFor(eventId: EventId): Result<String>
|
||||||
|
|
||||||
override fun close() = destroy()
|
override fun close() = destroy()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
package io.element.android.libraries.matrix.impl.room
|
package io.element.android.libraries.matrix.impl.room
|
||||||
|
|
||||||
|
import io.element.android.appconfig.MatrixConfiguration
|
||||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||||
import io.element.android.libraries.core.coroutine.childScope
|
import io.element.android.libraries.core.coroutine.childScope
|
||||||
import io.element.android.libraries.matrix.api.core.EventId
|
import io.element.android.libraries.matrix.api.core.EventId
|
||||||
|
|
@ -711,6 +712,19 @@ class RustMatrixRoom(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun getPermalinkFor(eventId: EventId): Result<String> {
|
||||||
|
// FIXME Use the SDK API once https://github.com/matrix-org/matrix-rust-sdk/issues/3259 has been done
|
||||||
|
// Now use a simple builder
|
||||||
|
return runCatching {
|
||||||
|
buildString {
|
||||||
|
append(MatrixConfiguration.MATRIX_TO_PERMALINK_BASE_URL)
|
||||||
|
append(roomId.value)
|
||||||
|
append("/")
|
||||||
|
append(eventId.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun sendAttachment(files: List<File>, handle: () -> SendAttachmentJoinHandle): Result<MediaUploadHandler> {
|
private fun sendAttachment(files: List<File>, handle: () -> SendAttachmentJoinHandle): Result<MediaUploadHandler> {
|
||||||
return runCatching {
|
return runCatching {
|
||||||
MediaUploadHandlerImpl(files, handle())
|
MediaUploadHandlerImpl(files, handle())
|
||||||
|
|
|
||||||
|
|
@ -84,6 +84,7 @@ class FakeMatrixRoom(
|
||||||
override val activeMemberCount: Long = 234L,
|
override val activeMemberCount: Long = 234L,
|
||||||
val notificationSettingsService: NotificationSettingsService = FakeNotificationSettingsService(),
|
val notificationSettingsService: NotificationSettingsService = FakeNotificationSettingsService(),
|
||||||
private val matrixTimeline: MatrixTimeline = FakeMatrixTimeline(),
|
private val matrixTimeline: MatrixTimeline = FakeMatrixTimeline(),
|
||||||
|
private var permalinkResult: () -> Result<String> = { Result.success("link") },
|
||||||
canRedactOwn: Boolean = false,
|
canRedactOwn: Boolean = false,
|
||||||
canRedactOther: Boolean = false,
|
canRedactOther: Boolean = false,
|
||||||
) : MatrixRoom {
|
) : MatrixRoom {
|
||||||
|
|
@ -273,6 +274,10 @@ class FakeMatrixRoom(
|
||||||
return cancelSendResult
|
return cancelSendResult
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun getPermalinkFor(eventId: EventId): Result<String> {
|
||||||
|
return permalinkResult()
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun editMessage(
|
override suspend fun editMessage(
|
||||||
originalEventId: EventId?,
|
originalEventId: EventId?,
|
||||||
transactionId: TransactionId?,
|
transactionId: TransactionId?,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue