Remove AttachmentsState and use the MessagesNavigator
This commit is contained in:
@@ -7,13 +7,16 @@
|
||||
|
||||
package io.element.android.features.messages.impl
|
||||
|
||||
import io.element.android.features.messages.impl.attachments.Attachment
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
interface MessagesNavigator {
|
||||
fun onShowEventDebugInfoClick(eventId: EventId?, debugInfo: TimelineItemDebugInfo)
|
||||
fun onForwardEventClick(eventId: EventId)
|
||||
fun onReportContentClick(eventId: EventId, senderId: UserId)
|
||||
fun onEditPollClick(eventId: EventId)
|
||||
fun onPreviewAttachment(attachments: ImmutableList<Attachment>)
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ import io.element.android.anvilannotations.ContributesNode
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.features.messages.impl.attachments.Attachment
|
||||
import io.element.android.features.messages.impl.messagecomposer.MessageComposerEvents
|
||||
import io.element.android.features.messages.impl.messagecomposer.MessageComposerPresenter
|
||||
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
|
||||
@@ -60,12 +61,16 @@ class MessagesNode @AssistedInject constructor(
|
||||
@Assisted plugins: List<Plugin>,
|
||||
private val room: MatrixRoom,
|
||||
private val analyticsService: AnalyticsService,
|
||||
messageComposerPresenterFactory: MessageComposerPresenter.Factory,
|
||||
presenterFactory: MessagesPresenter.Factory,
|
||||
private val timelineItemPresenterFactories: TimelineItemPresenterFactories,
|
||||
private val mediaPlayer: MediaPlayer,
|
||||
private val permalinkParser: PermalinkParser,
|
||||
) : Node(buildContext, plugins = plugins), MessagesNavigator {
|
||||
private val presenter = presenterFactory.create(this)
|
||||
private val presenter = presenterFactory.create(
|
||||
navigator = this,
|
||||
composerPresenter = messageComposerPresenterFactory.create(this),
|
||||
)
|
||||
private val callbacks = plugins<Callback>()
|
||||
|
||||
data class Inputs(val focusedEventId: EventId?) : NodeInputs
|
||||
@@ -114,10 +119,6 @@ class MessagesNode @AssistedInject constructor(
|
||||
.orFalse()
|
||||
}
|
||||
|
||||
private fun onPreviewAttachments(attachments: ImmutableList<Attachment>) {
|
||||
callbacks.forEach { it.onPreviewAttachments(attachments) }
|
||||
}
|
||||
|
||||
private fun onUserDataClick(userId: UserId) {
|
||||
callbacks.forEach { it.onUserDataClick(userId) }
|
||||
}
|
||||
@@ -178,6 +179,10 @@ class MessagesNode @AssistedInject constructor(
|
||||
callbacks.forEach { it.onEditPollClick(eventId) }
|
||||
}
|
||||
|
||||
override fun onPreviewAttachment(attachments: ImmutableList<Attachment>) {
|
||||
callbacks.forEach { it.onPreviewAttachments(attachments) }
|
||||
}
|
||||
|
||||
private fun onViewAllPinnedMessagesClick() {
|
||||
callbacks.forEach { it.onViewAllPinnedEvents() }
|
||||
}
|
||||
@@ -213,7 +218,6 @@ class MessagesNode @AssistedInject constructor(
|
||||
onBackClick = this::navigateUp,
|
||||
onRoomDetailsClick = this::onRoomDetailsClick,
|
||||
onEventContentClick = this::onEventClick,
|
||||
onPreviewAttachments = this::onPreviewAttachments,
|
||||
onUserDataClick = this::onUserDataClick,
|
||||
onLinkClick = { url -> onLinkClick(activity, isDark, url, state.timelineState.eventSink) },
|
||||
onSendLocationClick = this::onSendLocationClick,
|
||||
|
||||
@@ -89,12 +89,12 @@ import timber.log.Timber
|
||||
class MessagesPresenter @AssistedInject constructor(
|
||||
@Assisted private val navigator: MessagesNavigator,
|
||||
private val room: MatrixRoom,
|
||||
private val composerPresenter: Presenter<MessageComposerState>,
|
||||
@Assisted private val composerPresenter: Presenter<MessageComposerState>,
|
||||
private val voiceMessageComposerPresenter: Presenter<VoiceMessageComposerState>,
|
||||
timelinePresenterFactory: TimelinePresenter.Factory,
|
||||
private val timelineProtectionPresenter: Presenter<TimelineProtectionState>,
|
||||
private val identityChangeStatePresenter: Presenter<IdentityChangeState>,
|
||||
private val actionListPresenterFactory: ActionListPresenter.Factory,
|
||||
actionListPresenterFactory: ActionListPresenter.Factory,
|
||||
private val customReactionPresenter: Presenter<CustomReactionState>,
|
||||
private val reactionSummaryPresenter: Presenter<ReactionSummaryState>,
|
||||
private val readReceiptBottomSheetPresenter: Presenter<ReadReceiptBottomSheetState>,
|
||||
@@ -116,7 +116,10 @@ class MessagesPresenter @AssistedInject constructor(
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
fun create(navigator: MessagesNavigator): MessagesPresenter
|
||||
fun create(
|
||||
navigator: MessagesNavigator,
|
||||
composerPresenter: Presenter<MessageComposerState>,
|
||||
): MessagesPresenter
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
||||
@@ -32,10 +32,8 @@ import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
@@ -55,10 +53,8 @@ import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.features.messages.impl.actionlist.ActionListEvents
|
||||
import io.element.android.features.messages.impl.actionlist.ActionListView
|
||||
import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction
|
||||
import io.element.android.features.messages.impl.attachments.Attachment
|
||||
import io.element.android.features.messages.impl.crypto.identity.IdentityChangeStateView
|
||||
import io.element.android.features.messages.impl.messagecomposer.AttachmentsBottomSheet
|
||||
import io.element.android.features.messages.impl.messagecomposer.AttachmentsState
|
||||
import io.element.android.features.messages.impl.messagecomposer.MessageComposerEvents
|
||||
import io.element.android.features.messages.impl.messagecomposer.MessageComposerView
|
||||
import io.element.android.features.messages.impl.messagecomposer.suggestions.SuggestionsPickerView
|
||||
@@ -115,7 +111,6 @@ fun MessagesView(
|
||||
onEventContentClick: (event: TimelineItem.Event) -> Boolean,
|
||||
onUserDataClick: (UserId) -> Unit,
|
||||
onLinkClick: (String) -> Unit,
|
||||
onPreviewAttachments: (ImmutableList<Attachment>) -> Unit,
|
||||
onSendLocationClick: () -> Unit,
|
||||
onCreatePollClick: () -> Unit,
|
||||
onJoinCallClick: () -> Unit,
|
||||
@@ -129,11 +124,6 @@ fun MessagesView(
|
||||
|
||||
KeepScreenOn(state.voiceMessageComposerState.keepScreenOn)
|
||||
|
||||
AttachmentStateView(
|
||||
state = state.composerState.attachmentsState,
|
||||
onPreviewAttachments = onPreviewAttachments,
|
||||
)
|
||||
|
||||
val snackbarHostState = rememberSnackbarHostState(snackbarMessage = state.snackbarMessage)
|
||||
|
||||
// This is needed because the composer is inside an AndroidView that can't be affected by the FocusManager in Compose
|
||||
@@ -273,22 +263,6 @@ private fun ReinviteDialog(state: MessagesState) {
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AttachmentStateView(
|
||||
state: AttachmentsState,
|
||||
onPreviewAttachments: (ImmutableList<Attachment>) -> Unit,
|
||||
) {
|
||||
when (state) {
|
||||
AttachmentsState.None -> Unit
|
||||
is AttachmentsState.Previewing -> {
|
||||
val latestOnPreviewAttachments by rememberUpdatedState(onPreviewAttachments)
|
||||
LaunchedEffect(state) {
|
||||
latestOnPreviewAttachments(state.attachments)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MessagesViewContent(
|
||||
state: MessagesState,
|
||||
@@ -557,7 +531,6 @@ internal fun MessagesViewPreview(@PreviewParameter(MessagesStateProvider::class)
|
||||
onEventContentClick = { false },
|
||||
onUserDataClick = {},
|
||||
onLinkClick = {},
|
||||
onPreviewAttachments = {},
|
||||
onSendLocationClick = {},
|
||||
onCreatePollClick = {},
|
||||
onJoinCallClick = {},
|
||||
|
||||
@@ -36,7 +36,6 @@ internal fun MessagesViewWithIdentityChangePreview(
|
||||
onEventContentClick = { false },
|
||||
onUserDataClick = {},
|
||||
onLinkClick = {},
|
||||
onPreviewAttachments = {},
|
||||
onSendLocationClick = {},
|
||||
onCreatePollClick = {},
|
||||
onJoinCallClick = {},
|
||||
|
||||
@@ -14,8 +14,6 @@ import io.element.android.features.messages.impl.crypto.identity.IdentityChangeS
|
||||
import io.element.android.features.messages.impl.crypto.identity.IdentityChangeStatePresenter
|
||||
import io.element.android.features.messages.impl.crypto.sendfailure.resolve.ResolveVerifiedUserSendFailurePresenter
|
||||
import io.element.android.features.messages.impl.crypto.sendfailure.resolve.ResolveVerifiedUserSendFailureState
|
||||
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.pinned.banner.PinnedMessagesBannerPresenter
|
||||
import io.element.android.features.messages.impl.pinned.banner.PinnedMessagesBannerState
|
||||
import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionPresenter
|
||||
@@ -48,9 +46,6 @@ interface MessagesModule {
|
||||
@Binds
|
||||
fun bindTimelineProtectionPresenter(presenter: TimelineProtectionPresenter): Presenter<TimelineProtectionState>
|
||||
|
||||
@Binds
|
||||
fun bindMessageComposerPresenter(presenter: MessageComposerPresenter): Presenter<MessageComposerState>
|
||||
|
||||
@Binds
|
||||
fun bindVoiceMessageComposerPresenter(presenter: VoiceMessageComposerPresenter): Presenter<VoiceMessageComposerState>
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@ import androidx.annotation.VisibleForTesting
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
@@ -26,8 +25,12 @@ import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.media3.common.MimeTypes
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import im.vector.app.features.analytics.plan.Composer
|
||||
import im.vector.app.features.analytics.plan.Interaction
|
||||
import io.element.android.features.messages.impl.MessagesNavigator
|
||||
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.draft.ComposerDraftService
|
||||
@@ -38,8 +41,6 @@ import io.element.android.features.messages.impl.utils.TextPillificationHelper
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage
|
||||
import io.element.android.libraries.di.RoomScope
|
||||
import io.element.android.libraries.di.SingleIn
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
@@ -89,12 +90,11 @@ import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.merge
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
import io.element.android.libraries.core.mimetype.MimeTypes.Any as AnyMimeTypes
|
||||
|
||||
@SingleIn(RoomScope::class)
|
||||
class MessageComposerPresenter @Inject constructor(
|
||||
class MessageComposerPresenter @AssistedInject constructor(
|
||||
@Assisted private val navigator: MessagesNavigator,
|
||||
private val appCoroutineScope: CoroutineScope,
|
||||
private val room: MatrixRoom,
|
||||
private val mediaPickerProvider: PickerProvider,
|
||||
@@ -117,6 +117,11 @@ class MessageComposerPresenter @Inject constructor(
|
||||
private val roomMemberProfilesCache: RoomMemberProfilesCache,
|
||||
private val suggestionsProcessor: SuggestionsProcessor,
|
||||
) : Presenter<MessageComposerState> {
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
fun create(navigator: MessagesNavigator): MessageComposerPresenter
|
||||
}
|
||||
|
||||
private val cameraPermissionPresenter = permissionsPresenterFactory.create(Manifest.permission.CAMERA)
|
||||
private var pendingEvent: MessageComposerEvents? = null
|
||||
private val suggestionSearchTrigger = MutableStateFlow<Suggestion?>(null)
|
||||
@@ -147,9 +152,6 @@ class MessageComposerPresenter @Inject constructor(
|
||||
}
|
||||
|
||||
val cameraPermissionState = cameraPermissionPresenter.present()
|
||||
val attachmentsState = remember {
|
||||
mutableStateOf<AttachmentsState>(AttachmentsState.None)
|
||||
}
|
||||
|
||||
val canShareLocation = remember { mutableStateOf(false) }
|
||||
LaunchedEffect(Unit) {
|
||||
@@ -162,16 +164,16 @@ class MessageComposerPresenter @Inject constructor(
|
||||
}
|
||||
|
||||
val galleryMediaPicker = mediaPickerProvider.registerGalleryPicker { uri, mimeType ->
|
||||
handlePickedMedia(attachmentsState, uri, mimeType)
|
||||
handlePickedMedia(uri, mimeType)
|
||||
}
|
||||
val filesPicker = mediaPickerProvider.registerFilePicker(AnyMimeTypes) { uri ->
|
||||
handlePickedMedia(attachmentsState, uri)
|
||||
handlePickedMedia(uri)
|
||||
}
|
||||
val cameraPhotoPicker = mediaPickerProvider.registerCameraPhotoPicker { uri ->
|
||||
handlePickedMedia(attachmentsState, uri, MimeTypes.IMAGE_JPEG)
|
||||
handlePickedMedia(uri, MimeTypes.IMAGE_JPEG)
|
||||
}
|
||||
val cameraVideoPicker = mediaPickerProvider.registerCameraVideoPicker { uri ->
|
||||
handlePickedMedia(attachmentsState, uri, MimeTypes.VIDEO_MP4)
|
||||
handlePickedMedia(uri, MimeTypes.VIDEO_MP4)
|
||||
}
|
||||
val isFullScreen = rememberSaveable {
|
||||
mutableStateOf(false)
|
||||
@@ -277,7 +279,6 @@ class MessageComposerPresenter @Inject constructor(
|
||||
formattedFileSize = null
|
||||
),
|
||||
),
|
||||
attachmentState = attachmentsState,
|
||||
)
|
||||
is MessageComposerEvents.SetMode -> {
|
||||
localCoroutineScope.setMode(event.composerMode, markdownTextEditorState, richTextEditorState)
|
||||
@@ -396,7 +397,6 @@ class MessageComposerPresenter @Inject constructor(
|
||||
showTextFormatting = showTextFormatting,
|
||||
canShareLocation = canShareLocation.value,
|
||||
canCreatePoll = canCreatePoll.value,
|
||||
attachmentsState = attachmentsState.value,
|
||||
suggestions = suggestions.toPersistentList(),
|
||||
resolveMentionDisplay = resolveMentionDisplay,
|
||||
eventSink = { handleEvents(it) },
|
||||
@@ -459,14 +459,12 @@ class MessageComposerPresenter @Inject constructor(
|
||||
|
||||
private fun CoroutineScope.sendAttachment(
|
||||
attachment: Attachment,
|
||||
attachmentState: MutableState<AttachmentsState>,
|
||||
) = when (attachment) {
|
||||
is Attachment.Media -> {
|
||||
launch {
|
||||
sendMedia(
|
||||
uri = attachment.localMedia.uri,
|
||||
mimeType = attachment.localMedia.info.mimeType,
|
||||
attachmentState = attachmentState,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -474,14 +472,10 @@ class MessageComposerPresenter @Inject constructor(
|
||||
|
||||
@UnstableApi
|
||||
private fun handlePickedMedia(
|
||||
attachmentsState: MutableState<AttachmentsState>,
|
||||
uri: Uri?,
|
||||
mimeType: String? = null,
|
||||
) {
|
||||
if (uri == null) {
|
||||
attachmentsState.value = AttachmentsState.None
|
||||
return
|
||||
}
|
||||
uri ?: return
|
||||
val localMedia = localMediaFactory.createFromUri(
|
||||
uri = uri,
|
||||
mimeType = mimeType,
|
||||
@@ -489,13 +483,12 @@ class MessageComposerPresenter @Inject constructor(
|
||||
formattedFileSize = null
|
||||
)
|
||||
val mediaAttachment = Attachment.Media(localMedia)
|
||||
attachmentsState.value = AttachmentsState.Previewing(persistentListOf(mediaAttachment))
|
||||
navigator.onPreviewAttachment(persistentListOf(mediaAttachment))
|
||||
}
|
||||
|
||||
private suspend fun sendMedia(
|
||||
uri: Uri,
|
||||
mimeType: String,
|
||||
attachmentState: MutableState<AttachmentsState>,
|
||||
) = runCatching {
|
||||
mediaSender.sendMedia(
|
||||
uri = uri,
|
||||
@@ -503,12 +496,8 @@ class MessageComposerPresenter @Inject constructor(
|
||||
progressCallback = null,
|
||||
).getOrThrow()
|
||||
}
|
||||
.onSuccess {
|
||||
attachmentState.value = AttachmentsState.None
|
||||
}
|
||||
.onFailure { cause ->
|
||||
Timber.e(cause, "Failed to send attachment")
|
||||
attachmentState.value = AttachmentsState.None
|
||||
if (cause is CancellationException) {
|
||||
throw cause
|
||||
} else {
|
||||
|
||||
@@ -7,9 +7,7 @@
|
||||
|
||||
package io.element.android.features.messages.impl.messagecomposer
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.Stable
|
||||
import io.element.android.features.messages.impl.attachments.Attachment
|
||||
import io.element.android.libraries.textcomposer.mentions.ResolvedSuggestion
|
||||
import io.element.android.libraries.textcomposer.model.MessageComposerMode
|
||||
import io.element.android.libraries.textcomposer.model.TextEditorState
|
||||
@@ -25,14 +23,7 @@ data class MessageComposerState(
|
||||
val showTextFormatting: Boolean,
|
||||
val canShareLocation: Boolean,
|
||||
val canCreatePoll: Boolean,
|
||||
val attachmentsState: AttachmentsState,
|
||||
val suggestions: ImmutableList<ResolvedSuggestion>,
|
||||
val resolveMentionDisplay: (String, String) -> TextDisplay,
|
||||
val eventSink: (MessageComposerEvents) -> Unit,
|
||||
)
|
||||
|
||||
@Immutable
|
||||
sealed interface AttachmentsState {
|
||||
data object None : AttachmentsState
|
||||
data class Previewing(val attachments: ImmutableList<Attachment>) : AttachmentsState
|
||||
}
|
||||
|
||||
@@ -31,7 +31,6 @@ fun aMessageComposerState(
|
||||
showAttachmentSourcePicker: Boolean = false,
|
||||
canShareLocation: Boolean = true,
|
||||
canCreatePoll: Boolean = true,
|
||||
attachmentsState: AttachmentsState = AttachmentsState.None,
|
||||
suggestions: ImmutableList<ResolvedSuggestion> = persistentListOf(),
|
||||
eventSink: (MessageComposerEvents) -> Unit = {},
|
||||
) = MessageComposerState(
|
||||
@@ -42,7 +41,6 @@ fun aMessageComposerState(
|
||||
showAttachmentSourcePicker = showAttachmentSourcePicker,
|
||||
canShareLocation = canShareLocation,
|
||||
canCreatePoll = canCreatePoll,
|
||||
attachmentsState = attachmentsState,
|
||||
suggestions = suggestions,
|
||||
resolveMentionDisplay = { _, _ -> TextDisplay.Plain },
|
||||
eventSink = eventSink,
|
||||
|
||||
Reference in New Issue
Block a user