mirror of
https://github.com/Novaloop-AG/element-x-android.git
synced 2026-11-03 16:10:00 +00:00
Forward several events at once
This commit is contained in:
+1
-1
@@ -23,7 +23,7 @@ interface ForwardEntryPoint : FeatureEntryPoint {
|
|||||||
}
|
}
|
||||||
|
|
||||||
data class Params(
|
data class Params(
|
||||||
val eventId: EventId,
|
val eventIds: List<EventId>,
|
||||||
val timelineProvider: TimelineProvider,
|
val timelineProvider: TimelineProvider,
|
||||||
) : NodeInputs
|
) : NodeInputs
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -27,7 +27,7 @@ class DefaultForwardEntryPoint : ForwardEntryPoint {
|
|||||||
buildContext = buildContext,
|
buildContext = buildContext,
|
||||||
plugins = listOf(
|
plugins = listOf(
|
||||||
ForwardMessagesNode.Inputs(
|
ForwardMessagesNode.Inputs(
|
||||||
eventId = params.eventId,
|
eventIds = params.eventIds,
|
||||||
timelineProvider = params.timelineProvider,
|
timelineProvider = params.timelineProvider,
|
||||||
),
|
),
|
||||||
callback,
|
callback,
|
||||||
|
|||||||
+2
-2
@@ -52,13 +52,13 @@ class ForwardMessagesNode(
|
|||||||
object NavTarget : Parcelable
|
object NavTarget : Parcelable
|
||||||
|
|
||||||
data class Inputs(
|
data class Inputs(
|
||||||
val eventId: EventId,
|
val eventIds: List<EventId>,
|
||||||
val timelineProvider: TimelineProvider,
|
val timelineProvider: TimelineProvider,
|
||||||
) : NodeInputs
|
) : NodeInputs
|
||||||
|
|
||||||
private val inputs = inputs<Inputs>()
|
private val inputs = inputs<Inputs>()
|
||||||
private val callback: ForwardEntryPoint.Callback = callback()
|
private val callback: ForwardEntryPoint.Callback = callback()
|
||||||
private val presenter = presenterFactory.create(inputs.eventId.value, inputs.timelineProvider)
|
private val presenter = presenterFactory.create(inputs.eventIds, inputs.timelineProvider)
|
||||||
|
|
||||||
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
|
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
|
||||||
val callback = object : RoomSelectEntryPoint.Callback {
|
val callback = object : RoomSelectEntryPoint.Callback {
|
||||||
|
|||||||
+7
-9
@@ -28,22 +28,20 @@ import timber.log.Timber
|
|||||||
|
|
||||||
@AssistedInject
|
@AssistedInject
|
||||||
class ForwardMessagesPresenter(
|
class ForwardMessagesPresenter(
|
||||||
@Assisted eventId: String,
|
@Assisted private val eventIds: List<EventId>,
|
||||||
@Assisted private val timelineProvider: TimelineProvider,
|
@Assisted private val timelineProvider: TimelineProvider,
|
||||||
@SessionCoroutineScope
|
@SessionCoroutineScope
|
||||||
private val sessionCoroutineScope: CoroutineScope,
|
private val sessionCoroutineScope: CoroutineScope,
|
||||||
) : Presenter<ForwardMessagesState> {
|
) : Presenter<ForwardMessagesState> {
|
||||||
private val eventId: EventId = EventId(eventId)
|
|
||||||
|
|
||||||
@AssistedFactory
|
@AssistedFactory
|
||||||
fun interface Factory {
|
fun interface Factory {
|
||||||
fun create(eventId: String, timelineProvider: TimelineProvider): ForwardMessagesPresenter
|
fun create(eventIds: List<EventId>, timelineProvider: TimelineProvider): ForwardMessagesPresenter
|
||||||
}
|
}
|
||||||
|
|
||||||
private val forwardingActionState: MutableState<AsyncAction<List<RoomId>>> = mutableStateOf(AsyncAction.Uninitialized)
|
private val forwardingActionState: MutableState<AsyncAction<List<RoomId>>> = mutableStateOf(AsyncAction.Uninitialized)
|
||||||
|
|
||||||
fun onRoomSelected(roomIds: List<RoomId>) {
|
fun onRoomSelected(roomIds: List<RoomId>) {
|
||||||
sessionCoroutineScope.forwardEvent(eventId, roomIds)
|
sessionCoroutineScope.forwardEvents(eventIds, roomIds)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -60,14 +58,14 @@ class ForwardMessagesPresenter(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun CoroutineScope.forwardEvent(
|
private fun CoroutineScope.forwardEvents(
|
||||||
eventId: EventId,
|
eventIds: List<EventId>,
|
||||||
roomIds: List<RoomId>,
|
roomIds: List<RoomId>,
|
||||||
) = launch {
|
) = launch {
|
||||||
suspend {
|
suspend {
|
||||||
timelineProvider.getActiveTimeline().forwardEvent(eventId, roomIds)
|
timelineProvider.getActiveTimeline().forwardEvents(eventIds, roomIds)
|
||||||
.onFailure {
|
.onFailure {
|
||||||
Timber.e(it, "Error while forwarding event")
|
Timber.e(it, "Error while forwarding events")
|
||||||
}
|
}
|
||||||
.getOrThrow()
|
.getOrThrow()
|
||||||
roomIds
|
roomIds
|
||||||
|
|||||||
+2
-2
@@ -45,7 +45,7 @@ class DefaultForwardEntryPointTest {
|
|||||||
override fun onDone(roomIds: List<RoomId>) = lambdaError()
|
override fun onDone(roomIds: List<RoomId>) = lambdaError()
|
||||||
}
|
}
|
||||||
val params = ForwardEntryPoint.Params(
|
val params = ForwardEntryPoint.Params(
|
||||||
eventId = AN_EVENT_ID,
|
eventIds = listOf(AN_EVENT_ID),
|
||||||
timelineProvider = FakeTimelineProvider(),
|
timelineProvider = FakeTimelineProvider(),
|
||||||
)
|
)
|
||||||
val result = entryPoint.createNode(
|
val result = entryPoint.createNode(
|
||||||
@@ -57,7 +57,7 @@ class DefaultForwardEntryPointTest {
|
|||||||
assertThat(result).isInstanceOf(ForwardMessagesNode::class.java)
|
assertThat(result).isInstanceOf(ForwardMessagesNode::class.java)
|
||||||
assertThat(result.plugins).contains(
|
assertThat(result.plugins).contains(
|
||||||
ForwardMessagesNode.Inputs(
|
ForwardMessagesNode.Inputs(
|
||||||
eventId = params.eventId,
|
eventIds = params.eventIds,
|
||||||
timelineProvider = params.timelineProvider,
|
timelineProvider = params.timelineProvider,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
+8
-8
@@ -44,11 +44,11 @@ class ForwardMessagesPresenterTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - forward successful`() = runTest {
|
fun `present - forward successful`() = runTest {
|
||||||
val forwardEventLambda = lambdaRecorder { _: EventId, _: List<RoomId> ->
|
val forwardEventsLambda = lambdaRecorder { _: List<EventId>, _: List<RoomId> ->
|
||||||
Result.success(Unit)
|
Result.success(Unit)
|
||||||
}
|
}
|
||||||
val timeline = FakeTimeline().apply {
|
val timeline = FakeTimeline().apply {
|
||||||
this.forwardEventLambda = forwardEventLambda
|
this.forwardEventsLambda = forwardEventsLambda
|
||||||
}
|
}
|
||||||
val room = FakeJoinedRoom(liveTimeline = timeline)
|
val room = FakeJoinedRoom(liveTimeline = timeline)
|
||||||
val presenter = createForwardMessagesPresenter(fakeRoom = room)
|
val presenter = createForwardMessagesPresenter(fakeRoom = room)
|
||||||
@@ -62,17 +62,17 @@ class ForwardMessagesPresenterTest {
|
|||||||
assertThat(forwardingState.forwardAction.isLoading()).isTrue()
|
assertThat(forwardingState.forwardAction.isLoading()).isTrue()
|
||||||
val successfulForwardState = awaitItem()
|
val successfulForwardState = awaitItem()
|
||||||
assertThat(successfulForwardState.forwardAction).isEqualTo(AsyncAction.Success(listOf(summary.roomId)))
|
assertThat(successfulForwardState.forwardAction).isEqualTo(AsyncAction.Success(listOf(summary.roomId)))
|
||||||
forwardEventLambda.assertions().isCalledOnce()
|
forwardEventsLambda.assertions().isCalledOnce()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - select a room and forward failed, then clear`() = runTest {
|
fun `present - select a room and forward failed, then clear`() = runTest {
|
||||||
val forwardEventLambda = lambdaRecorder { _: EventId, _: List<RoomId> ->
|
val forwardEventsLambda = lambdaRecorder { _: List<EventId>, _: List<RoomId> ->
|
||||||
Result.failure<Unit>(IllegalStateException("error"))
|
Result.failure<Unit>(IllegalStateException("error"))
|
||||||
}
|
}
|
||||||
val timeline = FakeTimeline().apply {
|
val timeline = FakeTimeline().apply {
|
||||||
this.forwardEventLambda = forwardEventLambda
|
this.forwardEventsLambda = forwardEventsLambda
|
||||||
}
|
}
|
||||||
val room = FakeJoinedRoom(liveTimeline = timeline)
|
val room = FakeJoinedRoom(liveTimeline = timeline)
|
||||||
val presenter = createForwardMessagesPresenter(fakeRoom = room)
|
val presenter = createForwardMessagesPresenter(fakeRoom = room)
|
||||||
@@ -88,16 +88,16 @@ class ForwardMessagesPresenterTest {
|
|||||||
// Then clear error
|
// Then clear error
|
||||||
failedForwardState.eventSink(ForwardMessagesEvent.ClearError)
|
failedForwardState.eventSink(ForwardMessagesEvent.ClearError)
|
||||||
assertThat(awaitItem().forwardAction.isUninitialized()).isTrue()
|
assertThat(awaitItem().forwardAction.isUninitialized()).isTrue()
|
||||||
forwardEventLambda.assertions().isCalledOnce()
|
forwardEventsLambda.assertions().isCalledOnce()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun TestScope.createForwardMessagesPresenter(
|
fun TestScope.createForwardMessagesPresenter(
|
||||||
eventId: EventId = AN_EVENT_ID,
|
eventIds: List<EventId> = listOf(AN_EVENT_ID),
|
||||||
fakeRoom: FakeJoinedRoom = FakeJoinedRoom(),
|
fakeRoom: FakeJoinedRoom = FakeJoinedRoom(),
|
||||||
) = ForwardMessagesPresenter(
|
) = ForwardMessagesPresenter(
|
||||||
eventId = eventId.value,
|
eventIds = eventIds,
|
||||||
timelineProvider = LiveTimelineProvider(fakeRoom),
|
timelineProvider = LiveTimelineProvider(fakeRoom),
|
||||||
sessionCoroutineScope = this,
|
sessionCoroutineScope = this,
|
||||||
)
|
)
|
||||||
|
|||||||
+5
-4
@@ -316,12 +316,13 @@ interface Timeline : AutoCloseable {
|
|||||||
suspend fun toggleReaction(emoji: String, eventOrTransactionId: EventOrTransactionId): Result<Boolean>
|
suspend fun toggleReaction(emoji: String, eventOrTransactionId: EventOrTransactionId): Result<Boolean>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Forwards the content of an event to other rooms, as new messages sent by the current user.
|
* Forwards the content of some events to other rooms, as new messages sent by the current user.
|
||||||
|
* The events are forwarded in the order of [eventIds].
|
||||||
*
|
*
|
||||||
* @param eventId the event whose content is forwarded.
|
* @param eventIds the events whose content is forwarded.
|
||||||
* @param roomIds the rooms to forward it to.
|
* @param roomIds the rooms to forward them to.
|
||||||
*/
|
*/
|
||||||
suspend fun forwardEvent(eventId: EventId, roomIds: List<RoomId>): Result<Unit>
|
suspend fun forwardEvents(eventIds: List<EventId>, roomIds: List<RoomId>): Result<Unit>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cancels an event that is still waiting in the send queue, by redacting its local echo.
|
* Cancels an event that is still waiting in the send queue, by redacting its local echo.
|
||||||
|
|||||||
+19
-17
@@ -14,7 +14,6 @@ import io.element.android.libraries.matrix.api.core.EventId
|
|||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import io.element.android.libraries.matrix.api.room.ForwardEventException
|
import io.element.android.libraries.matrix.api.room.ForwardEventException
|
||||||
import io.element.android.libraries.matrix.impl.roomlist.roomOrNull
|
import io.element.android.libraries.matrix.impl.roomlist.roomOrNull
|
||||||
import io.element.android.libraries.matrix.impl.timeline.runWithTimelineListenerRegistered
|
|
||||||
import kotlinx.coroutines.withTimeout
|
import kotlinx.coroutines.withTimeout
|
||||||
import org.matrix.rustcomponents.sdk.MsgLikeKind
|
import org.matrix.rustcomponents.sdk.MsgLikeKind
|
||||||
import org.matrix.rustcomponents.sdk.RoomListService
|
import org.matrix.rustcomponents.sdk.RoomListService
|
||||||
@@ -31,33 +30,36 @@ class RoomContentForwarder(
|
|||||||
private val roomListService: RoomListService,
|
private val roomListService: RoomListService,
|
||||||
) {
|
) {
|
||||||
/**
|
/**
|
||||||
* Forwards the event with the given [eventId] from the [fromTimeline] to the given [toRoomIds].
|
* Forwards the events with the given [eventIds] from the [fromTimeline] to the given [toRoomIds].
|
||||||
* @param fromTimeline the room to forward the event from
|
* The events are sent to each room in the order they are provided in [eventIds], it is up to the caller to sort them.
|
||||||
* @param eventId the id of the event to forward
|
* @param fromTimeline the room to forward the events from
|
||||||
* @param toRoomIds the ids of the rooms to forward the event to
|
* @param eventIds the ids of the events to forward, in the order they must be sent
|
||||||
* @param timeoutMs the maximum time in milliseconds to wait for the event to be sent to a room
|
* @param toRoomIds the ids of the rooms to forward the events to
|
||||||
|
* @param timeoutMs the maximum time in milliseconds to wait for an event to be sent to a room
|
||||||
*/
|
*/
|
||||||
suspend fun forward(
|
suspend fun forward(
|
||||||
fromTimeline: Timeline,
|
fromTimeline: Timeline,
|
||||||
eventId: EventId,
|
eventIds: List<EventId>,
|
||||||
toRoomIds: List<RoomId>,
|
toRoomIds: List<RoomId>,
|
||||||
timeoutMs: Long = 5000L
|
timeoutMs: Long = 5000L
|
||||||
) {
|
) {
|
||||||
val messageLikeContent = (fromTimeline.getEventTimelineItemByEventId(eventId.value).content as? TimelineItemContent.MsgLike)?.content
|
val contents = eventIds.map { eventId ->
|
||||||
?: throw ForwardEventException(toRoomIds)
|
val timelineEvent = fromTimeline.getEventTimelineItemByEventId(eventId.value)
|
||||||
|
val content = timelineEvent.content as? TimelineItemContent.MsgLike ?: throw ForwardEventException(toRoomIds)
|
||||||
val content = (messageLikeContent.kind as? MsgLikeKind.Message)?.content
|
val message = content.content.kind as? MsgLikeKind.Message ?: throw ForwardEventException(toRoomIds)
|
||||||
?: throw ForwardEventException(toRoomIds)
|
contentWithoutRelationFromMessage(message.content)
|
||||||
|
}
|
||||||
val targetRooms = toRoomIds.toSet().mapNotNull { roomId -> roomListService.roomOrNull(roomId.value) }
|
val targetRooms = toRoomIds.toSet().mapNotNull { roomId -> roomListService.roomOrNull(roomId.value) }
|
||||||
val failedForwardingTo = mutableSetOf<RoomId>()
|
val failedForwardingTo = mutableSetOf<RoomId>()
|
||||||
targetRooms.parallelMap { room ->
|
targetRooms.parallelMap { room ->
|
||||||
room.use { targetRoom ->
|
room.use { targetRoom ->
|
||||||
runCatchingExceptions {
|
runCatchingExceptions {
|
||||||
// Sending a message requires a registered timeline listener
|
targetRoom.timeline().use { timeline ->
|
||||||
targetRoom.timeline().runWithTimelineListenerRegistered {
|
// Send the contents sequentially, so that they are received in the same order
|
||||||
withTimeout(timeoutMs.milliseconds) {
|
contents.forEach { content ->
|
||||||
targetRoom.timeline().send(contentWithoutRelationFromMessage(content))
|
withTimeout(timeoutMs.milliseconds) {
|
||||||
|
timeline.send(content)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
-13
@@ -53,16 +53,3 @@ internal fun TimelineInterface.timelineDiffFlow(): Flow<List<TimelineDiff>> =
|
|||||||
}.catch {
|
}.catch {
|
||||||
Timber.d(it, "timelineDiffFlow() failed")
|
Timber.d(it, "timelineDiffFlow() failed")
|
||||||
}.buffer(Channel.UNLIMITED)
|
}.buffer(Channel.UNLIMITED)
|
||||||
|
|
||||||
internal suspend fun TimelineInterface.runWithTimelineListenerRegistered(action: suspend () -> Unit) {
|
|
||||||
val result = addListener(NoOpTimelineListener)
|
|
||||||
try {
|
|
||||||
action()
|
|
||||||
} finally {
|
|
||||||
result.cancelAndDestroy()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private object NoOpTimelineListener : TimelineListener {
|
|
||||||
override fun onUpdate(diff: List<TimelineDiff>) = Unit
|
|
||||||
}
|
|
||||||
|
|||||||
+2
-2
@@ -489,9 +489,9 @@ class RustTimeline(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun forwardEvent(eventId: EventId, roomIds: List<RoomId>): Result<Unit> = withContext(dispatcher) {
|
override suspend fun forwardEvents(eventIds: List<EventId>, roomIds: List<RoomId>): Result<Unit> = withContext(dispatcher) {
|
||||||
runCatchingExceptions {
|
runCatchingExceptions {
|
||||||
roomContentForwarder.forward(fromTimeline = inner, eventId = eventId, toRoomIds = roomIds)
|
roomContentForwarder.forward(fromTimeline = inner, eventIds = eventIds, toRoomIds = roomIds)
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
Timber.tag(loggerTag).e(it)
|
Timber.tag(loggerTag).e(it)
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-3
@@ -355,10 +355,10 @@ class FakeTimeline(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
var forwardEventLambda: (eventId: EventId, roomIds: List<RoomId>) -> Result<Unit> = { _, _ -> lambdaError() }
|
var forwardEventsLambda: (eventIds: List<EventId>, roomIds: List<RoomId>) -> Result<Unit> = { _, _ -> lambdaError() }
|
||||||
|
|
||||||
override suspend fun forwardEvent(eventId: EventId, roomIds: List<RoomId>): Result<Unit> = simulateLongTask {
|
override suspend fun forwardEvents(eventIds: List<EventId>, roomIds: List<RoomId>): Result<Unit> = simulateLongTask {
|
||||||
forwardEventLambda(eventId, roomIds)
|
forwardEventsLambda(eventIds, roomIds)
|
||||||
}
|
}
|
||||||
|
|
||||||
var createPollLambda: (
|
var createPollLambda: (
|
||||||
|
|||||||
Reference in New Issue
Block a user