Timeline : makes sure to use the right timeline when making some action (edit, reply, reaction)
This commit is contained in:
@@ -35,6 +35,7 @@ import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import io.element.android.anvilannotations.ContributesNode
|
||||
import io.element.android.features.messages.impl.attachments.Attachment
|
||||
import io.element.android.features.messages.impl.timeline.TimelineController
|
||||
import io.element.android.features.messages.impl.timeline.TimelineEvents
|
||||
import io.element.android.features.messages.impl.timeline.di.LocalTimelineItemPresenterFactories
|
||||
import io.element.android.features.messages.impl.timeline.di.TimelineItemPresenterFactories
|
||||
@@ -71,6 +72,7 @@ class MessagesNode @AssistedInject constructor(
|
||||
private val permalinkParser: PermalinkParser,
|
||||
@ApplicationContext
|
||||
private val context: Context,
|
||||
private val timelineController: TimelineController,
|
||||
) : Node(buildContext, plugins = plugins), MessagesNavigator {
|
||||
private val presenter = presenterFactory.create(this)
|
||||
private val callback = plugins<Callback>().firstOrNull()
|
||||
@@ -101,6 +103,7 @@ class MessagesNode @AssistedInject constructor(
|
||||
analyticsService.capture(room.toAnalyticsViewRoom())
|
||||
},
|
||||
onDestroy = {
|
||||
timelineController.close()
|
||||
mediaPlayer.close()
|
||||
}
|
||||
)
|
||||
|
||||
@@ -38,6 +38,7 @@ import io.element.android.features.messages.impl.actionlist.model.TimelineItemAc
|
||||
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.MessageComposerState
|
||||
import io.element.android.features.messages.impl.timeline.TimelineController
|
||||
import io.element.android.features.messages.impl.timeline.TimelineEvents
|
||||
import io.element.android.features.messages.impl.timeline.TimelinePresenter
|
||||
import io.element.android.features.messages.impl.timeline.TimelineState
|
||||
@@ -116,6 +117,7 @@ class MessagesPresenter @AssistedInject constructor(
|
||||
private val htmlConverterProvider: HtmlConverterProvider,
|
||||
@Assisted private val navigator: MessagesNavigator,
|
||||
private val buildMeta: BuildMeta,
|
||||
private val timelineController: TimelineController,
|
||||
) : Presenter<MessagesState> {
|
||||
private val timelinePresenter = timelinePresenterFactory.create(navigator = navigator)
|
||||
|
||||
@@ -286,8 +288,10 @@ class MessagesPresenter @AssistedInject constructor(
|
||||
emoji: String,
|
||||
eventId: EventId,
|
||||
) = launch(dispatchers.io) {
|
||||
room.toggleReaction(emoji, eventId)
|
||||
.onFailure { Timber.e(it) }
|
||||
timelineController.invokeOnTimeline {
|
||||
toggleReaction(emoji, eventId)
|
||||
.onFailure { Timber.e(it) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun CoroutineScope.reinviteOtherUser(inviteProgress: MutableState<AsyncData<Unit>>) = launch(dispatchers.io) {
|
||||
|
||||
@@ -38,6 +38,7 @@ import io.element.android.features.messages.impl.attachments.Attachment
|
||||
import io.element.android.features.messages.impl.attachments.preview.error.sendAttachmentError
|
||||
import io.element.android.features.messages.impl.mentions.MentionSuggestion
|
||||
import io.element.android.features.messages.impl.mentions.MentionSuggestionsProcessor
|
||||
import io.element.android.features.messages.impl.timeline.TimelineController
|
||||
import io.element.android.features.preferences.api.store.SessionPreferencesStore
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
|
||||
@@ -100,6 +101,7 @@ class MessageComposerPresenter @Inject constructor(
|
||||
private val permalinkParser: PermalinkParser,
|
||||
private val permalinkBuilder: PermalinkBuilder,
|
||||
permissionsPresenterFactory: PermissionsPresenter.Factory,
|
||||
private val timelineController: TimelineController,
|
||||
) : Presenter<MessageComposerState> {
|
||||
private val cameraPermissionPresenter = permissionsPresenterFactory.create(Manifest.permission.CAMERA)
|
||||
private var pendingEvent: MessageComposerEvents? = null
|
||||
@@ -264,7 +266,9 @@ class MessageComposerPresenter @Inject constructor(
|
||||
is MessageComposerMode.Quote -> null
|
||||
}.let { relatedEventId ->
|
||||
appCoroutineScope.launch {
|
||||
room.enterSpecialMode(relatedEventId)
|
||||
timelineController.invokeOnTimeline {
|
||||
enterSpecialMode(relatedEventId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -386,16 +390,17 @@ class MessageComposerPresenter @Inject constructor(
|
||||
is MessageComposerMode.Edit -> {
|
||||
val eventId = capturedMode.eventId
|
||||
val transactionId = capturedMode.transactionId
|
||||
room.editMessage(eventId, transactionId, message.markdown, message.html, mentions)
|
||||
timelineController.invokeOnTimeline {
|
||||
editMessage(eventId, transactionId, message.markdown, message.html, mentions)
|
||||
}
|
||||
}
|
||||
|
||||
is MessageComposerMode.Quote -> TODO()
|
||||
is MessageComposerMode.Reply -> room.replyMessage(
|
||||
capturedMode.eventId,
|
||||
message.markdown,
|
||||
message.html,
|
||||
mentions
|
||||
)
|
||||
is MessageComposerMode.Reply -> {
|
||||
timelineController.invokeOnTimeline {
|
||||
replyMessage(capturedMode.eventId, message.markdown, message.html, mentions)
|
||||
}
|
||||
}
|
||||
}
|
||||
analyticsService.capture(
|
||||
Composer(
|
||||
|
||||
@@ -17,11 +17,14 @@
|
||||
package io.element.android.features.messages.impl.timeline
|
||||
|
||||
import androidx.compose.runtime.MutableState
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
||||
import io.element.android.libraries.di.RoomScope
|
||||
import io.element.android.libraries.di.SingleIn
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||
import io.element.android.libraries.matrix.api.timeline.LiveTimelineProvider
|
||||
import io.element.android.libraries.matrix.api.timeline.TimelineProvider
|
||||
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
|
||||
import io.element.android.libraries.matrix.api.timeline.ReceiptType
|
||||
import io.element.android.libraries.matrix.api.timeline.Timeline
|
||||
@@ -30,23 +33,25 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.filterIsInstance
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.getAndUpdate
|
||||
import kotlinx.coroutines.flow.map
|
||||
import java.io.Closeable
|
||||
import java.util.Optional
|
||||
import javax.inject.Inject
|
||||
import kotlin.coroutines.cancellation.CancellationException
|
||||
|
||||
/**
|
||||
* This controller is responsible of using the right timeline to display messages.
|
||||
* This controller is responsible of using the right timeline to display messages and make associated actions.
|
||||
* It can be focused on the live timeline or on a detached timeline (focusing an unknown event).
|
||||
* This controller will replace the [LiveTimelineProvider] in the DI.
|
||||
*/
|
||||
@SingleIn(RoomScope::class)
|
||||
@ContributesBinding(RoomScope::class, boundType = TimelineProvider::class, replaces = [LiveTimelineProvider::class])
|
||||
class TimelineController @Inject constructor(
|
||||
private val room: MatrixRoom,
|
||||
) {
|
||||
) : Closeable, TimelineProvider {
|
||||
|
||||
private val liveTimeline = MutableStateFlow(room.liveTimeline)
|
||||
private val detachedTimeline = MutableStateFlow<Optional<Timeline>>(Optional.empty())
|
||||
@@ -60,6 +65,12 @@ class TimelineController @Inject constructor(
|
||||
return detachedTimeline.map { !it.isPresent }
|
||||
}
|
||||
|
||||
suspend fun invokeOnTimeline(block: suspend (Timeline.() -> Any)) {
|
||||
currentTimelineFlow().first().run {
|
||||
block(this)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun focusOnEvent(eventId: EventId): Result<Unit> {
|
||||
return try {
|
||||
val newDetachedTimeline = room.timelineFocusedOnEvent(eventId)
|
||||
@@ -93,6 +104,10 @@ class TimelineController @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
focusOnLive()
|
||||
}
|
||||
|
||||
suspend fun paginate(direction: Timeline.PaginationDirection): Result<Boolean> {
|
||||
return currentTimelineFlow().first().paginate(direction)
|
||||
.onSuccess { hasReachedEnd ->
|
||||
@@ -124,7 +139,6 @@ class TimelineController @Inject constructor(
|
||||
if (eventId != null && eventId != lastReadReceiptId.value) {
|
||||
lastReadReceiptId.value = eventId
|
||||
currentTimelineFlow()
|
||||
.filterIsInstance(Timeline::class)
|
||||
.first()
|
||||
.sendReadReceipt(eventId = eventId, receiptType = readReceiptType)
|
||||
}
|
||||
@@ -140,4 +154,8 @@ class TimelineController @Inject constructor(
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
override suspend fun getActiveTimeline(): Timeline {
|
||||
return currentTimelineFlow().first()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,15 +20,18 @@ import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.poll.PollKind
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
|
||||
import io.element.android.libraries.matrix.api.timeline.TimelineProvider
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.PollContent
|
||||
import kotlinx.coroutines.flow.first
|
||||
import javax.inject.Inject
|
||||
|
||||
class PollRepository @Inject constructor(
|
||||
private val room: MatrixRoom,
|
||||
private val timelineProvider: TimelineProvider,
|
||||
) {
|
||||
suspend fun getPoll(eventId: EventId): Result<PollContent> = runCatching {
|
||||
room.liveTimeline
|
||||
timelineProvider
|
||||
.getActiveTimeline()
|
||||
.timelineItems
|
||||
.first()
|
||||
.asSequence()
|
||||
@@ -51,13 +54,15 @@ class PollRepository @Inject constructor(
|
||||
maxSelections = maxSelections,
|
||||
pollKind = pollKind,
|
||||
)
|
||||
else -> room.editPoll(
|
||||
pollStartId = existingPollId,
|
||||
question = question,
|
||||
answers = answers,
|
||||
maxSelections = maxSelections,
|
||||
pollKind = pollKind,
|
||||
)
|
||||
else -> timelineProvider
|
||||
.getActiveTimeline()
|
||||
.editPoll(
|
||||
pollStartId = existingPollId,
|
||||
question = question,
|
||||
answers = answers,
|
||||
maxSelections = maxSelections,
|
||||
pollKind = pollKind,
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun deletePoll(
|
||||
|
||||
@@ -32,6 +32,7 @@ import io.element.android.libraries.matrix.api.poll.PollKind
|
||||
import io.element.android.libraries.matrix.api.room.location.AssetType
|
||||
import io.element.android.libraries.matrix.api.room.powerlevels.MatrixRoomPowerLevels
|
||||
import io.element.android.libraries.matrix.api.room.powerlevels.UserRoleChange
|
||||
import io.element.android.libraries.matrix.api.timeline.TimelineProvider
|
||||
import io.element.android.libraries.matrix.api.timeline.Timeline
|
||||
import io.element.android.libraries.matrix.api.timeline.ReceiptType
|
||||
import io.element.android.libraries.matrix.api.widget.MatrixWidgetDriver
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (c) 2024 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.timeline
|
||||
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.libraries.di.RoomScope
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* This interface defines a way to get the active timeline.
|
||||
* It could be the current room timeline, or a timeline for a specific event.
|
||||
*/
|
||||
interface TimelineProvider {
|
||||
suspend fun getActiveTimeline(): Timeline
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation of [TimelineProvider] that provides the live timeline of a room.
|
||||
*/
|
||||
@ContributesBinding(RoomScope::class)
|
||||
class LiveTimelineProvider @Inject constructor(
|
||||
private val room: MatrixRoom,
|
||||
) : TimelineProvider {
|
||||
override suspend fun getActiveTimeline(): Timeline = room.liveTimeline
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user