mirror of
https://github.com/Novaloop-AG/element-x-android.git
synced 2026-11-05 04:15:12 +00:00
Merge
This commit is contained in:
@@ -61,6 +61,7 @@ dependencies {
|
||||
implementation(projects.libraries.push.api)
|
||||
implementation(projects.libraries.uiUtils)
|
||||
implementation(projects.libraries.testtags)
|
||||
implementation(projects.libraries.wellknown.api)
|
||||
implementation(projects.features.networkmonitor.api)
|
||||
implementation(projects.features.contentscanner.api)
|
||||
implementation(projects.services.analytics.compose)
|
||||
|
||||
+14
@@ -400,9 +400,23 @@ class MessagesPresenter(
|
||||
TimelineItemAction.Pin -> handlePinAction(targetEvent)
|
||||
TimelineItemAction.Unpin -> handleUnpinAction(targetEvent)
|
||||
TimelineItemAction.ViewInTimeline -> Unit
|
||||
TimelineItemAction.RetrySending -> handleRetrySending(targetEvent)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun handleRetrySending(targetEvent: TimelineItem.Event) {
|
||||
val sendHandle = targetEvent.sendhandle ?: return Unit.also {
|
||||
Timber.w("No send handle for event ${targetEvent.eventOrTransactionId}")
|
||||
}
|
||||
sendHandle.retry()
|
||||
.onSuccess {
|
||||
Timber.d("Succeed to add the message back to the send queue")
|
||||
}
|
||||
.onFailure {
|
||||
Timber.e(it, "Failed to add the message back to the send queue")
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun handleRemoveCaption(targetEvent: TimelineItem.Event) {
|
||||
timelineController.invokeOnCurrentTimeline {
|
||||
editCaption(
|
||||
|
||||
+1
-1
@@ -57,7 +57,7 @@ import io.element.android.libraries.textcomposer.model.aTextEditorStateRich
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.persistentSetOf
|
||||
|
||||
open class MessagesStateProvider : PreviewParameterProvider<MessagesState> {
|
||||
open class MessagesStatePreviewParam : PreviewParameterProvider<MessagesState> {
|
||||
override val values: Sequence<MessagesState>
|
||||
get() = sequenceOf(
|
||||
aMessagesState(),
|
||||
+37
-5
@@ -93,6 +93,7 @@ import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
||||
import io.element.android.features.messages.impl.timeline.model.TimelineItemGroupPosition
|
||||
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemStateEventContent
|
||||
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemTextContent
|
||||
import io.element.android.features.messages.impl.timeline.sendfailure.SendFailureDialogView
|
||||
import io.element.android.features.messages.impl.topbars.MessagesViewTopBar
|
||||
import io.element.android.features.messages.impl.topbars.ThreadTopBar
|
||||
import io.element.android.features.messages.impl.voicemessages.composer.VoiceMessagePermissionRationaleDialog
|
||||
@@ -178,8 +179,8 @@ fun MessagesView(
|
||||
|
||||
fun onContentClick(event: TimelineItem.Event) {
|
||||
Timber.v("onMessageClick= ${event.id}")
|
||||
val eventId = event.eventId ?: return
|
||||
if (eventContentValidationState[eventId].getCurrentOverallState() != ContentValidationValue.Valid) return
|
||||
val eventId = event.eventId
|
||||
if (eventId != null && eventContentValidationState[eventId].getCurrentOverallState() != ContentValidationValue.Valid) return
|
||||
|
||||
val hideKeyboard = onEventContentClick(state.timelineState.isLive, event)
|
||||
if (hideKeyboard) {
|
||||
@@ -429,6 +430,31 @@ fun MessagesView(
|
||||
},
|
||||
state = state.linkState,
|
||||
)
|
||||
|
||||
SendFailureDialogView(
|
||||
sendFailureDialogState = state.timelineState.sendFailureDialogState,
|
||||
onDismiss = {
|
||||
state.timelineState.eventSink(TimelineEvent.HideSendFailureDialog)
|
||||
},
|
||||
onRetry = { event ->
|
||||
state.eventSink(
|
||||
MessagesEvent.HandleAction(
|
||||
action = TimelineItemAction.RetrySending,
|
||||
event = event,
|
||||
)
|
||||
)
|
||||
state.timelineState.eventSink(TimelineEvent.HideSendFailureDialog)
|
||||
},
|
||||
onRemoveMessage = { event ->
|
||||
state.eventSink(
|
||||
MessagesEvent.HandleAction(
|
||||
action = TimelineItemAction.Redact,
|
||||
event = event,
|
||||
)
|
||||
)
|
||||
state.timelineState.eventSink(TimelineEvent.HideSendFailureDialog)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
@@ -590,13 +616,19 @@ private fun MessagesViewComposerBottomSheetContents(
|
||||
when {
|
||||
state.successorRoom != null -> {
|
||||
SuccessorRoomBanner(
|
||||
modifier = Modifier.fillMaxWidth().padding(contentPadding),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(contentPadding),
|
||||
roomSuccessor = state.successorRoom,
|
||||
onRoomSuccessorClick = onRoomSuccessorClick
|
||||
)
|
||||
}
|
||||
state.userEventPermissions.canSendMessage -> {
|
||||
Column(modifier = Modifier.fillMaxWidth().padding(contentPadding)) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(contentPadding)
|
||||
) {
|
||||
// Do not show the identity change if user is composing a Rich message or is seeing suggestion(s).
|
||||
if (state.composerState.suggestions.isEmpty() &&
|
||||
state.composerState.textEditorState is TextEditorState.Markdown) {
|
||||
@@ -662,7 +694,7 @@ private fun SuccessorRoomBanner(
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun MessagesViewPreview(@PreviewParameter(MessagesStateProvider::class) state: MessagesState) = ElementPreview {
|
||||
internal fun MessagesViewPreview(@PreviewParameter(MessagesStatePreviewParam::class) state: MessagesState) = ElementPreview {
|
||||
MessagesView(
|
||||
state = state,
|
||||
onBackClick = {},
|
||||
|
||||
+1
-1
@@ -28,7 +28,7 @@ import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
|
||||
open class ActionListStatePreviewParam : PreviewParameterProvider<ActionListState> {
|
||||
private val suggestedEmojis = persistentListOf("👍️", "👎️", "🔥", "❤️", "👏")
|
||||
|
||||
override val values: Sequence<ActionListState>
|
||||
+1
-1
@@ -514,7 +514,7 @@ private fun EmojiButton(
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun ActionListViewContentPreview(
|
||||
@PreviewParameter(ActionListStateProvider::class) state: ActionListState
|
||||
@PreviewParameter(ActionListStatePreviewParam::class) state: ActionListState
|
||||
) = ElementPreview {
|
||||
ActionListViewContent(
|
||||
state = state,
|
||||
|
||||
+3
@@ -36,4 +36,7 @@ enum class TimelineItemAction(
|
||||
EndPoll(CommonStrings.action_end_poll, CompoundDrawables.ic_compound_polls_end),
|
||||
Pin(CommonStrings.action_pin, CompoundDrawables.ic_compound_pin),
|
||||
Unpin(CommonStrings.action_unpin, CompoundDrawables.ic_compound_unpin),
|
||||
|
||||
// Note: this action is not used in UI for now
|
||||
RetrySending(CommonStrings.action_retry, CompoundDrawables.ic_compound_send_solid),
|
||||
}
|
||||
|
||||
+2
@@ -32,6 +32,8 @@ class TimelineItemActionComparator : Comparator<TimelineItemAction> {
|
||||
TimelineItemAction.ViewSource,
|
||||
TimelineItemAction.ReportContent,
|
||||
TimelineItemAction.Redact,
|
||||
// Not rendered in the action list, but has to be listed here for exhaustiveness.
|
||||
TimelineItemAction.RetrySending,
|
||||
)
|
||||
|
||||
override fun compare(o1: TimelineItemAction, o2: TimelineItemAction): Int {
|
||||
|
||||
+1
-1
@@ -30,7 +30,7 @@ import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import java.io.File
|
||||
|
||||
open class AttachmentsPreviewStateProvider : PreviewParameterProvider<AttachmentsPreviewState> {
|
||||
open class AttachmentsPreviewStatePreviewParam : PreviewParameterProvider<AttachmentsPreviewState> {
|
||||
override val values: Sequence<AttachmentsPreviewState>
|
||||
get() = sequenceOf(
|
||||
anAttachmentsPreviewState(),
|
||||
+1
-1
@@ -553,7 +553,7 @@ private fun AttachmentsPreviewBottomActions(
|
||||
// Only preview in dark, dark theme is forced on the Node.
|
||||
@Preview
|
||||
@Composable
|
||||
internal fun AttachmentsPreviewViewPreview(@PreviewParameter(AttachmentsPreviewStateProvider::class) state: AttachmentsPreviewState) = ElementPreviewDark {
|
||||
internal fun AttachmentsPreviewViewPreview(@PreviewParameter(AttachmentsPreviewStatePreviewParam::class) state: AttachmentsPreviewState) = ElementPreviewDark {
|
||||
AttachmentsPreviewView(
|
||||
state = state,
|
||||
localMediaRenderer = SampleMediaRenderer(),
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ import androidx.core.net.toUri
|
||||
import io.element.android.libraries.mediaviewer.api.anImageMediaInfo
|
||||
import io.element.android.libraries.mediaviewer.api.local.LocalMedia
|
||||
|
||||
open class AttachmentImageEditorStateProvider : PreviewParameterProvider<AttachmentImageEditorState> {
|
||||
open class AttachmentImageEditorStatePreviewParam : PreviewParameterProvider<AttachmentImageEditorState> {
|
||||
private val caterpillarCrop = NormalizedCropRect(
|
||||
left = 0.3f,
|
||||
top = 0.3f,
|
||||
+1
-1
@@ -676,7 +676,7 @@ private fun DrawScope.drawEdgeHandle(
|
||||
@Preview
|
||||
@Composable
|
||||
internal fun AttachmentImageEditorViewPreview(
|
||||
@PreviewParameter(AttachmentImageEditorStateProvider::class) state: AttachmentImageEditorState,
|
||||
@PreviewParameter(AttachmentImageEditorStatePreviewParam::class) state: AttachmentImageEditorState,
|
||||
) = ElementPreviewDark {
|
||||
AttachmentImageEditorView(
|
||||
state = state,
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@ import io.element.android.libraries.matrix.ui.room.IdentityRoomMember
|
||||
import io.element.android.libraries.matrix.ui.room.RoomMemberIdentityStateChange
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
class IdentityChangeStateProvider : PreviewParameterProvider<IdentityChangeState> {
|
||||
class IdentityChangeStatePreviewParam : PreviewParameterProvider<IdentityChangeState> {
|
||||
override val values: Sequence<IdentityChangeState>
|
||||
get() = sequenceOf(
|
||||
anIdentityChangeState(),
|
||||
+1
-1
@@ -122,7 +122,7 @@ private fun ViolationAlert(
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun IdentityChangeStateViewPreview(
|
||||
@PreviewParameter(IdentityChangeStateProvider::class) state: IdentityChangeState,
|
||||
@PreviewParameter(IdentityChangeStatePreviewParam::class) state: IdentityChangeState,
|
||||
) = ElementPreview {
|
||||
IdentityChangeStateView(
|
||||
state = state,
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@ import io.element.android.libraries.textcomposer.model.aTextEditorStateMarkdown
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun MessagesViewWithIdentityChangePreview(
|
||||
@PreviewParameter(IdentityChangeStateProvider::class) identityChangeState: IdentityChangeState
|
||||
@PreviewParameter(IdentityChangeStatePreviewParam::class) identityChangeState: IdentityChangeState
|
||||
) = ElementPreview {
|
||||
MessagesView(
|
||||
state = aMessagesState(
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@ import io.element.android.features.messages.impl.crypto.sendfailure.VerifiedUser
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.designsystem.preview.USER_NAME_ALICE
|
||||
|
||||
open class ResolveVerifiedUserSendFailureStateProvider : PreviewParameterProvider<ResolveVerifiedUserSendFailureState> {
|
||||
open class ResolveVerifiedUserSendFailureStatePreviewParam : PreviewParameterProvider<ResolveVerifiedUserSendFailureState> {
|
||||
override val values: Sequence<ResolveVerifiedUserSendFailureState>
|
||||
get() = sequenceOf(
|
||||
aResolveVerifiedUserSendFailureState(),
|
||||
+1
-1
@@ -158,7 +158,7 @@ private fun VerifiedUserSendFailure.resolveAction(): String {
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun ResolveVerifiedUserSendFailureViewPreview(
|
||||
@PreviewParameter(ResolveVerifiedUserSendFailureStateProvider::class) state: ResolveVerifiedUserSendFailureState
|
||||
@PreviewParameter(ResolveVerifiedUserSendFailureStatePreviewParam::class) state: ResolveVerifiedUserSendFailureState
|
||||
) = ElementPreview {
|
||||
ResolveVerifiedUserSendFailureView(state)
|
||||
}
|
||||
|
||||
+1
-1
@@ -34,7 +34,7 @@ import io.element.android.libraries.di.RoomScope
|
||||
|
||||
@ContributesTo(RoomScope::class)
|
||||
@BindingContainer
|
||||
interface MessagesBindsModule {
|
||||
interface MessagesBindingContainer {
|
||||
@Binds
|
||||
fun bindPinnedMessagesBannerPresenter(presenter: PinnedMessagesBannerPresenter): Presenter<PinnedMessagesBannerState>
|
||||
|
||||
+2
-2
@@ -18,8 +18,8 @@ import io.element.android.libraries.matrix.api.timeline.Timeline
|
||||
|
||||
@ContributesTo(RoomScope::class)
|
||||
@BindingContainer
|
||||
object MessagesProvidesModule {
|
||||
object MessagesProvidesBindingContainer {
|
||||
@Provides
|
||||
@LiveTimeline
|
||||
fun provideLiveTimeline(joinedRoom: JoinedRoom): Timeline = joinedRoom.liveTimeline
|
||||
fun providesLiveTimeline(joinedRoom: JoinedRoom): Timeline = joinedRoom.liveTimeline
|
||||
}
|
||||
+1
-1
@@ -12,7 +12,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.wysiwyg.link.Link
|
||||
|
||||
open class LinkStateProvider : PreviewParameterProvider<LinkState> {
|
||||
open class LinkStatePreviewParam : PreviewParameterProvider<LinkState> {
|
||||
override val values: Sequence<LinkState>
|
||||
get() = sequenceOf(
|
||||
aLinkState(),
|
||||
+1
-1
@@ -66,7 +66,7 @@ fun LinkView(
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun LinkViewPreview(@PreviewParameter(LinkStateProvider::class) state: LinkState) = ElementPreview {
|
||||
internal fun LinkViewPreview(@PreviewParameter(LinkStatePreviewParam::class) state: LinkState) = ElementPreview {
|
||||
LinkView(
|
||||
state = state,
|
||||
onLinkValid = {},
|
||||
|
||||
+12
@@ -86,6 +86,7 @@ import io.element.android.libraries.textcomposer.model.MarkdownTextEditorState
|
||||
import io.element.android.libraries.textcomposer.model.Message
|
||||
import io.element.android.libraries.textcomposer.model.MessageComposerMode
|
||||
import io.element.android.libraries.textcomposer.model.Suggestion
|
||||
import io.element.android.libraries.textcomposer.model.SuggestionType
|
||||
import io.element.android.libraries.textcomposer.model.TextEditorState
|
||||
import io.element.android.libraries.textcomposer.model.rememberMarkdownTextEditorState
|
||||
import io.element.android.services.analytics.api.AnalyticsService
|
||||
@@ -103,7 +104,9 @@ import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.debounce
|
||||
import kotlinx.coroutines.flow.distinctUntilChangedBy
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.merge
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -425,6 +428,7 @@ class MessageComposerPresenter(
|
||||
textEditorState = textEditorState,
|
||||
isFullScreen = isFullScreen.value,
|
||||
mode = messageComposerContext.composerMode,
|
||||
isInThreadTimeline = isInThread,
|
||||
showAttachmentSourcePicker = showAttachmentSourcePicker,
|
||||
showTextFormatting = showTextFormatting,
|
||||
canShareLocation = canShareLocation.value,
|
||||
@@ -458,6 +462,14 @@ class MessageComposerPresenter(
|
||||
|
||||
val mentionTriggerFlow = merge(mentionStartTrigger, mentionCompletionTrigger)
|
||||
|
||||
// Refresh the room members, which are only loaded on demand, when a mention starts
|
||||
launch {
|
||||
suggestionSearchTrigger
|
||||
.distinctUntilChangedBy { it?.type }
|
||||
.filter { it?.type == SuggestionType.Mention }
|
||||
.collect { room.updateMembers() }
|
||||
}
|
||||
|
||||
val roomAliasSuggestionsFlow = roomAliasSuggestionsDataSource
|
||||
.getAllRoomAliasSuggestions()
|
||||
.stateIn(this, SharingStarted.Lazily, emptyList())
|
||||
|
||||
+1
@@ -21,6 +21,7 @@ data class MessageComposerState(
|
||||
val textEditorState: TextEditorState,
|
||||
val isFullScreen: Boolean,
|
||||
val mode: MessageComposerMode,
|
||||
val isInThreadTimeline: Boolean,
|
||||
val showAttachmentSourcePicker: Boolean,
|
||||
val showTextFormatting: Boolean,
|
||||
val canShareLocation: Boolean,
|
||||
|
||||
+3
-1
@@ -18,7 +18,7 @@ import io.element.android.wysiwyg.display.TextDisplay
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
open class MessageComposerStateProvider : PreviewParameterProvider<MessageComposerState> {
|
||||
open class MessageComposerStatePreviewParam : PreviewParameterProvider<MessageComposerState> {
|
||||
override val values: Sequence<MessageComposerState>
|
||||
get() = sequenceOf(
|
||||
aMessageComposerState(),
|
||||
@@ -29,6 +29,7 @@ fun aMessageComposerState(
|
||||
textEditorState: TextEditorState = aTextEditorStateRich(),
|
||||
isFullScreen: Boolean = false,
|
||||
mode: MessageComposerMode = MessageComposerMode.Normal,
|
||||
isInThreadTimeline: Boolean = false,
|
||||
showTextFormatting: Boolean = false,
|
||||
showAttachmentSourcePicker: Boolean = false,
|
||||
canShareLocation: Boolean = true,
|
||||
@@ -39,6 +40,7 @@ fun aMessageComposerState(
|
||||
textEditorState = textEditorState,
|
||||
isFullScreen = isFullScreen,
|
||||
mode = mode,
|
||||
isInThreadTimeline = isInThreadTimeline,
|
||||
showTextFormatting = showTextFormatting,
|
||||
showAttachmentSourcePicker = showAttachmentSourcePicker,
|
||||
canShareLocation = canShareLocation,
|
||||
+4
-3
@@ -20,7 +20,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.features.messages.api.timeline.voicemessages.composer.VoiceMessageComposerEvent
|
||||
import io.element.android.features.messages.api.timeline.voicemessages.composer.VoiceMessageComposerState
|
||||
import io.element.android.features.messages.api.timeline.voicemessages.composer.VoiceMessageComposerStateProvider
|
||||
import io.element.android.features.messages.api.timeline.voicemessages.composer.VoiceMessageComposerStatePreviewParam
|
||||
import io.element.android.features.messages.api.timeline.voicemessages.composer.aVoiceMessageComposerState
|
||||
import io.element.android.libraries.designsystem.components.async.AsyncActionView
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
@@ -102,6 +102,7 @@ internal fun MessageComposerView(
|
||||
onRequestFocus = ::onRequestFocus,
|
||||
onSendMessage = ::sendMessage,
|
||||
composerMode = state.mode,
|
||||
isInThreadTimeline = state.isInThreadTimeline,
|
||||
showTextFormatting = state.showTextFormatting,
|
||||
onResetComposerMode = ::onCloseSpecialMode,
|
||||
onAddAttachment = ::onAddAttachment,
|
||||
@@ -128,7 +129,7 @@ internal fun MessageComposerView(
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun MessageComposerViewPreview(
|
||||
@PreviewParameter(MessageComposerStateProvider::class) state: MessageComposerState,
|
||||
@PreviewParameter(MessageComposerStatePreviewParam::class) state: MessageComposerState,
|
||||
) = ElementPreview {
|
||||
Column {
|
||||
MessageComposerView(
|
||||
@@ -148,7 +149,7 @@ internal fun MessageComposerViewPreview(
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun MessageComposerViewVoicePreview(
|
||||
@PreviewParameter(VoiceMessageComposerStateProvider::class) state: VoiceMessageComposerState,
|
||||
@PreviewParameter(VoiceMessageComposerStatePreviewParam::class) state: VoiceMessageComposerState,
|
||||
) = ElementPreview {
|
||||
Column {
|
||||
MessageComposerView(
|
||||
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (c) 2026 Element Creations Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.messages.impl.pinned
|
||||
|
||||
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.EventContent
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParseMessageLikeContent
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParseStateContent
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.UnknownContent
|
||||
import io.element.android.libraries.matrix.api.timeline.item.virtual.VirtualTimelineItem
|
||||
|
||||
internal fun List<MatrixTimelineItem>.keepDisplayablePinnedEvents(): List<MatrixTimelineItem> {
|
||||
val items = filter { it.isDisplayableAsPinnedEvent() }
|
||||
return items.filterIndexed { index, item ->
|
||||
!item.isDayDivider() || items.getOrNull(index + 1) is MatrixTimelineItem.Event
|
||||
}
|
||||
}
|
||||
|
||||
internal fun EventContent.isDisplayableAsPinnedEvent(): Boolean {
|
||||
return when (this) {
|
||||
UnknownContent,
|
||||
is FailedToParseMessageLikeContent,
|
||||
is FailedToParseStateContent -> false
|
||||
else -> true
|
||||
}
|
||||
}
|
||||
|
||||
private fun MatrixTimelineItem.isDisplayableAsPinnedEvent(): Boolean {
|
||||
return this !is MatrixTimelineItem.Event || event.content.isDisplayableAsPinnedEvent()
|
||||
}
|
||||
|
||||
private fun MatrixTimelineItem.isDayDivider(): Boolean {
|
||||
return this is MatrixTimelineItem.Virtual && virtual is VirtualTimelineItem.DayDivider
|
||||
}
|
||||
+2
@@ -10,6 +10,7 @@ package io.element.android.features.messages.impl.pinned.banner
|
||||
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import dev.zacsweers.metro.Inject
|
||||
import io.element.android.features.messages.impl.pinned.isDisplayableAsPinnedEvent
|
||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||
import io.element.android.libraries.eventformatter.api.PinnedMessagesBannerFormatter
|
||||
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
|
||||
@@ -24,6 +25,7 @@ class PinnedMessagesBannerItemFactory(
|
||||
when (timelineItem) {
|
||||
is MatrixTimelineItem.Event -> {
|
||||
val eventId = timelineItem.eventId ?: return@withContext null
|
||||
if (!timelineItem.event.content.isDisplayableAsPinnedEvent()) return@withContext null
|
||||
val formatted = formatter.format(timelineItem.event)
|
||||
PinnedMessagesBannerItem(
|
||||
eventId = eventId,
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import kotlin.random.Random
|
||||
|
||||
internal class PinnedMessagesBannerStateProvider : PreviewParameterProvider<PinnedMessagesBannerState> {
|
||||
internal class PinnedMessagesBannerStatePreviewParam : PreviewParameterProvider<PinnedMessagesBannerState> {
|
||||
override val values: Sequence<PinnedMessagesBannerState>
|
||||
get() = sequenceOf(
|
||||
aHiddenPinnedMessagesBannerState(),
|
||||
+3
-1
@@ -294,7 +294,9 @@ private class ExitOnScrollBehavior : PinnedMessagesBannerViewScrollBehavior {
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun PinnedMessagesBannerViewPreview(@PreviewParameter(PinnedMessagesBannerStateProvider::class) state: PinnedMessagesBannerState) = ElementPreview {
|
||||
internal fun PinnedMessagesBannerViewPreview(@PreviewParameter(
|
||||
PinnedMessagesBannerStatePreviewParam::class
|
||||
) state: PinnedMessagesBannerState) = ElementPreview {
|
||||
PinnedMessagesBannerView(
|
||||
state = state,
|
||||
onClick = {},
|
||||
|
||||
+2
@@ -11,8 +11,10 @@ package io.element.android.features.messages.impl.pinned.list
|
||||
import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction
|
||||
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
||||
import io.element.android.libraries.matrix.api.core.ThreadId
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.EventOrTransactionId
|
||||
|
||||
sealed interface PinnedMessagesListEvent {
|
||||
data class HandleAction(val action: TimelineItemAction, val event: TimelineItem.Event) : PinnedMessagesListEvent
|
||||
data class OpenThread(val threadRootId: ThreadId) : PinnedMessagesListEvent
|
||||
data class ToggleReaction(val emoji: String, val eventOrTransactionId: EventOrTransactionId) : PinnedMessagesListEvent
|
||||
}
|
||||
|
||||
+3
@@ -30,6 +30,7 @@ import io.element.android.libraries.androidutils.system.copyToClipboard
|
||||
import io.element.android.libraries.androidutils.system.openUrlInExternalApp
|
||||
import io.element.android.libraries.architecture.callback
|
||||
import io.element.android.libraries.di.RoomScope
|
||||
import io.element.android.libraries.emoji.api.picker.EmojiPickerRenderer
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.ThreadId
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
@@ -50,6 +51,7 @@ class PinnedMessagesListNode(
|
||||
actionListPresenterFactory: ActionListPresenter.Factory,
|
||||
private val timelineItemPresenterFactories: TimelineItemPresenterFactories,
|
||||
private val permalinkParser: PermalinkParser,
|
||||
private val emojiPickerRenderer: EmojiPickerRenderer,
|
||||
) : Node(buildContext, plugins = plugins), PinnedMessagesListNavigator {
|
||||
interface Callback : Plugin {
|
||||
fun handleEventClick(event: TimelineItem.Event, canUseOverlay: Boolean)
|
||||
@@ -134,6 +136,7 @@ class PinnedMessagesListNode(
|
||||
toastMessage = toastMessage,
|
||||
)
|
||||
},
|
||||
emojiPickerRenderer = emojiPickerRenderer,
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
|
||||
+30
-7
@@ -28,7 +28,10 @@ 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.link.LinkState
|
||||
import io.element.android.features.messages.impl.pinned.DefaultPinnedEventsTimelineProvider
|
||||
import io.element.android.features.messages.impl.pinned.keepDisplayablePinnedEvents
|
||||
import io.element.android.features.messages.impl.timeline.TimelineRoomInfo
|
||||
import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionState
|
||||
import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryState
|
||||
import io.element.android.features.messages.impl.timeline.factories.TimelineItemsFactory
|
||||
import io.element.android.features.messages.impl.timeline.factories.TimelineItemsFactoryConfig
|
||||
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
||||
@@ -46,6 +49,7 @@ import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
||||
import io.element.android.libraries.matrix.api.room.powerlevels.permissionsAsState
|
||||
import io.element.android.libraries.matrix.api.room.roomMembers
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.EventOrTransactionId
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import io.element.android.services.analytics.api.AnalyticsService
|
||||
import io.element.android.services.analyticsproviders.api.trackers.captureInteraction
|
||||
@@ -70,6 +74,8 @@ class PinnedMessagesListPresenter(
|
||||
private val linkPresenter: Presenter<LinkState>,
|
||||
private val snackbarDispatcher: SnackbarDispatcher,
|
||||
@Assisted private val actionListPresenter: Presenter<ActionListState>,
|
||||
private val customReactionPresenter: Presenter<CustomReactionState>,
|
||||
private val reactionSummaryPresenter: Presenter<ReactionSummaryState>,
|
||||
@SessionCoroutineScope
|
||||
private val sessionCoroutineScope: CoroutineScope,
|
||||
private val analyticsService: AnalyticsService,
|
||||
@@ -87,7 +93,7 @@ class PinnedMessagesListPresenter(
|
||||
private val timelineItemsFactory: TimelineItemsFactory = timelineItemsFactoryCreator.create(
|
||||
config = TimelineItemsFactoryConfig(
|
||||
computeReadReceipts = false,
|
||||
computeReactions = false,
|
||||
computeReactions = true,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -95,6 +101,9 @@ class PinnedMessagesListPresenter(
|
||||
override fun present(): PinnedMessagesListState {
|
||||
htmlConverterProvider.Update()
|
||||
val roomInfo by room.roomInfoFlow.collectAsState()
|
||||
val userEventPermissions by room.permissionsAsState(UserEventPermissions.DEFAULT) { perms ->
|
||||
perms.userEventPermissions()
|
||||
}
|
||||
val timelineRoomInfo by remember {
|
||||
derivedStateOf {
|
||||
TimelineRoomInfo(
|
||||
@@ -102,7 +111,7 @@ class PinnedMessagesListPresenter(
|
||||
name = roomInfo.name,
|
||||
// We don't need to compute those values
|
||||
userHasPermissionToSendMessage = false,
|
||||
userHasPermissionToSendReaction = false,
|
||||
userHasPermissionToSendReaction = userEventPermissions.canSendReaction,
|
||||
// We do not care about the call state here.
|
||||
roomCallState = aStandByCallState(),
|
||||
// don't compute this value or the pin icon will be shown
|
||||
@@ -118,9 +127,8 @@ class PinnedMessagesListPresenter(
|
||||
}
|
||||
val timelineProtectionState = timelineProtectionPresenter.present()
|
||||
val linkState = linkPresenter.present()
|
||||
val userEventPermissions by room.permissionsAsState(UserEventPermissions.DEFAULT) { perms ->
|
||||
perms.userEventPermissions()
|
||||
}
|
||||
val customReactionState = customReactionPresenter.present()
|
||||
val reactionSummaryState = reactionSummaryPresenter.present()
|
||||
|
||||
val displayThreadSummaries by featureFlagService.isFeatureEnabledFlow(FeatureFlags.Threads).collectAsState(false)
|
||||
|
||||
@@ -137,6 +145,7 @@ class PinnedMessagesListPresenter(
|
||||
when (event) {
|
||||
is PinnedMessagesListEvent.HandleAction -> sessionCoroutineScope.handleTimelineAction(event.action, event.event)
|
||||
is PinnedMessagesListEvent.OpenThread -> navigator.navigateToThread(event.threadRootId)
|
||||
is PinnedMessagesListEvent.ToggleReaction -> sessionCoroutineScope.toggleReaction(event.emoji, event.eventOrTransactionId)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,10 +156,20 @@ class PinnedMessagesListPresenter(
|
||||
displayThreadSummaries = displayThreadSummaries,
|
||||
userEventPermissions = userEventPermissions,
|
||||
timelineItems = pinnedMessageItems,
|
||||
customReactionState = customReactionState,
|
||||
reactionSummaryState = reactionSummaryState,
|
||||
eventSink = ::handleEvent,
|
||||
)
|
||||
}
|
||||
|
||||
private fun CoroutineScope.toggleReaction(emoji: String, eventOrTransactionId: EventOrTransactionId) {
|
||||
launch {
|
||||
timelineProvider.invokeOnTimeline {
|
||||
toggleReaction(emoji, eventOrTransactionId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun CoroutineScope.handleTimelineAction(
|
||||
action: TimelineItemAction,
|
||||
targetEvent: TimelineItem.Event,
|
||||
@@ -207,7 +226,7 @@ class PinnedMessagesListPresenter(
|
||||
val timelineItemsFlow = asyncTimeline.data.timelineItems
|
||||
combine(timelineItemsFlow, room.membersStateFlow) { items, membersState ->
|
||||
timelineItemsFactory.replaceWith(
|
||||
timelineItems = items,
|
||||
timelineItems = items.keepDisplayablePinnedEvents(),
|
||||
roomMembers = membersState.roomMembers().orEmpty(),
|
||||
renderReadReceipts = false,
|
||||
)
|
||||
@@ -233,13 +252,15 @@ class PinnedMessagesListPresenter(
|
||||
linkState: LinkState,
|
||||
userEventPermissions: UserEventPermissions,
|
||||
timelineItems: AsyncData<ImmutableList<TimelineItem>>,
|
||||
customReactionState: CustomReactionState,
|
||||
reactionSummaryState: ReactionSummaryState,
|
||||
eventSink: (PinnedMessagesListEvent) -> Unit
|
||||
): PinnedMessagesListState {
|
||||
return when (timelineItems) {
|
||||
AsyncData.Uninitialized, is AsyncData.Loading -> PinnedMessagesListState.Loading
|
||||
is AsyncData.Failure -> PinnedMessagesListState.Failed
|
||||
is AsyncData.Success -> {
|
||||
if (timelineItems.data.isEmpty()) {
|
||||
if (timelineItems.data.all { it is TimelineItem.Virtual }) {
|
||||
PinnedMessagesListState.Empty
|
||||
} else {
|
||||
val actionListState = actionListPresenter.present()
|
||||
@@ -251,6 +272,8 @@ class PinnedMessagesListPresenter(
|
||||
userEventPermissions = userEventPermissions,
|
||||
timelineItems = timelineItems.data,
|
||||
actionListState = actionListState,
|
||||
customReactionState = customReactionState,
|
||||
reactionSummaryState = reactionSummaryState,
|
||||
eventSink = eventSink
|
||||
)
|
||||
}
|
||||
|
||||
+4
@@ -16,6 +16,8 @@ import io.element.android.features.messages.impl.UserEventPermissions
|
||||
import io.element.android.features.messages.impl.actionlist.ActionListState
|
||||
import io.element.android.features.messages.impl.link.LinkState
|
||||
import io.element.android.features.messages.impl.timeline.TimelineRoomInfo
|
||||
import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionState
|
||||
import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryState
|
||||
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
||||
import io.element.android.features.messages.impl.timeline.protection.TimelineProtectionState
|
||||
import io.element.android.libraries.ui.strings.CommonPlurals
|
||||
@@ -34,6 +36,8 @@ sealed interface PinnedMessagesListState {
|
||||
val timelineItems: ImmutableList<TimelineItem>,
|
||||
val actionListState: ActionListState,
|
||||
val linkState: LinkState,
|
||||
val customReactionState: CustomReactionState,
|
||||
val reactionSummaryState: ReactionSummaryState,
|
||||
val displayThreadSummaries: Boolean,
|
||||
val eventSink: (PinnedMessagesListEvent) -> Unit,
|
||||
) : PinnedMessagesListState {
|
||||
|
||||
+9
-1
@@ -10,6 +10,8 @@ package io.element.android.features.messages.impl.pinned.list
|
||||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.features.messages.impl.UserEventPermissions
|
||||
import io.element.android.features.messages.impl.aCustomReactionState
|
||||
import io.element.android.features.messages.impl.aReactionSummaryState
|
||||
import io.element.android.features.messages.impl.actionlist.ActionListState
|
||||
import io.element.android.features.messages.impl.actionlist.anActionListState
|
||||
import io.element.android.features.messages.impl.link.LinkState
|
||||
@@ -19,6 +21,8 @@ import io.element.android.features.messages.impl.timeline.aTimelineItemDaySepara
|
||||
import io.element.android.features.messages.impl.timeline.aTimelineItemEvent
|
||||
import io.element.android.features.messages.impl.timeline.aTimelineItemReactions
|
||||
import io.element.android.features.messages.impl.timeline.aTimelineRoomInfo
|
||||
import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionState
|
||||
import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryState
|
||||
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
||||
import io.element.android.features.messages.impl.timeline.model.TimelineItemGroupPosition
|
||||
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemAudioContent
|
||||
@@ -30,7 +34,7 @@ import io.element.android.features.messages.impl.timeline.protection.aTimelinePr
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
open class PinnedMessagesListStateProvider : PreviewParameterProvider<PinnedMessagesListState> {
|
||||
open class PinnedMessagesListStatePreviewParam : PreviewParameterProvider<PinnedMessagesListState> {
|
||||
override val values: Sequence<PinnedMessagesListState>
|
||||
get() = sequenceOf(
|
||||
aFailedPinnedMessagesListState(),
|
||||
@@ -92,6 +96,8 @@ fun aLoadedPinnedMessagesListState(
|
||||
linkState: LinkState = aLinkState(),
|
||||
timelineItems: List<TimelineItem> = emptyList(),
|
||||
actionListState: ActionListState = anActionListState(),
|
||||
customReactionState: CustomReactionState = aCustomReactionState(),
|
||||
reactionSummaryState: ReactionSummaryState = aReactionSummaryState(),
|
||||
aUserEventPermissions: UserEventPermissions = UserEventPermissions.DEFAULT,
|
||||
displayThreadSummaries: Boolean = false,
|
||||
eventSink: (PinnedMessagesListEvent) -> Unit = {}
|
||||
@@ -101,6 +107,8 @@ fun aLoadedPinnedMessagesListState(
|
||||
linkState = linkState,
|
||||
timelineItems = timelineItems.toImmutableList(),
|
||||
actionListState = actionListState,
|
||||
customReactionState = customReactionState,
|
||||
reactionSummaryState = reactionSummaryState,
|
||||
userEventPermissions = aUserEventPermissions,
|
||||
displayThreadSummaries = displayThreadSummaries,
|
||||
eventSink = eventSink,
|
||||
+40
-6
@@ -33,8 +33,12 @@ import io.element.android.features.messages.impl.link.LinkEvent
|
||||
import io.element.android.features.messages.impl.link.LinkView
|
||||
import io.element.android.features.messages.impl.timeline.TimelineEvent
|
||||
import io.element.android.features.messages.impl.timeline.components.TimelineItemRow
|
||||
import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionBottomSheet
|
||||
import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionEvent
|
||||
import io.element.android.features.messages.impl.timeline.components.event.TimelineItemEventContentView
|
||||
import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData
|
||||
import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryEvent
|
||||
import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryView
|
||||
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPollContent
|
||||
import io.element.android.features.messages.impl.timeline.protection.TimelineProtectionState
|
||||
@@ -50,6 +54,8 @@ import io.element.android.libraries.designsystem.theme.components.Scaffold
|
||||
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||
import io.element.android.libraries.designsystem.utils.lazyColumnContentPadding
|
||||
import io.element.android.libraries.designsystem.utils.scaffoldScrollableContentInsets
|
||||
import io.element.android.libraries.emoji.api.picker.EmojiPickerRenderer
|
||||
import io.element.android.libraries.emoji.api.picker.NoOpEmojiPickerRenderer
|
||||
import io.element.android.libraries.matrix.api.timeline.Timeline
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
@@ -66,6 +72,7 @@ fun PinnedMessagesListView(
|
||||
onUserDataClick: (MatrixUser) -> Unit,
|
||||
onLinkClick: (Link) -> Unit,
|
||||
onLinkLongClick: (Link) -> Unit,
|
||||
emojiPickerRenderer: EmojiPickerRenderer,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Scaffold(
|
||||
@@ -89,6 +96,7 @@ fun PinnedMessagesListView(
|
||||
onLinkClick = onLinkClick,
|
||||
onLinkLongClick = onLinkLongClick,
|
||||
onErrorDismiss = onBackClick,
|
||||
emojiPickerRenderer = emojiPickerRenderer,
|
||||
modifier = Modifier
|
||||
.padding(padding)
|
||||
.consumeWindowInsets(padding),
|
||||
@@ -121,6 +129,7 @@ private fun PinnedMessagesListContent(
|
||||
onLinkClick: (Link) -> Unit,
|
||||
onLinkLongClick: (Link) -> Unit,
|
||||
onErrorDismiss: () -> Unit,
|
||||
emojiPickerRenderer: EmojiPickerRenderer,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Box(modifier.fillMaxSize()) {
|
||||
@@ -141,6 +150,7 @@ private fun PinnedMessagesListContent(
|
||||
onUserDataClick = onUserDataClick,
|
||||
onLinkClick = onLinkClick,
|
||||
onLinkLongClick = onLinkLongClick,
|
||||
emojiPickerRenderer = emojiPickerRenderer,
|
||||
)
|
||||
PinnedMessagesListState.Loading -> {
|
||||
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||
@@ -180,6 +190,7 @@ private fun PinnedMessagesListLoaded(
|
||||
onUserDataClick: (MatrixUser) -> Unit,
|
||||
onLinkClick: (Link) -> Unit,
|
||||
onLinkLongClick: (Link) -> Unit,
|
||||
emojiPickerRenderer: EmojiPickerRenderer,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
fun onActionSelected(timelineItemAction: TimelineItemAction, event: TimelineItem.Event) {
|
||||
@@ -203,11 +214,25 @@ private fun PinnedMessagesListLoaded(
|
||||
)
|
||||
}
|
||||
|
||||
fun onReactionClick(emoji: String, event: TimelineItem.Event) {
|
||||
state.eventSink(PinnedMessagesListEvent.ToggleReaction(emoji, event.eventOrTransactionId))
|
||||
}
|
||||
|
||||
fun onReactionLongClick(emoji: String, event: TimelineItem.Event) {
|
||||
event.eventId?.let { eventId ->
|
||||
state.reactionSummaryState.eventSink(ReactionSummaryEvent.ShowReactionSummary(eventId, event.reactionsState.reactions, emoji))
|
||||
}
|
||||
}
|
||||
|
||||
fun onMoreReactionsClick(event: TimelineItem.Event) {
|
||||
state.customReactionState.eventSink(CustomReactionEvent.ShowCustomReactionSheet(event))
|
||||
}
|
||||
|
||||
ActionListView(
|
||||
state = state.actionListState,
|
||||
onSelectAction = ::onActionSelected,
|
||||
onCustomReactionClick = {},
|
||||
onEmojiReactionClick = { _, _ -> },
|
||||
onCustomReactionClick = ::onMoreReactionsClick,
|
||||
onEmojiReactionClick = ::onReactionClick,
|
||||
onVerifiedUserSendFailureClick = {}
|
||||
)
|
||||
|
||||
@@ -239,9 +264,9 @@ private fun PinnedMessagesListLoaded(
|
||||
onLongClick = ::onMessageLongClick,
|
||||
displayThreadSummaries = displayThreadSummaries,
|
||||
inReplyToClick = {},
|
||||
onReactionClick = { _, _ -> },
|
||||
onReactionLongClick = { _, _ -> },
|
||||
onMoreReactionsClick = {},
|
||||
onReactionClick = ::onReactionClick,
|
||||
onReactionLongClick = ::onReactionLongClick,
|
||||
onMoreReactionsClick = ::onMoreReactionsClick,
|
||||
onReadReceiptClick = {},
|
||||
onSwipeToReply = {},
|
||||
onJoinCallClick = {},
|
||||
@@ -273,6 +298,14 @@ private fun PinnedMessagesListLoaded(
|
||||
state.linkState,
|
||||
onLinkValid = onLinkClick,
|
||||
)
|
||||
CustomReactionBottomSheet(
|
||||
state = state.customReactionState,
|
||||
onSelectEmoji = { eventOrTransactionId, emoji ->
|
||||
state.eventSink(PinnedMessagesListEvent.ToggleReaction(emoji.unicode, eventOrTransactionId))
|
||||
},
|
||||
emojiPickerRenderer = emojiPickerRenderer,
|
||||
)
|
||||
ReactionSummaryView(state = state.reactionSummaryState)
|
||||
}
|
||||
|
||||
@Composable
|
||||
@@ -312,7 +345,7 @@ private fun TimelineItemEventContentViewWrapper(
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun PinnedMessagesListViewPreview(@PreviewParameter(PinnedMessagesListStateProvider::class) state: PinnedMessagesListState) =
|
||||
internal fun PinnedMessagesListViewPreview(@PreviewParameter(PinnedMessagesListStatePreviewParam::class) state: PinnedMessagesListState) =
|
||||
ElementPreview {
|
||||
PinnedMessagesListView(
|
||||
state = state,
|
||||
@@ -322,5 +355,6 @@ internal fun PinnedMessagesListViewPreview(@PreviewParameter(PinnedMessagesListS
|
||||
onUserDataClick = {},
|
||||
onLinkClick = {},
|
||||
onLinkLongClick = {},
|
||||
emojiPickerRenderer = NoOpEmojiPickerRenderer,
|
||||
)
|
||||
}
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ package io.element.android.features.messages.impl.report
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
|
||||
open class ReportMessageStateProvider : PreviewParameterProvider<ReportMessageState> {
|
||||
open class ReportMessageStatePreviewParam : PreviewParameterProvider<ReportMessageState> {
|
||||
override val values: Sequence<ReportMessageState>
|
||||
get() = sequenceOf(
|
||||
aReportMessageState(),
|
||||
+1
-1
@@ -137,7 +137,7 @@ fun ReportMessageView(
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun ReportMessageViewPreview(@PreviewParameter(ReportMessageStateProvider::class) state: ReportMessageState) = ElementPreview {
|
||||
internal fun ReportMessageViewPreview(@PreviewParameter(ReportMessageStatePreviewParam::class) state: ReportMessageState) = ElementPreview {
|
||||
ReportMessageView(
|
||||
onBackClick = {},
|
||||
state = state,
|
||||
|
||||
+13
-1
@@ -28,6 +28,7 @@ import dev.zacsweers.metro.Assisted
|
||||
import dev.zacsweers.metro.AssistedInject
|
||||
import io.element.android.annotations.ContributesNode
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.features.messages.impl.MessagesEvent
|
||||
import io.element.android.features.messages.impl.MessagesNavigator
|
||||
import io.element.android.features.messages.impl.MessagesPresenter
|
||||
import io.element.android.features.messages.impl.MessagesState
|
||||
@@ -40,6 +41,7 @@ import io.element.android.features.messages.impl.messagecomposer.MessageComposer
|
||||
import io.element.android.features.messages.impl.timeline.TimelineController
|
||||
import io.element.android.features.messages.impl.timeline.TimelineEvent
|
||||
import io.element.android.features.messages.impl.timeline.TimelinePresenter
|
||||
import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionBottomSheet
|
||||
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.model.TimelineItem
|
||||
@@ -54,6 +56,7 @@ import io.element.android.libraries.architecture.callback
|
||||
import io.element.android.libraries.architecture.inputs
|
||||
import io.element.android.libraries.designsystem.utils.OnLifecycleEvent
|
||||
import io.element.android.libraries.di.RoomScope
|
||||
import io.element.android.libraries.emoji.api.picker.EmojiPickerRenderer
|
||||
import io.element.android.libraries.matrix.api.analytics.toAnalyticsViewRoom
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
@@ -91,6 +94,7 @@ class ThreadedMessagesNode(
|
||||
private val permalinkParser: PermalinkParser,
|
||||
private val appNavigationStateService: AppNavigationStateService,
|
||||
private val roomMemberModerationRenderer: RoomMemberModerationRenderer,
|
||||
private val emojiPickerRenderer: EmojiPickerRenderer,
|
||||
) : Node(buildContext, plugins = plugins), MessagesNavigator {
|
||||
data class Inputs(
|
||||
val threadRootEventId: ThreadId,
|
||||
@@ -322,7 +326,15 @@ class ThreadedMessagesNode(
|
||||
onViewAllPinnedMessagesClick = {},
|
||||
modifier = modifier,
|
||||
knockRequestsBannerView = {},
|
||||
customReactionBottomSheet = {},
|
||||
customReactionBottomSheet = {
|
||||
CustomReactionBottomSheet(
|
||||
state = state.customReactionState,
|
||||
onSelectEmoji = { uniqueId, emoji ->
|
||||
state.eventSink(MessagesEvent.ToggleReaction(emoji.unicode, uniqueId))
|
||||
},
|
||||
emojiPickerRenderer = emojiPickerRenderer,
|
||||
)
|
||||
},
|
||||
onThreadsListClick = {},
|
||||
)
|
||||
|
||||
|
||||
+3
@@ -34,6 +34,9 @@ sealed interface TimelineEvent {
|
||||
*/
|
||||
sealed interface TimelineItemEvent : TimelineEvent
|
||||
|
||||
data class ShowSendFailureDialog(val event: TimelineItem.Event) : TimelineItemEvent
|
||||
data object HideSendFailureDialog : TimelineEvent
|
||||
|
||||
data class ComputeVerifiedUserSendFailure(val event: TimelineItem.Event) : TimelineItemEvent
|
||||
data class ShowShieldDialog(val messageShieldData: MessageShieldData) : TimelineItemEvent
|
||||
data class LoadMore(val direction: Timeline.PaginationDirection) : TimelineItemEvent
|
||||
|
||||
+63
-3
@@ -37,6 +37,7 @@ import io.element.android.features.messages.impl.timeline.model.virtual.Timeline
|
||||
import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemTypingNotificationModel
|
||||
import io.element.android.features.messages.impl.timeline.protection.TimelineProtectionEvent
|
||||
import io.element.android.features.messages.impl.timeline.protection.TimelineProtectionState
|
||||
import io.element.android.features.messages.impl.timeline.sendfailure.SendFailureDialogState
|
||||
import io.element.android.features.messages.impl.typing.TypingNotificationState
|
||||
import io.element.android.features.messages.impl.userEventPermissions
|
||||
import io.element.android.features.messages.impl.voicemessages.timeline.RedactedVoiceMessageManager
|
||||
@@ -56,6 +57,7 @@ import io.element.android.libraries.matrix.api.room.powerlevels.permissionsAsSta
|
||||
import io.element.android.libraries.matrix.api.room.roomMembers
|
||||
import io.element.android.libraries.matrix.api.timeline.ReceiptType
|
||||
import io.element.android.libraries.matrix.api.timeline.Timeline
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.TimelineItemEventOrigin
|
||||
import io.element.android.libraries.preferences.api.store.SessionPreferencesStore
|
||||
import io.element.android.services.analytics.api.AnalyticsLongRunningTransaction.DisplayFirstTimelineItems
|
||||
@@ -142,6 +144,7 @@ class TimelinePresenter(
|
||||
|
||||
val newEventState = remember { mutableStateOf<NewEventState>(NewEventState.None) }
|
||||
val messageShieldDialogData: MutableState<MessageShieldData?> = remember { mutableStateOf(null) }
|
||||
var sendFailureDialogState by remember { mutableStateOf<SendFailureDialogState>(SendFailureDialogState.Hidden) }
|
||||
|
||||
// Forces [JumpToUnreadState.Hidden] until the next RoomInfo push. Set after a
|
||||
// [TimelineEvent.MarkAllAsRead] await completes so the FAB hides without waiting for
|
||||
@@ -166,9 +169,14 @@ class TimelinePresenter(
|
||||
fun handleEvent(event: TimelineEvent) {
|
||||
when (event) {
|
||||
is TimelineEvent.LoadMore -> {
|
||||
if (event.direction == Timeline.PaginationDirection.FORWARDS && timelineMode is Timeline.Mode.Thread) {
|
||||
// Do not paginate forwards in thread mode, as it's not supported
|
||||
return
|
||||
if (event.direction == Timeline.PaginationDirection.FORWARDS) {
|
||||
if (timelineMode is Timeline.Mode.Thread) {
|
||||
// Do not paginate forwards in thread mode, as it's not supported
|
||||
return
|
||||
}
|
||||
if (focusRequestState.value.isPending()) {
|
||||
return
|
||||
}
|
||||
}
|
||||
localScope.launch {
|
||||
timelineController.paginate(direction = event.direction)
|
||||
@@ -246,6 +254,47 @@ class TimelinePresenter(
|
||||
suppressJumpToUnread.value = true
|
||||
}
|
||||
is TimelineEvent.ShowShieldDialog -> messageShieldDialogData.value = event.messageShieldData
|
||||
is TimelineEvent.ShowSendFailureDialog -> {
|
||||
val sendStateFailure = event.event.localSendState as? LocalEventSendState.Failed ?: return
|
||||
when (sendStateFailure) {
|
||||
is LocalEventSendState.Failed.VerifiedUser -> {
|
||||
// defer to the resolveVerifiedUserSendFailureState to handle this case,
|
||||
// as it will show a dialog with the right information
|
||||
resolveVerifiedUserSendFailureState.eventSink(ResolveVerifiedUserSendFailureEvent.ComputeForMessage(event.event))
|
||||
}
|
||||
is LocalEventSendState.Failed.InvalidMimeType -> {
|
||||
sendFailureDialogState = SendFailureDialogState.Show(
|
||||
event = event.event,
|
||||
sendFailureType = SendFailureDialogState.SendFailureType.InvalidMimeType(sendStateFailure.mimeType),
|
||||
)
|
||||
}
|
||||
LocalEventSendState.Failed.MissingMediaContent -> {
|
||||
sendFailureDialogState = SendFailureDialogState.Show(
|
||||
event = event.event,
|
||||
sendFailureType = SendFailureDialogState.SendFailureType.MissingMediaContent,
|
||||
)
|
||||
}
|
||||
LocalEventSendState.Failed.SendingFromUnverifiedDevice -> {
|
||||
sendFailureDialogState = SendFailureDialogState.Show(
|
||||
event = event.event,
|
||||
sendFailureType = SendFailureDialogState.SendFailureType.SendingFromUnverifiedDevice,
|
||||
)
|
||||
}
|
||||
is LocalEventSendState.Failed.Unknown -> {
|
||||
sendFailureDialogState = SendFailureDialogState.Show(
|
||||
event = event.event,
|
||||
sendFailureType = if (sendStateFailure.error.isNotBlank()) {
|
||||
SendFailureDialogState.SendFailureType.Error(sendStateFailure.error)
|
||||
} else {
|
||||
SendFailureDialogState.SendFailureType.Unknown
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
is TimelineEvent.HideSendFailureDialog -> {
|
||||
sendFailureDialogState = SendFailureDialogState.Hidden
|
||||
}
|
||||
is TimelineEvent.ComputeVerifiedUserSendFailure -> {
|
||||
resolveVerifiedUserSendFailureState.eventSink(ResolveVerifiedUserSendFailureEvent.ComputeForMessage(event.event))
|
||||
}
|
||||
@@ -410,6 +459,7 @@ class TimelinePresenter(
|
||||
focusRequestState = focusRequestState.value,
|
||||
messageShieldDialogData = messageShieldDialogData.value,
|
||||
resolveVerifiedUserSendFailureState = resolveVerifiedUserSendFailureState,
|
||||
sendFailureDialogState = sendFailureDialogState,
|
||||
displayThreadSummaries = displayThreadSummaries,
|
||||
displayJumpToUnread = displayJumpToUnread,
|
||||
jumpToUnread = jumpToUnread.value,
|
||||
@@ -539,6 +589,16 @@ private fun FocusRequestState.onFocusEventRender(): FocusRequestState {
|
||||
}
|
||||
}
|
||||
|
||||
private fun FocusRequestState.isPending(): Boolean {
|
||||
return when (this) {
|
||||
is FocusRequestState.Requested,
|
||||
is FocusRequestState.Loading -> true
|
||||
is FocusRequestState.Success -> !rendered
|
||||
FocusRequestState.None,
|
||||
is FocusRequestState.Failure -> false
|
||||
}
|
||||
}
|
||||
|
||||
// Workaround for not having the server names available, get possible server names from the user ids of the room members
|
||||
private fun calculateServerNamesForRoom(room: JoinedRoom): List<String> {
|
||||
// If we have no room members, return right ahead
|
||||
|
||||
+2
@@ -13,6 +13,7 @@ import io.element.android.features.messages.impl.crypto.sendfailure.resolve.Reso
|
||||
import io.element.android.features.messages.impl.timeline.components.MessageShieldData
|
||||
import io.element.android.features.messages.impl.timeline.model.NewEventState
|
||||
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
||||
import io.element.android.features.messages.impl.timeline.sendfailure.SendFailureDialogState
|
||||
import io.element.android.features.messages.impl.typing.TypingNotificationState
|
||||
import io.element.android.features.roomcall.api.RoomCallState
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
@@ -32,6 +33,7 @@ data class TimelineState(
|
||||
// If not null, info will be rendered in a dialog
|
||||
val messageShieldDialogData: MessageShieldData?,
|
||||
val resolveVerifiedUserSendFailureState: ResolveVerifiedUserSendFailureState,
|
||||
val sendFailureDialogState: SendFailureDialogState,
|
||||
val displayThreadSummaries: Boolean,
|
||||
val displayJumpToUnread: Boolean,
|
||||
val jumpToUnread: JumpToUnreadState,
|
||||
|
||||
+3
@@ -25,6 +25,7 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
|
||||
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemStateEventContent
|
||||
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemTextContent
|
||||
import io.element.android.features.messages.impl.timeline.model.virtual.aTimelineItemDaySeparatorModel
|
||||
import io.element.android.features.messages.impl.timeline.sendfailure.SendFailureDialogState
|
||||
import io.element.android.features.messages.impl.typing.TypingNotificationState
|
||||
import io.element.android.features.messages.impl.typing.aTypingNotificationState
|
||||
import io.element.android.features.roomcall.api.aStandByCallState
|
||||
@@ -57,6 +58,7 @@ fun aTimelineState(
|
||||
isLive: Boolean = true,
|
||||
messageShield: MessageShield? = null,
|
||||
resolveVerifiedUserSendFailureState: ResolveVerifiedUserSendFailureState = aResolveVerifiedUserSendFailureState(),
|
||||
sendFailureDialogState: SendFailureDialogState = SendFailureDialogState.Hidden,
|
||||
displayThreadSummaries: Boolean = false,
|
||||
displayJumpToUnread: Boolean = false,
|
||||
jumpToUnread: JumpToUnreadState = JumpToUnreadState.Hidden,
|
||||
@@ -78,6 +80,7 @@ fun aTimelineState(
|
||||
focusRequestState = focusRequestState,
|
||||
messageShieldDialogData = messageShield?.let { MessageShieldData(it) },
|
||||
resolveVerifiedUserSendFailureState = resolveVerifiedUserSendFailureState,
|
||||
sendFailureDialogState = sendFailureDialogState,
|
||||
displayThreadSummaries = displayThreadSummaries,
|
||||
displayJumpToUnread = displayJumpToUnread,
|
||||
jumpToUnread = jumpToUnread,
|
||||
|
||||
+2
-2
@@ -90,7 +90,7 @@ import io.element.android.features.messages.impl.timeline.focus.FocusRequestStat
|
||||
import io.element.android.features.messages.impl.timeline.model.NewEventState
|
||||
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContentProvider
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContentPreviewParam
|
||||
import io.element.android.features.messages.impl.timeline.protection.TimelineProtectionState
|
||||
import io.element.android.features.messages.impl.timeline.protection.aTimelineProtectionState
|
||||
import io.element.android.libraries.androidutils.system.copyToClipboard
|
||||
@@ -582,7 +582,7 @@ private fun JumpToPositionButton(
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun TimelineViewPreview(
|
||||
@PreviewParameter(TimelineItemEventContentProvider::class) content: TimelineItemEventContent
|
||||
@PreviewParameter(TimelineItemEventContentPreviewParam::class) content: TimelineItemEventContent
|
||||
) = ElementPreview {
|
||||
val timelineItems = aTimelineItemList(content)
|
||||
val timelineEvents = timelineItems.filterIsInstance<TimelineItem.Event>()
|
||||
|
||||
+2
-2
@@ -25,7 +25,7 @@ import androidx.compose.ui.unit.dp
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||
import io.element.android.features.roomcall.api.RoomCallState
|
||||
import io.element.android.features.roomcall.api.RoomCallStateProvider
|
||||
import io.element.android.features.roomcall.api.RoomCallStatePreviewParam
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.theme.components.Icon
|
||||
@@ -133,7 +133,7 @@ private fun OnGoingCallMenuItem(
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun CallMenuItemPreview(
|
||||
@PreviewParameter(RoomCallStateProvider::class) roomCallState: RoomCallState
|
||||
@PreviewParameter(RoomCallStatePreviewParam::class) roomCallState: RoomCallState
|
||||
) = ElementPreview {
|
||||
CallMenuItem(
|
||||
roomCallState = roomCallState,
|
||||
|
||||
+2
-2
@@ -38,7 +38,7 @@ import androidx.compose.ui.unit.dp
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.features.messages.impl.timeline.model.TimelineItemGroupPosition
|
||||
import io.element.android.features.messages.impl.timeline.model.bubble.BubbleState
|
||||
import io.element.android.features.messages.impl.timeline.model.bubble.BubbleStateProvider
|
||||
import io.element.android.features.messages.impl.timeline.model.bubble.BubbleStatePreviewParam
|
||||
import io.element.android.libraries.core.extensions.to01
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||
import io.element.android.libraries.designsystem.modifiers.onKeyboardContextMenuAction
|
||||
@@ -192,7 +192,7 @@ object MessageEventBubbleDefaults {
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun MessageEventBubblePreview(@PreviewParameter(BubbleStateProvider::class) state: BubbleState) = ElementPreview {
|
||||
internal fun MessageEventBubblePreview(@PreviewParameter(BubbleStatePreviewParam::class) state: BubbleState) = ElementPreview {
|
||||
// Due to position offset, surround with a Box
|
||||
Box(
|
||||
modifier = Modifier
|
||||
|
||||
+2
-2
@@ -41,7 +41,7 @@ import io.element.android.features.messages.impl.R
|
||||
import io.element.android.features.messages.impl.timeline.a11y.a11yReactionAction
|
||||
import io.element.android.features.messages.impl.timeline.a11y.a11yReactionDetails
|
||||
import io.element.android.features.messages.impl.timeline.model.AggregatedReaction
|
||||
import io.element.android.features.messages.impl.timeline.model.AggregatedReactionProvider
|
||||
import io.element.android.features.messages.impl.timeline.model.AggregatedReactionPreviewParam
|
||||
import io.element.android.features.messages.impl.timeline.model.aTimelineItemReactions
|
||||
import io.element.android.libraries.designsystem.icons.CompoundDrawables
|
||||
import io.element.android.libraries.designsystem.modifiers.onKeyboardContextMenuAction
|
||||
@@ -199,7 +199,7 @@ private fun ReactionContent(
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun MessagesReactionButtonPreview(@PreviewParameter(AggregatedReactionProvider::class) reaction: AggregatedReaction) = ElementPreview {
|
||||
internal fun MessagesReactionButtonPreview(@PreviewParameter(AggregatedReactionPreviewParam::class) reaction: AggregatedReaction) = ElementPreview {
|
||||
MessagesReactionButton(
|
||||
content = MessagesReactionsButtonContent.Reaction(reaction),
|
||||
onClick = {},
|
||||
|
||||
+7
-5
@@ -56,10 +56,11 @@ fun TimelineEventTimestampView(
|
||||
val isVerifiedUserSendFailure = event.localSendState is LocalEventSendState.Failed.VerifiedUser
|
||||
val onClickLabel = when {
|
||||
shield != null -> stringResource(CommonStrings.a11y_view_details)
|
||||
hasError && isVerifiedUserSendFailure -> stringResource(CommonStrings.action_open_context_menu)
|
||||
isVerifiedUserSendFailure -> stringResource(CommonStrings.action_open_context_menu)
|
||||
hasError -> stringResource(CommonStrings.a11y_view_details)
|
||||
else -> null
|
||||
}
|
||||
val clickableModifier = remember(shield, hasError) {
|
||||
val clickableModifier = remember(event) {
|
||||
when {
|
||||
shield != null -> {
|
||||
Modifier.clickable(
|
||||
@@ -70,10 +71,9 @@ fun TimelineEventTimestampView(
|
||||
}
|
||||
hasError -> Modifier
|
||||
.clickable(
|
||||
enabled = isVerifiedUserSendFailure,
|
||||
onClickLabel = onClickLabel,
|
||||
) {
|
||||
eventSink(TimelineEvent.ComputeVerifiedUserSendFailure(event))
|
||||
eventSink(TimelineEvent.ShowSendFailureDialog(event))
|
||||
}
|
||||
else -> Modifier
|
||||
}
|
||||
@@ -134,7 +134,9 @@ fun TimelineEventTimestampView(
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun TimelineEventTimestampViewPreview(@PreviewParameter(TimelineItemEventForTimestampViewProvider::class) event: TimelineItem.Event) = ElementPreview {
|
||||
internal fun TimelineEventTimestampViewPreview(@PreviewParameter(
|
||||
TimelineItemEventForTimestampViewPreviewParam::class
|
||||
) event: TimelineItem.Event) = ElementPreview {
|
||||
TimelineEventTimestampView(
|
||||
event = event,
|
||||
eventSink = {},
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@ import io.element.android.features.messages.impl.timeline.model.event.aTimelineI
|
||||
import io.element.android.features.messages.impl.timeline.model.event.anAttachmentItem
|
||||
import kotlin.time.Duration
|
||||
|
||||
class TimelineItemEventContentForGalleryViewProvider :
|
||||
class TimelineItemEventContentForGalleryViewPreviewParam :
|
||||
PreviewParameterProvider<TimelineItemEventContent> {
|
||||
override val values: Sequence<TimelineItemEventContent>
|
||||
get() = sequenceOf(
|
||||
+1
-1
@@ -16,7 +16,7 @@ import io.element.android.features.messages.impl.timeline.model.event.aTimelineI
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.MessageShield
|
||||
|
||||
class TimelineItemEventForTimestampViewProvider : PreviewParameterProvider<TimelineItem.Event> {
|
||||
class TimelineItemEventForTimestampViewPreviewParam : PreviewParameterProvider<TimelineItem.Event> {
|
||||
override val values: Sequence<TimelineItem.Event>
|
||||
get() = sequenceOf(
|
||||
aTimelineItemEvent(),
|
||||
+7
-10
@@ -125,6 +125,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.getAvatarUrl
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.getDisambiguatedDisplayName
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.getDisplayName
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.mediaSources
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.toMatrixUser
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
import io.element.android.libraries.matrix.ui.media.contentvalidation.collectOverallState
|
||||
import io.element.android.libraries.matrix.ui.media.contentvalidation.rememberEventContentValidationState
|
||||
@@ -207,11 +208,7 @@ fun TimelineItemEventRow(
|
||||
}
|
||||
|
||||
fun onUserDataClick() {
|
||||
val sender = MatrixUser(
|
||||
userId = event.senderId,
|
||||
displayName = event.senderProfile.getDisplayName(),
|
||||
avatarUrl = event.senderProfile.getAvatarUrl(),
|
||||
)
|
||||
val sender = event.senderProfile.toMatrixUser(event.senderId)
|
||||
onUserDataClick(sender)
|
||||
}
|
||||
|
||||
@@ -804,15 +801,15 @@ private fun MessageEventBubbleContent(
|
||||
.padding(top = topPadding, start = 8.dp, end = 8.dp)
|
||||
.clip(shape)
|
||||
|
||||
val talkbackCompatModifier = if (isTalkbackActive()) {
|
||||
val talkbackCompatModifier = when {
|
||||
// Use z-index to make the replied to text being read after the message
|
||||
// Usually, you'd use traversalIndex for that, but it's not working for some reason
|
||||
inReplyToModifier.zIndex(1f)
|
||||
} else {
|
||||
inReplyToModifier.clickable(onClick = inReplyToClick)
|
||||
isTalkbackActive() -> inReplyToModifier.zIndex(1f)
|
||||
inReplyTo is InReplyToDetails.Error -> inReplyToModifier
|
||||
else -> inReplyToModifier.clickable(onClick = inReplyToClick)
|
||||
}
|
||||
|
||||
val contentHasError = currentContentValidationState.hasError()
|
||||
val contentHasError = currentContentValidationState.hasError() || inReplyTo is InReplyToDetails.Error
|
||||
val borderColor = if (contentHasError) ElementTheme.colors.borderCriticalSubtle else ElementTheme.colors.separatorPrimary
|
||||
val backgroundColor = if (contentHasError) ElementTheme.colors.bgCriticalSubtle else ElementTheme.colors.bgCanvasDefault
|
||||
Box(
|
||||
|
||||
+2
-2
@@ -13,12 +13,12 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.matrix.ui.messages.reply.InReplyToDetails
|
||||
import io.element.android.libraries.matrix.ui.messages.reply.InReplyToDetailsDisambiguatedProvider
|
||||
import io.element.android.libraries.matrix.ui.messages.reply.InReplyToDetailsDisambiguatedPreviewParam
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun TimelineItemEventRowDisambiguatedPreview(
|
||||
@PreviewParameter(InReplyToDetailsDisambiguatedProvider::class) inReplyToDetails: InReplyToDetails,
|
||||
@PreviewParameter(InReplyToDetailsDisambiguatedPreviewParam::class) inReplyToDetails: InReplyToDetails,
|
||||
) = ElementPreview {
|
||||
TimelineItemEventRowWithReplyContentToPreview(
|
||||
inReplyToDetails = inReplyToDetails,
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@ import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun TimelineItemEventRowWithGalleryPreview(
|
||||
@PreviewParameter(TimelineItemEventContentForGalleryViewProvider::class) content: TimelineItemEventContent,
|
||||
@PreviewParameter(TimelineItemEventContentForGalleryViewPreviewParam::class) content: TimelineItemEventContent,
|
||||
) = ElementPreview {
|
||||
Column {
|
||||
sequenceOf(false, true).forEach { isMine ->
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@ import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun TimelineItemEventRowTimestampPreview(
|
||||
@PreviewParameter(TimelineItemEventForTimestampViewProvider::class) event: TimelineItem.Event
|
||||
@PreviewParameter(TimelineItemEventForTimestampViewPreviewParam::class) event: TimelineItem.Event
|
||||
) = ElementPreview {
|
||||
Column {
|
||||
when (event.content) {
|
||||
|
||||
+2
-2
@@ -14,7 +14,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import io.element.android.features.messages.impl.timeline.aTimelineItemEvent
|
||||
import io.element.android.features.messages.impl.timeline.aTimelineItemReactions
|
||||
import io.element.android.features.messages.impl.timeline.components.receipt.ReadReceiptViewState
|
||||
import io.element.android.features.messages.impl.timeline.components.receipt.ReadReceiptViewStateForTimelineItemEventRowProvider
|
||||
import io.element.android.features.messages.impl.timeline.components.receipt.ReadReceiptViewStateForTimelineItemEventRowPreviewParam
|
||||
import io.element.android.features.messages.impl.timeline.model.TimelineItemReadReceipts
|
||||
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemTextContent
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
@@ -25,7 +25,7 @@ import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun TimelineItemEventRowWithRRPreview(
|
||||
@PreviewParameter(ReadReceiptViewStateForTimelineItemEventRowProvider::class) state: ReadReceiptViewState,
|
||||
@PreviewParameter(ReadReceiptViewStateForTimelineItemEventRowPreviewParam::class) state: ReadReceiptViewState,
|
||||
) = ElementPreview {
|
||||
Column {
|
||||
// A message from someone else
|
||||
|
||||
+2
-2
@@ -13,12 +13,12 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.matrix.ui.messages.reply.InReplyToDetails
|
||||
import io.element.android.libraries.matrix.ui.messages.reply.InReplyToDetailsInformativeProvider
|
||||
import io.element.android.libraries.matrix.ui.messages.reply.InReplyToDetailsInformativePreviewParam
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun TimelineItemEventRowWithReplyInformativePreview(
|
||||
@PreviewParameter(InReplyToDetailsInformativeProvider::class) inReplyToDetails: InReplyToDetails,
|
||||
@PreviewParameter(InReplyToDetailsInformativePreviewParam::class) inReplyToDetails: InReplyToDetails,
|
||||
) = ElementPreview {
|
||||
TimelineItemEventRowWithReplyContentToPreview(inReplyToDetails)
|
||||
}
|
||||
|
||||
+2
-2
@@ -13,12 +13,12 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.matrix.ui.messages.reply.InReplyToDetails
|
||||
import io.element.android.libraries.matrix.ui.messages.reply.InReplyToDetailsOtherProvider
|
||||
import io.element.android.libraries.matrix.ui.messages.reply.InReplyToDetailsOtherPreviewParam
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun TimelineItemEventRowWithReplyOtherPreview(
|
||||
@PreviewParameter(InReplyToDetailsOtherProvider::class) inReplyToDetails: InReplyToDetails,
|
||||
@PreviewParameter(InReplyToDetailsOtherPreviewParam::class) inReplyToDetails: InReplyToDetails,
|
||||
) = ElementPreview {
|
||||
TimelineItemEventRowWithReplyContentToPreview(inReplyToDetails)
|
||||
}
|
||||
|
||||
+2
-2
@@ -21,12 +21,12 @@ import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.matrix.api.core.ThreadId
|
||||
import io.element.android.libraries.matrix.ui.messages.reply.InReplyToDetails
|
||||
import io.element.android.libraries.matrix.ui.messages.reply.InReplyToDetailsProvider
|
||||
import io.element.android.libraries.matrix.ui.messages.reply.InReplyToDetailsPreviewParam
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun TimelineItemEventRowWithReplyPreview(
|
||||
@PreviewParameter(InReplyToDetailsProvider::class) inReplyToDetails: InReplyToDetails,
|
||||
@PreviewParameter(InReplyToDetailsPreviewParam::class) inReplyToDetails: InReplyToDetails,
|
||||
) = ElementPreview {
|
||||
TimelineItemEventRowWithReplyContentToPreview(inReplyToDetails)
|
||||
}
|
||||
|
||||
+17
@@ -8,11 +8,16 @@
|
||||
|
||||
package io.element.android.features.messages.impl.timeline.components.customreaction
|
||||
|
||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.isImeVisible
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.SheetState
|
||||
import androidx.compose.material3.SheetValue
|
||||
import androidx.compose.material3.rememberBottomSheetState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Modifier
|
||||
import io.element.android.emojibasebindings.Emoji
|
||||
@@ -54,6 +59,7 @@ fun CustomReactionBottomSheet(
|
||||
modifier = modifier,
|
||||
scrollable = false,
|
||||
) {
|
||||
ExpandSheetWhenImeIsVisible(sheetState)
|
||||
emojiPickerRenderer.Render(
|
||||
state = state.target.emojiPickerState,
|
||||
onSelectEmoji = ::onEmojiSelectedDismiss,
|
||||
@@ -67,3 +73,14 @@ fun CustomReactionBottomSheet(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalLayoutApi::class, ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
internal fun ExpandSheetWhenImeIsVisible(sheetState: SheetState) {
|
||||
val isImeVisible = WindowInsets.isImeVisible
|
||||
LaunchedEffect(isImeVisible) {
|
||||
if (isImeVisible) {
|
||||
sheetState.expand()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@ import io.element.android.features.messages.impl.timeline.model.event.aTimelineI
|
||||
import io.element.android.features.messages.impl.timeline.model.event.anAttachmentItem
|
||||
import io.element.android.libraries.matrix.api.media.MediaSource
|
||||
|
||||
class TimelineItemAttachmentsContentProvider : PreviewParameterProvider<TimelineItemAttachmentsContent> {
|
||||
class TimelineItemAttachmentsContentPreviewParam : PreviewParameterProvider<TimelineItemAttachmentsContent> {
|
||||
override val values: Sequence<TimelineItemAttachmentsContent>
|
||||
get() = sequenceOf(
|
||||
aTimelineItemAttachmentsContent(
|
||||
+1
-1
@@ -265,7 +265,7 @@ private fun AttachmentListItem(
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun TimelineItemAttachmentsListViewPreview(
|
||||
@PreviewParameter(TimelineItemAttachmentsContentProvider::class) content: TimelineItemAttachmentsContent
|
||||
@PreviewParameter(TimelineItemAttachmentsContentPreviewParam::class) content: TimelineItemAttachmentsContent
|
||||
) = ElementPreview {
|
||||
TimelineItemAttachmentsListView(
|
||||
eventId = EventId("\$eventId"),
|
||||
|
||||
+2
-2
@@ -14,7 +14,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||
import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemAudioContent
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemAudioContentProvider
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemAudioContentPreviewParam
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.matrix.ui.media.contentvalidation.ContentValidationValue
|
||||
|
||||
@@ -38,7 +38,7 @@ fun TimelineItemAudioView(
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun TimelineItemAudioViewPreview(@PreviewParameter(TimelineItemAudioContentProvider::class) content: TimelineItemAudioContent) =
|
||||
internal fun TimelineItemAudioViewPreview(@PreviewParameter(TimelineItemAudioContentPreviewParam::class) content: TimelineItemAudioContent) =
|
||||
ElementTimelineItemPreview {
|
||||
TimelineItemAudioView(
|
||||
content,
|
||||
|
||||
+2
-2
@@ -14,7 +14,7 @@ import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEncryptedContent
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEncryptedContentProvider
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEncryptedContentPreviewParam
|
||||
import io.element.android.libraries.designsystem.icons.CompoundDrawables
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
@@ -75,7 +75,7 @@ fun TimelineItemEncryptedView(
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun TimelineItemEncryptedViewPreview(
|
||||
@PreviewParameter(TimelineItemEncryptedContentProvider::class) content: TimelineItemEncryptedContent
|
||||
@PreviewParameter(TimelineItemEncryptedContentPreviewParam::class) content: TimelineItemEncryptedContent
|
||||
) = ElementPreview {
|
||||
TimelineItemEncryptedView(
|
||||
content = content,
|
||||
|
||||
+2
-2
@@ -15,7 +15,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||
import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemFileContent
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemFileContentProvider
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemFileContentPreviewParam
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.matrix.ui.media.contentvalidation.ContentValidationValue
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
@@ -43,7 +43,7 @@ fun TimelineItemFileView(
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun TimelineItemFileViewPreview(@PreviewParameter(TimelineItemFileContentProvider::class) content: TimelineItemFileContent) {
|
||||
internal fun TimelineItemFileViewPreview(@PreviewParameter(TimelineItemFileContentPreviewParam::class) content: TimelineItemFileContent) {
|
||||
ElementTimelineItemPreview {
|
||||
TimelineItemFileView(
|
||||
content,
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@ import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
class TimelineItemGalleryContentProvider : PreviewParameterProvider<TimelineItemGalleryContent> {
|
||||
class TimelineItemGalleryContentPreviewParam : PreviewParameterProvider<TimelineItemGalleryContent> {
|
||||
override val values: Sequence<TimelineItemGalleryContent>
|
||||
get() = sequenceOf(
|
||||
aTimelineItemGalleryContent(
|
||||
+1
-1
@@ -390,7 +390,7 @@ private fun RemainingCountOverlay(count: Int) {
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun TimelineItemGalleryViewPreview(
|
||||
@PreviewParameter(TimelineItemGalleryContentProvider::class) content: TimelineItemGalleryContent,
|
||||
@PreviewParameter(TimelineItemGalleryContentPreviewParam::class) content: TimelineItemGalleryContent,
|
||||
) = ElementPreview {
|
||||
TimelineItemGalleryView(
|
||||
eventId = null,
|
||||
|
||||
+2
-2
@@ -33,7 +33,7 @@ import io.element.android.features.messages.impl.timeline.aTimelineItemEvent
|
||||
import io.element.android.features.messages.impl.timeline.components.ATimelineItemEventRow
|
||||
import io.element.android.features.messages.impl.timeline.model.TimelineItemGroupPosition
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemImageContent
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemImageContentProvider
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemImageContentPreviewParam
|
||||
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemImageContent
|
||||
import io.element.android.features.messages.impl.timeline.protection.ProtectedView
|
||||
import io.element.android.features.messages.impl.timeline.protection.coerceRatioWhenHidingContent
|
||||
@@ -125,7 +125,7 @@ fun TimelineItemImageView(
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun TimelineItemImageViewPreview(@PreviewParameter(TimelineItemImageContentProvider::class) content: TimelineItemImageContent) = ElementPreview {
|
||||
internal fun TimelineItemImageViewPreview(@PreviewParameter(TimelineItemImageContentPreviewParam::class) content: TimelineItemImageContent) = ElementPreview {
|
||||
TimelineItemImageView(
|
||||
content = content,
|
||||
hideMediaContent = false,
|
||||
|
||||
+4
-2
@@ -28,9 +28,10 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||
import io.element.android.features.location.api.LocalMapTilerConfig
|
||||
import io.element.android.features.location.api.StaticMapView
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLocationContent
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLocationContentProvider
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLocationContentPreviewParam
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator
|
||||
@@ -50,6 +51,7 @@ fun TimelineItemLocationView(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(max = 188.dp),
|
||||
mapTilerConfig = LocalMapTilerConfig.current,
|
||||
pinVariant = content.pinVariant,
|
||||
location = content.location,
|
||||
zoom = 15.0,
|
||||
@@ -160,7 +162,7 @@ private fun LiveLocationOverlay(
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun TimelineItemLocationViewPreview(@PreviewParameter(TimelineItemLocationContentProvider::class) content: TimelineItemLocationContent) =
|
||||
internal fun TimelineItemLocationViewPreview(@PreviewParameter(TimelineItemLocationContentPreviewParam::class) content: TimelineItemLocationContent) =
|
||||
ElementPreview {
|
||||
TimelineItemLocationView(
|
||||
content = content,
|
||||
|
||||
+2
-2
@@ -13,7 +13,7 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import io.element.android.features.messages.impl.timeline.TimelineEvent
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPollContent
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPollContentProvider
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPollContentPreviewParam
|
||||
import io.element.android.features.poll.api.pollcontent.PollContentView
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
@@ -57,7 +57,7 @@ fun TimelineItemPollView(
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun TimelineItemPollViewPreview(@PreviewParameter(TimelineItemPollContentProvider::class) content: TimelineItemPollContent) =
|
||||
internal fun TimelineItemPollViewPreview(@PreviewParameter(TimelineItemPollContentPreviewParam::class) content: TimelineItemPollContent) =
|
||||
ElementPreview {
|
||||
TimelineItemPollView(
|
||||
content = content,
|
||||
|
||||
+4
-2
@@ -28,7 +28,7 @@ import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import coil3.compose.AsyncImage
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStickerContent
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStickerContentProvider
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStickerContentPreviewParam
|
||||
import io.element.android.features.messages.impl.timeline.protection.ProtectedView
|
||||
import io.element.android.features.messages.impl.timeline.protection.coerceRatioWhenHidingContent
|
||||
import io.element.android.features.messages.impl.timeline.util.handleAsyncImageStateChange
|
||||
@@ -123,7 +123,9 @@ fun TimelineItemStickerView(
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun TimelineItemStickerViewPreview(@PreviewParameter(TimelineItemStickerContentProvider::class) content: TimelineItemStickerContent) = ElementPreview {
|
||||
internal fun TimelineItemStickerViewPreview(@PreviewParameter(
|
||||
TimelineItemStickerContentPreviewParam::class
|
||||
) content: TimelineItemStickerContent) = ElementPreview {
|
||||
TimelineItemStickerView(
|
||||
content = content,
|
||||
hideMediaContent = false,
|
||||
|
||||
+2
-2
@@ -24,7 +24,7 @@ import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayout
|
||||
import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextBasedContent
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextBasedContentProvider
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextBasedContentPreviewParam
|
||||
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemTextContent
|
||||
import io.element.android.features.messages.impl.utils.containsOnlyEmojis
|
||||
import io.element.android.libraries.androidutils.text.LinkifyHelper
|
||||
@@ -82,7 +82,7 @@ internal fun getTextWithResolvedMentions(content: TimelineItemTextBasedContent):
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun TimelineItemTextViewPreview(
|
||||
@PreviewParameter(TimelineItemTextBasedContentProvider::class) content: TimelineItemTextBasedContent
|
||||
@PreviewParameter(TimelineItemTextBasedContentPreviewParam::class) content: TimelineItemTextBasedContent
|
||||
) = ElementPreview {
|
||||
TimelineItemTextView(
|
||||
content = content,
|
||||
|
||||
+2
-2
@@ -37,7 +37,7 @@ import androidx.compose.ui.unit.dp
|
||||
import coil3.compose.AsyncImage
|
||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVideoContent
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVideoContentProvider
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVideoContentPreviewParam
|
||||
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemVideoContent
|
||||
import io.element.android.features.messages.impl.timeline.protection.ProtectedView
|
||||
import io.element.android.features.messages.impl.timeline.protection.coerceRatioWhenHidingContent
|
||||
@@ -150,7 +150,7 @@ fun TimelineItemVideoView(
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun TimelineItemVideoViewPreview(@PreviewParameter(TimelineItemVideoContentProvider::class) content: TimelineItemVideoContent) = ElementPreview {
|
||||
internal fun TimelineItemVideoViewPreview(@PreviewParameter(TimelineItemVideoContentPreviewParam::class) content: TimelineItemVideoContent) = ElementPreview {
|
||||
TimelineItemVideoView(
|
||||
content = content,
|
||||
hideMediaContent = false,
|
||||
|
||||
+7
-7
@@ -42,7 +42,7 @@ import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||
import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVoiceContent
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVoiceContentProvider
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVoiceContentPreviewParam
|
||||
import io.element.android.libraries.designsystem.atomic.atoms.PlaybackSpeedButton
|
||||
import io.element.android.libraries.designsystem.components.media.WaveformPlaybackView
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
@@ -56,7 +56,7 @@ import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import io.element.android.libraries.ui.utils.a11y.isTalkbackActive
|
||||
import io.element.android.libraries.voiceplayer.api.VoiceMessageEvent
|
||||
import io.element.android.libraries.voiceplayer.api.VoiceMessageState
|
||||
import io.element.android.libraries.voiceplayer.api.VoiceMessageStateProvider
|
||||
import io.element.android.libraries.voiceplayer.api.VoiceMessageStatePreviewParam
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
@Composable
|
||||
@@ -261,9 +261,9 @@ private fun CustomIconButton(
|
||||
)
|
||||
}
|
||||
|
||||
open class TimelineItemVoiceViewParametersProvider : PreviewParameterProvider<TimelineItemVoiceViewParameters> {
|
||||
private val voiceMessageStateProvider = VoiceMessageStateProvider()
|
||||
private val timelineItemVoiceContentProvider = TimelineItemVoiceContentProvider()
|
||||
open class TimelineItemVoiceViewParametersPreviewParam : PreviewParameterProvider<TimelineItemVoiceViewParameters> {
|
||||
private val voiceMessageStateProvider = VoiceMessageStatePreviewParam()
|
||||
private val timelineItemVoiceContentProvider = TimelineItemVoiceContentPreviewParam()
|
||||
override val values: Sequence<TimelineItemVoiceViewParameters>
|
||||
get() = timelineItemVoiceContentProvider.values.flatMap { content ->
|
||||
voiceMessageStateProvider.values.map { state ->
|
||||
@@ -283,7 +283,7 @@ data class TimelineItemVoiceViewParameters(
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun TimelineItemVoiceViewPreview(
|
||||
@PreviewParameter(TimelineItemVoiceViewParametersProvider::class) timelineItemVoiceViewParameters: TimelineItemVoiceViewParameters,
|
||||
@PreviewParameter(TimelineItemVoiceViewParametersPreviewParam::class) timelineItemVoiceViewParameters: TimelineItemVoiceViewParameters,
|
||||
) = ElementPreview {
|
||||
TimelineItemVoiceView(
|
||||
state = timelineItemVoiceViewParameters.state,
|
||||
@@ -296,7 +296,7 @@ internal fun TimelineItemVoiceViewPreview(
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun TimelineItemVoiceViewUnifiedPreview() = ElementPreview {
|
||||
val timelineItemVoiceViewParametersProvider = TimelineItemVoiceViewParametersProvider()
|
||||
val timelineItemVoiceViewParametersProvider = TimelineItemVoiceViewParametersPreviewParam()
|
||||
Column {
|
||||
timelineItemVoiceViewParametersProvider.values.forEach {
|
||||
TimelineItemVoiceView(
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.features.messages.impl.timeline.aTimelineItemReactions
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
|
||||
open class ReactionSummaryStateProvider : PreviewParameterProvider<ReactionSummaryState> {
|
||||
open class ReactionSummaryStatePreviewParam : PreviewParameterProvider<ReactionSummaryState> {
|
||||
override val values = sequenceOf(aReactionSummaryState())
|
||||
}
|
||||
|
||||
+1
-1
@@ -293,7 +293,7 @@ private fun SenderRow(
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun ReactionSummaryViewContentPreview(
|
||||
@PreviewParameter(ReactionSummaryStateProvider::class) state: ReactionSummaryState
|
||||
@PreviewParameter(ReactionSummaryStatePreviewParam::class) state: ReactionSummaryState
|
||||
) = ElementPreview {
|
||||
ReactionSummaryViewContent(summary = state.target as ReactionSummaryState.Summary)
|
||||
}
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@ import io.element.android.features.messages.impl.timeline.model.ReadReceiptData
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState
|
||||
|
||||
class ReadReceiptViewStateForTimelineItemEventRowProvider :
|
||||
class ReadReceiptViewStateForTimelineItemEventRowPreviewParam :
|
||||
PreviewParameterProvider<ReadReceiptViewState> {
|
||||
override val values: Sequence<ReadReceiptViewState>
|
||||
get() = sequenceOf(
|
||||
+1
-1
@@ -17,7 +17,7 @@ import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
class ReadReceiptViewStateProvider : PreviewParameterProvider<ReadReceiptViewState> {
|
||||
class ReadReceiptViewStatePreviewParam : PreviewParameterProvider<ReadReceiptViewState> {
|
||||
override val values: Sequence<ReadReceiptViewState>
|
||||
get() = sequenceOf(
|
||||
aReadReceiptViewState(),
|
||||
+1
-1
@@ -202,7 +202,7 @@ private fun computeReceiptDescription(receipts: ImmutableList<ReadReceiptData>):
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun TimelineItemReadReceiptViewPreview(
|
||||
@PreviewParameter(ReadReceiptViewStateProvider::class) state: ReadReceiptViewState,
|
||||
@PreviewParameter(ReadReceiptViewStatePreviewParam::class) state: ReadReceiptViewState,
|
||||
) = ElementPreview {
|
||||
TimelineItemReadReceiptView(
|
||||
state = state,
|
||||
|
||||
+1
-1
@@ -117,7 +117,7 @@ private fun ReadReceiptBottomSheetContent(
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun ReadReceiptBottomSheetPreview(@PreviewParameter(ReadReceiptBottomSheetStateProvider::class) state: ReadReceiptBottomSheetState) {
|
||||
internal fun ReadReceiptBottomSheetPreview(@PreviewParameter(ReadReceiptBottomSheetStatePreviewParam::class) state: ReadReceiptBottomSheetState) {
|
||||
ElementPreview(fillMaxSize = true) {
|
||||
ReadReceiptBottomSheet(
|
||||
state = state,
|
||||
|
||||
+4
-4
@@ -10,14 +10,14 @@ package io.element.android.features.messages.impl.timeline.components.receipt.bo
|
||||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.features.messages.impl.timeline.aTimelineItemEvent
|
||||
import io.element.android.features.messages.impl.timeline.components.receipt.ReadReceiptViewStateProvider
|
||||
import io.element.android.features.messages.impl.timeline.components.receipt.ReadReceiptViewStatePreviewParam
|
||||
import io.element.android.features.messages.impl.timeline.model.TimelineItemReadReceipts
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
class ReadReceiptBottomSheetStateProvider : PreviewParameterProvider<ReadReceiptBottomSheetState> {
|
||||
// Reuse the provider ReadReceiptViewStateProvider
|
||||
private val readReceiptViewStateProvider = ReadReceiptViewStateProvider()
|
||||
class ReadReceiptBottomSheetStatePreviewParam : PreviewParameterProvider<ReadReceiptBottomSheetState> {
|
||||
// Reuse the provider ReadReceiptViewStatePreviewParam
|
||||
private val readReceiptViewStateProvider = ReadReceiptViewStatePreviewParam()
|
||||
override val values: Sequence<ReadReceiptBottomSheetState> = readReceiptViewStateProvider.values
|
||||
.filter { it.sendState is LocalEventSendState.Sent }
|
||||
.map { readReceiptViewState ->
|
||||
+2
-2
@@ -21,7 +21,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemDaySeparatorModel
|
||||
import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemDaySeparatorModelProvider
|
||||
import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemDaySeparatorModelPreviewParam
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
@@ -53,7 +53,7 @@ internal fun TimelineItemDaySeparatorView(
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun TimelineItemDaySeparatorViewPreview(
|
||||
@PreviewParameter(TimelineItemDaySeparatorModelProvider::class) model: TimelineItemDaySeparatorModel
|
||||
@PreviewParameter(TimelineItemDaySeparatorModelPreviewParam::class) model: TimelineItemDaySeparatorModel
|
||||
) = ElementPreview {
|
||||
TimelineItemDaySeparatorView(
|
||||
model = model,
|
||||
|
||||
-15
@@ -10,28 +10,13 @@ package io.element.android.features.messages.impl.timeline.di
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import dev.zacsweers.metro.BindingContainer
|
||||
import dev.zacsweers.metro.ContributesTo
|
||||
import dev.zacsweers.metro.Inject
|
||||
import dev.zacsweers.metro.Multibinds
|
||||
import dev.zacsweers.metro.SingleIn
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.di.RoomScope
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
/**
|
||||
* Container that declares the [TimelineItemPresenterFactory] map multi binding.
|
||||
*
|
||||
* Its sole purpose is to support the case of an empty map multibinding.
|
||||
*/
|
||||
@BindingContainer
|
||||
@ContributesTo(RoomScope::class)
|
||||
interface TimelineItemPresenterFactoriesModule {
|
||||
@Multibinds
|
||||
fun multiBindTimelineItemPresenterFactories(): @JvmSuppressWildcards Map<KClass<out TimelineItemEventContent>, TimelineItemPresenterFactory<*, *>>
|
||||
}
|
||||
|
||||
/**
|
||||
* Room level caching layer for the [TimelineItemPresenterFactory] instances.
|
||||
*
|
||||
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (c) 2026 Element Creations Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.messages.impl.timeline.di
|
||||
|
||||
import dev.zacsweers.metro.BindingContainer
|
||||
import dev.zacsweers.metro.ContributesTo
|
||||
import dev.zacsweers.metro.Multibinds
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent
|
||||
import io.element.android.libraries.di.RoomScope
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
/**
|
||||
* Container that declares the [TimelineItemPresenterFactory] map multi binding.
|
||||
*
|
||||
* Its sole purpose is to support the case of an empty map multibinding.
|
||||
*/
|
||||
@BindingContainer
|
||||
@ContributesTo(RoomScope::class)
|
||||
interface TimelineItemPresenterFactoriesBindingContainer {
|
||||
@Multibinds
|
||||
fun multiBindTimelineItemPresenterFactories(): @JvmSuppressWildcards Map<KClass<out TimelineItemEventContent>, TimelineItemPresenterFactory<*, *>>
|
||||
}
|
||||
+13
-1
@@ -21,6 +21,9 @@ import io.element.android.libraries.androidutils.diff.MutableListDiffCache
|
||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.MessageContent
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.OtherMessageType
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
@@ -110,7 +113,11 @@ class TimelineItemsFactory(
|
||||
): TimelineItem? {
|
||||
val timelineItem =
|
||||
when (val currentTimelineItem = timelineItems[index]) {
|
||||
is MatrixTimelineItem.Event -> eventItemFactory.create(currentTimelineItem, index, timelineItems, roomMembers, renderReadReceipts)
|
||||
is MatrixTimelineItem.Event -> if (currentTimelineItem.event.isKeyVerificationRequest()) {
|
||||
null
|
||||
} else {
|
||||
eventItemFactory.create(currentTimelineItem, index, timelineItems, roomMembers, renderReadReceipts)
|
||||
}
|
||||
is MatrixTimelineItem.Virtual -> virtualItemFactory.create(currentTimelineItem)
|
||||
MatrixTimelineItem.Other -> null
|
||||
}
|
||||
@@ -118,3 +125,8 @@ class TimelineItemsFactory(
|
||||
return timelineItem
|
||||
}
|
||||
}
|
||||
|
||||
private fun EventTimelineItem.isKeyVerificationRequest(): Boolean {
|
||||
val messageType = (content as? MessageContent)?.type
|
||||
return messageType is OtherMessageType && messageType.isKeyVerificationRequest
|
||||
}
|
||||
|
||||
+10
-2
@@ -38,6 +38,8 @@ import io.element.android.libraries.matrix.ui.messages.reply.map
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
private const val GROUPING_TIMEOUT_MS = 5 * 60 * 1000L
|
||||
|
||||
@AssistedInject
|
||||
class TimelineItemEventFactory(
|
||||
@Assisted private val config: TimelineItemsFactoryConfig,
|
||||
@@ -221,8 +223,14 @@ class TimelineItemEventFactory(
|
||||
val previousSender = prevTimelineItem?.event?.sender
|
||||
val nextSender = nextTimelineItem?.event?.sender
|
||||
|
||||
val previousIsGroupable = prevTimelineItem?.canBeDisplayedInBubbleBlock().orTrue()
|
||||
val nextIsGroupable = nextTimelineItem?.canBeDisplayedInBubbleBlock().orTrue()
|
||||
val previousIsGroupable = prevTimelineItem?.let {
|
||||
it.canBeDisplayedInBubbleBlock() &&
|
||||
currentTimelineItem.event.timestamp - it.event.timestamp <= GROUPING_TIMEOUT_MS
|
||||
}.orTrue()
|
||||
val nextIsGroupable = nextTimelineItem?.let {
|
||||
it.canBeDisplayedInBubbleBlock() &&
|
||||
it.event.timestamp - currentTimelineItem.event.timestamp <= GROUPING_TIMEOUT_MS
|
||||
}.orTrue()
|
||||
|
||||
return when {
|
||||
previousSender != currentSender && nextSender == currentSender -> {
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@ import io.element.android.features.messages.impl.timeline.FocusRequestState
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.room.errors.FocusEventException
|
||||
|
||||
open class FocusRequestStateProvider : PreviewParameterProvider<FocusRequestState> {
|
||||
open class FocusRequestStatePreviewParam : PreviewParameterProvider<FocusRequestState> {
|
||||
override val values: Sequence<FocusRequestState>
|
||||
get() = sequenceOf(
|
||||
FocusRequestState.Loading(
|
||||
+1
-1
@@ -55,7 +55,7 @@ fun FocusRequestStateView(
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun FocusRequestStateViewPreview(
|
||||
@PreviewParameter(FocusRequestStateProvider::class) state: FocusRequestState,
|
||||
@PreviewParameter(FocusRequestStatePreviewParam::class) state: FocusRequestState,
|
||||
) = ElementPreview {
|
||||
FocusRequestStateView(
|
||||
focusRequestState = state,
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@ import java.text.DateFormat
|
||||
import java.util.Date
|
||||
import java.util.TimeZone
|
||||
|
||||
open class AggregatedReactionProvider : PreviewParameterProvider<AggregatedReaction> {
|
||||
open class AggregatedReactionPreviewParam : PreviewParameterProvider<AggregatedReaction> {
|
||||
override val values: Sequence<AggregatedReaction>
|
||||
get() = sequenceOf(false, true).flatMap {
|
||||
sequenceOf(
|
||||
+2
-2
@@ -11,6 +11,6 @@ package io.element.android.features.messages.impl.timeline.model
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
fun aTimelineItemReactions() = TimelineItemReactions(
|
||||
// Use values from AggregatedReactionProvider
|
||||
reactions = AggregatedReactionProvider().values.toImmutableList()
|
||||
// Use values from AggregatedReactionPreviewParam
|
||||
reactions = AggregatedReactionPreviewParam().values.toImmutableList()
|
||||
)
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@ import io.element.android.features.messages.impl.timeline.TimelineRoomInfo
|
||||
import io.element.android.features.messages.impl.timeline.aTimelineRoomInfo
|
||||
import io.element.android.features.messages.impl.timeline.model.TimelineItemGroupPosition
|
||||
|
||||
open class BubbleStateProvider : PreviewParameterProvider<BubbleState> {
|
||||
open class BubbleStatePreviewParam : PreviewParameterProvider<BubbleState> {
|
||||
override val values: Sequence<BubbleState>
|
||||
get() = sequenceOf(
|
||||
TimelineItemGroupPosition.First,
|
||||
+1
-1
@@ -13,7 +13,7 @@ import io.element.android.libraries.core.mimetype.MimeTypes
|
||||
import io.element.android.libraries.matrix.api.media.MediaSource
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
|
||||
open class TimelineItemAudioContentProvider : PreviewParameterProvider<TimelineItemAudioContent> {
|
||||
open class TimelineItemAudioContentPreviewParam : PreviewParameterProvider<TimelineItemAudioContent> {
|
||||
override val values: Sequence<TimelineItemAudioContent>
|
||||
get() = sequenceOf(
|
||||
aTimelineItemAudioContent("A sound.mp3"),
|
||||
+1
-1
@@ -12,7 +12,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.UnableToDecryptContent
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.UtdCause
|
||||
|
||||
open class TimelineItemEncryptedContentProvider : PreviewParameterProvider<TimelineItemEncryptedContent> {
|
||||
open class TimelineItemEncryptedContentPreviewParam : PreviewParameterProvider<TimelineItemEncryptedContent> {
|
||||
override val values: Sequence<TimelineItemEncryptedContent>
|
||||
get() = sequenceOf(
|
||||
aTimelineItemEncryptedContent(),
|
||||
+3
-1
@@ -58,10 +58,12 @@ fun TimelineItemEventContent.canBeForwarded(): Boolean =
|
||||
is TimelineItemFileContent,
|
||||
is TimelineItemAudioContent,
|
||||
is TimelineItemVideoContent,
|
||||
is TimelineItemLocationContent,
|
||||
is TimelineItemVoiceContent,
|
||||
is TimelineItemGalleryContent,
|
||||
is TimelineItemAttachmentsContent -> true
|
||||
// Live location shares can't be forwarded, the SDK rejects them, so we only show the option for static locations
|
||||
// See https://github.com/element-hq/element-x-android/issues/7190
|
||||
is TimelineItemLocationContent -> mode is TimelineItemLocationContent.Mode.Static
|
||||
// Stickers can't be forwarded (yet) so we don't show the option
|
||||
// See https://github.com/element-hq/element-x-android/issues/2161
|
||||
is TimelineItemStickerContent -> false
|
||||
|
||||
+2
-2
@@ -18,7 +18,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.UnableToDecry
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import org.jsoup.nodes.Document
|
||||
|
||||
class TimelineItemEventContentProvider : PreviewParameterProvider<TimelineItemEventContent> {
|
||||
class TimelineItemEventContentPreviewParam : PreviewParameterProvider<TimelineItemEventContent> {
|
||||
override val values = sequenceOf(
|
||||
aTimelineItemEmoteContent(),
|
||||
aTimelineItemEncryptedContent(),
|
||||
@@ -45,7 +45,7 @@ class TimelineItemEventContentProvider : PreviewParameterProvider<TimelineItemEv
|
||||
|
||||
const val AN_EMOJI_ONLY_TEXT = "😁"
|
||||
|
||||
class TimelineItemTextBasedContentProvider : PreviewParameterProvider<TimelineItemTextBasedContent> {
|
||||
class TimelineItemTextBasedContentPreviewParam : PreviewParameterProvider<TimelineItemTextBasedContent> {
|
||||
private fun buildSpanned(text: String) = buildSpannedString {
|
||||
inSpans(StyleSpan(Typeface.BOLD)) {
|
||||
append("Rich Text")
|
||||
+1
-1
@@ -12,7 +12,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.libraries.core.mimetype.MimeTypes
|
||||
import io.element.android.libraries.matrix.api.media.MediaSource
|
||||
|
||||
open class TimelineItemFileContentProvider : PreviewParameterProvider<TimelineItemFileContent> {
|
||||
open class TimelineItemFileContentPreviewParam : PreviewParameterProvider<TimelineItemFileContent> {
|
||||
override val values: Sequence<TimelineItemFileContent>
|
||||
get() = sequenceOf(
|
||||
aTimelineItemFileContent(),
|
||||
+1
-1
@@ -13,7 +13,7 @@ import androidx.media3.common.MimeTypes
|
||||
import io.element.android.libraries.matrix.api.media.MediaSource
|
||||
import io.element.android.libraries.matrix.ui.components.A_BLUR_HASH
|
||||
|
||||
open class TimelineItemImageContentProvider : PreviewParameterProvider<TimelineItemImageContent> {
|
||||
open class TimelineItemImageContentPreviewParam : PreviewParameterProvider<TimelineItemImageContent> {
|
||||
override val values: Sequence<TimelineItemImageContent>
|
||||
get() = sequenceOf(
|
||||
aTimelineItemImageContent(),
|
||||
+1
-1
@@ -14,7 +14,7 @@ import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.ProfileDetails
|
||||
import io.element.android.libraries.matrix.ui.messages.reply.aProfileDetailsReady
|
||||
|
||||
open class TimelineItemLocationContentProvider : PreviewParameterProvider<TimelineItemLocationContent> {
|
||||
open class TimelineItemLocationContentPreviewParam : PreviewParameterProvider<TimelineItemLocationContent> {
|
||||
override val values: Sequence<TimelineItemLocationContent>
|
||||
get() = sequenceOf(
|
||||
aTimelineItemLocationContent(
|
||||
+1
-1
@@ -15,7 +15,7 @@ import io.element.android.features.poll.api.pollcontent.aPollQuestion
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.poll.PollKind
|
||||
|
||||
open class TimelineItemPollContentProvider : PreviewParameterProvider<TimelineItemPollContent> {
|
||||
open class TimelineItemPollContentPreviewParam : PreviewParameterProvider<TimelineItemPollContent> {
|
||||
override val values: Sequence<TimelineItemPollContent>
|
||||
get() = sequenceOf(
|
||||
aTimelineItemPollContent(),
|
||||
+1
-1
@@ -13,7 +13,7 @@ import androidx.media3.common.MimeTypes
|
||||
import io.element.android.libraries.matrix.api.media.MediaSource
|
||||
import io.element.android.libraries.matrix.ui.components.A_BLUR_HASH
|
||||
|
||||
open class TimelineItemStickerContentProvider : PreviewParameterProvider<TimelineItemStickerContent> {
|
||||
open class TimelineItemStickerContentPreviewParam : PreviewParameterProvider<TimelineItemStickerContent> {
|
||||
override val values: Sequence<TimelineItemStickerContent>
|
||||
get() = sequenceOf(
|
||||
aTimelineItemStickerContent(),
|
||||
+1
-1
@@ -14,7 +14,7 @@ import io.element.android.libraries.matrix.api.media.MediaSource
|
||||
import io.element.android.libraries.matrix.ui.components.A_BLUR_HASH
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
|
||||
open class TimelineItemVideoContentProvider : PreviewParameterProvider<TimelineItemVideoContent> {
|
||||
open class TimelineItemVideoContentPreviewParam : PreviewParameterProvider<TimelineItemVideoContent> {
|
||||
override val values: Sequence<TimelineItemVideoContent>
|
||||
get() = sequenceOf(
|
||||
aTimelineItemVideoContent(),
|
||||
+1
-1
@@ -17,7 +17,7 @@ import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
import kotlin.time.Duration.Companion.minutes
|
||||
|
||||
open class TimelineItemVoiceContentProvider : PreviewParameterProvider<TimelineItemVoiceContent> {
|
||||
open class TimelineItemVoiceContentPreviewParam : PreviewParameterProvider<TimelineItemVoiceContent> {
|
||||
override val values: Sequence<TimelineItemVoiceContent>
|
||||
get() = sequenceOf(
|
||||
aTimelineItemVoiceContent(
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user