timeline : makes typingNotification item part of the timelineItems.
This commit is contained in:
parent
60f1bf6e54
commit
3a1c20a0fe
21 changed files with 95 additions and 68 deletions
|
|
@ -46,7 +46,6 @@ 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.model.event.TimelineItemPollContent
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStateContent
|
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStateContent
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextBasedContent
|
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextBasedContent
|
||||||
import io.element.android.features.messages.impl.typing.TypingNotificationPresenter
|
|
||||||
import io.element.android.features.messages.impl.voicemessages.composer.VoiceMessageComposerPresenter
|
import io.element.android.features.messages.impl.voicemessages.composer.VoiceMessageComposerPresenter
|
||||||
import io.element.android.features.networkmonitor.api.NetworkMonitor
|
import io.element.android.features.networkmonitor.api.NetworkMonitor
|
||||||
import io.element.android.features.networkmonitor.api.NetworkStatus
|
import io.element.android.features.networkmonitor.api.NetworkStatus
|
||||||
|
|
@ -91,7 +90,6 @@ class MessagesPresenter @AssistedInject constructor(
|
||||||
private val composerPresenter: MessageComposerPresenter,
|
private val composerPresenter: MessageComposerPresenter,
|
||||||
private val voiceMessageComposerPresenter: VoiceMessageComposerPresenter,
|
private val voiceMessageComposerPresenter: VoiceMessageComposerPresenter,
|
||||||
timelinePresenterFactory: TimelinePresenter.Factory,
|
timelinePresenterFactory: TimelinePresenter.Factory,
|
||||||
private val typingNotificationPresenter: TypingNotificationPresenter,
|
|
||||||
private val actionListPresenterFactory: ActionListPresenter.Factory,
|
private val actionListPresenterFactory: ActionListPresenter.Factory,
|
||||||
private val customReactionPresenter: CustomReactionPresenter,
|
private val customReactionPresenter: CustomReactionPresenter,
|
||||||
private val reactionSummaryPresenter: ReactionSummaryPresenter,
|
private val reactionSummaryPresenter: ReactionSummaryPresenter,
|
||||||
|
|
@ -125,7 +123,6 @@ class MessagesPresenter @AssistedInject constructor(
|
||||||
val composerState = composerPresenter.present()
|
val composerState = composerPresenter.present()
|
||||||
val voiceMessageComposerState = voiceMessageComposerPresenter.present()
|
val voiceMessageComposerState = voiceMessageComposerPresenter.present()
|
||||||
val timelineState = timelinePresenter.present()
|
val timelineState = timelinePresenter.present()
|
||||||
val typingNotificationState = typingNotificationPresenter.present()
|
|
||||||
val actionListState = actionListPresenter.present()
|
val actionListState = actionListPresenter.present()
|
||||||
val customReactionState = customReactionPresenter.present()
|
val customReactionState = customReactionPresenter.present()
|
||||||
val reactionSummaryState = reactionSummaryPresenter.present()
|
val reactionSummaryState = reactionSummaryPresenter.present()
|
||||||
|
|
@ -216,7 +213,6 @@ class MessagesPresenter @AssistedInject constructor(
|
||||||
userEventPermissions = userEventPermissions,
|
userEventPermissions = userEventPermissions,
|
||||||
voiceMessageComposerState = voiceMessageComposerState,
|
voiceMessageComposerState = voiceMessageComposerState,
|
||||||
timelineState = timelineState,
|
timelineState = timelineState,
|
||||||
typingNotificationState = typingNotificationState,
|
|
||||||
actionListState = actionListState,
|
actionListState = actionListState,
|
||||||
customReactionState = customReactionState,
|
customReactionState = customReactionState,
|
||||||
reactionSummaryState = reactionSummaryState,
|
reactionSummaryState = reactionSummaryState,
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,6 @@ import io.element.android.features.messages.impl.timeline.TimelineState
|
||||||
import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionState
|
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.components.reactionsummary.ReactionSummaryState
|
||||||
import io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet.ReadReceiptBottomSheetState
|
import io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet.ReadReceiptBottomSheetState
|
||||||
import io.element.android.features.messages.impl.typing.TypingNotificationState
|
|
||||||
import io.element.android.features.messages.impl.voicemessages.composer.VoiceMessageComposerState
|
import io.element.android.features.messages.impl.voicemessages.composer.VoiceMessageComposerState
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||||
|
|
@ -33,7 +32,6 @@ data class MessagesState(
|
||||||
val composerState: MessageComposerState,
|
val composerState: MessageComposerState,
|
||||||
val voiceMessageComposerState: VoiceMessageComposerState,
|
val voiceMessageComposerState: VoiceMessageComposerState,
|
||||||
val timelineState: TimelineState,
|
val timelineState: TimelineState,
|
||||||
val typingNotificationState: TypingNotificationState,
|
|
||||||
val actionListState: ActionListState,
|
val actionListState: ActionListState,
|
||||||
val customReactionState: CustomReactionState,
|
val customReactionState: CustomReactionState,
|
||||||
val reactionSummaryState: ReactionSummaryState,
|
val reactionSummaryState: ReactionSummaryState,
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,6 @@ import io.element.android.features.messages.impl.timeline.components.receipt.bot
|
||||||
import io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet.ReadReceiptBottomSheetState
|
import io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet.ReadReceiptBottomSheetState
|
||||||
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemTextContent
|
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemTextContent
|
||||||
import io.element.android.features.messages.impl.typing.aTypingNotificationState
|
|
||||||
import io.element.android.features.messages.impl.voicemessages.composer.VoiceMessageComposerState
|
import io.element.android.features.messages.impl.voicemessages.composer.VoiceMessageComposerState
|
||||||
import io.element.android.features.messages.impl.voicemessages.composer.aVoiceMessageComposerState
|
import io.element.android.features.messages.impl.voicemessages.composer.aVoiceMessageComposerState
|
||||||
import io.element.android.features.messages.impl.voicemessages.composer.aVoiceMessagePreviewState
|
import io.element.android.features.messages.impl.voicemessages.composer.aVoiceMessagePreviewState
|
||||||
|
|
@ -122,7 +121,6 @@ fun aMessagesState(
|
||||||
userEventPermissions = userEventPermissions,
|
userEventPermissions = userEventPermissions,
|
||||||
composerState = composerState,
|
composerState = composerState,
|
||||||
voiceMessageComposerState = voiceMessageComposerState,
|
voiceMessageComposerState = voiceMessageComposerState,
|
||||||
typingNotificationState = aTypingNotificationState(),
|
|
||||||
timelineState = timelineState,
|
timelineState = timelineState,
|
||||||
readReceiptBottomSheetState = readReceiptBottomSheetState,
|
readReceiptBottomSheetState = readReceiptBottomSheetState,
|
||||||
actionListState = actionListState,
|
actionListState = actionListState,
|
||||||
|
|
|
||||||
|
|
@ -379,7 +379,6 @@ private fun MessagesViewContent(
|
||||||
val scrollBehavior = PinnedMessagesBannerViewDefaults.rememberExitOnScrollBehavior()
|
val scrollBehavior = PinnedMessagesBannerViewDefaults.rememberExitOnScrollBehavior()
|
||||||
TimelineView(
|
TimelineView(
|
||||||
state = state.timelineState,
|
state = state.timelineState,
|
||||||
typingNotificationState = state.typingNotificationState,
|
|
||||||
onUserDataClick = onUserDataClick,
|
onUserDataClick = onUserDataClick,
|
||||||
onLinkClick = onLinkClick,
|
onLinkClick = onLinkClick,
|
||||||
onMessageClick = onMessageClick,
|
onMessageClick = onMessageClick,
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,8 @@ import io.element.android.features.messages.impl.crypto.sendfailure.resolve.Reso
|
||||||
import io.element.android.features.messages.impl.crypto.sendfailure.resolve.ResolveVerifiedUserSendFailureState
|
import io.element.android.features.messages.impl.crypto.sendfailure.resolve.ResolveVerifiedUserSendFailureState
|
||||||
import io.element.android.features.messages.impl.pinned.banner.PinnedMessagesBannerPresenter
|
import io.element.android.features.messages.impl.pinned.banner.PinnedMessagesBannerPresenter
|
||||||
import io.element.android.features.messages.impl.pinned.banner.PinnedMessagesBannerState
|
import io.element.android.features.messages.impl.pinned.banner.PinnedMessagesBannerState
|
||||||
|
import io.element.android.features.messages.impl.typing.TypingNotificationPresenter
|
||||||
|
import io.element.android.features.messages.impl.typing.TypingNotificationState
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.libraries.di.RoomScope
|
import io.element.android.libraries.di.RoomScope
|
||||||
|
|
||||||
|
|
@ -25,4 +27,8 @@ interface MessagesModule {
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
fun bindResolveVerifiedUserSendFailurePresenter(presenter: ResolveVerifiedUserSendFailurePresenter): Presenter<ResolveVerifiedUserSendFailureState>
|
fun bindResolveVerifiedUserSendFailurePresenter(presenter: ResolveVerifiedUserSendFailurePresenter): Presenter<ResolveVerifiedUserSendFailureState>
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
fun bindTypingNotificationPresenter(presenter: TypingNotificationPresenter): Presenter<TypingNotificationState>
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ import io.element.android.features.messages.impl.timeline.TimelineRoomInfo
|
||||||
import io.element.android.features.messages.impl.timeline.factories.TimelineItemsFactory
|
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.factories.TimelineItemsFactoryConfig
|
||||||
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
||||||
|
import io.element.android.features.messages.impl.typing.TypingNotificationState
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
|
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
|
||||||
|
|
@ -44,6 +45,7 @@ import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
import io.element.android.services.analytics.api.AnalyticsService
|
import io.element.android.services.analytics.api.AnalyticsService
|
||||||
import io.element.android.services.analyticsproviders.api.trackers.captureInteraction
|
import io.element.android.services.analyticsproviders.api.trackers.captureInteraction
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.flowOf
|
import kotlinx.coroutines.flow.flowOf
|
||||||
|
|
@ -87,7 +89,12 @@ class PinnedMessagesListPresenter @AssistedInject constructor(
|
||||||
userHasPermissionToSendReaction = false,
|
userHasPermissionToSendReaction = false,
|
||||||
isCallOngoing = false,
|
isCallOngoing = false,
|
||||||
// don't compute this value or the pin icon will be shown
|
// don't compute this value or the pin icon will be shown
|
||||||
pinnedEventIds = emptyList()
|
pinnedEventIds = emptyList(),
|
||||||
|
typingNotificationState = TypingNotificationState(
|
||||||
|
renderTypingNotifications = false,
|
||||||
|
typingMembers = persistentListOf(),
|
||||||
|
reserveSpace = false,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ import io.element.android.features.messages.impl.timeline.factories.TimelineItem
|
||||||
import io.element.android.features.messages.impl.timeline.factories.TimelineItemsFactoryConfig
|
import io.element.android.features.messages.impl.timeline.factories.TimelineItemsFactoryConfig
|
||||||
import io.element.android.features.messages.impl.timeline.model.NewEventState
|
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.TimelineItem
|
||||||
|
import io.element.android.features.messages.impl.typing.TypingNotificationState
|
||||||
import io.element.android.features.messages.impl.voicemessages.timeline.RedactedVoiceMessageManager
|
import io.element.android.features.messages.impl.voicemessages.timeline.RedactedVoiceMessageManager
|
||||||
import io.element.android.features.poll.api.actions.EndPollAction
|
import io.element.android.features.poll.api.actions.EndPollAction
|
||||||
import io.element.android.features.poll.api.actions.SendPollResponseAction
|
import io.element.android.features.poll.api.actions.SendPollResponseAction
|
||||||
|
|
@ -70,6 +71,7 @@ class TimelinePresenter @AssistedInject constructor(
|
||||||
private val sessionPreferencesStore: SessionPreferencesStore,
|
private val sessionPreferencesStore: SessionPreferencesStore,
|
||||||
private val timelineController: TimelineController,
|
private val timelineController: TimelineController,
|
||||||
private val resolveVerifiedUserSendFailurePresenter: Presenter<ResolveVerifiedUserSendFailureState>,
|
private val resolveVerifiedUserSendFailurePresenter: Presenter<ResolveVerifiedUserSendFailureState>,
|
||||||
|
private val typingNotificationPresenter: Presenter<TypingNotificationState>,
|
||||||
) : Presenter<TimelineState> {
|
) : Presenter<TimelineState> {
|
||||||
@AssistedFactory
|
@AssistedFactory
|
||||||
interface Factory {
|
interface Factory {
|
||||||
|
|
@ -225,7 +227,8 @@ class TimelinePresenter @AssistedInject constructor(
|
||||||
.launchIn(this)
|
.launchIn(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
val timelineRoomInfo by remember {
|
val typingNotificationState = typingNotificationPresenter.present()
|
||||||
|
val timelineRoomInfo by remember(typingNotificationState) {
|
||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
TimelineRoomInfo(
|
TimelineRoomInfo(
|
||||||
name = room.displayName,
|
name = room.displayName,
|
||||||
|
|
@ -234,6 +237,7 @@ class TimelinePresenter @AssistedInject constructor(
|
||||||
userHasPermissionToSendReaction = userHasPermissionToSendReaction,
|
userHasPermissionToSendReaction = userHasPermissionToSendReaction,
|
||||||
isCallOngoing = roomInfo?.hasRoomCall.orFalse(),
|
isCallOngoing = roomInfo?.hasRoomCall.orFalse(),
|
||||||
pinnedEventIds = roomInfo?.pinnedEventIds.orEmpty(),
|
pinnedEventIds = roomInfo?.pinnedEventIds.orEmpty(),
|
||||||
|
typingNotificationState = typingNotificationState,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import androidx.compose.runtime.Immutable
|
||||||
import io.element.android.features.messages.impl.crypto.sendfailure.resolve.ResolveVerifiedUserSendFailureState
|
import io.element.android.features.messages.impl.crypto.sendfailure.resolve.ResolveVerifiedUserSendFailureState
|
||||||
import io.element.android.features.messages.impl.timeline.model.NewEventState
|
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.TimelineItem
|
||||||
|
import io.element.android.features.messages.impl.typing.TypingNotificationState
|
||||||
import io.element.android.libraries.matrix.api.core.EventId
|
import io.element.android.libraries.matrix.api.core.EventId
|
||||||
import io.element.android.libraries.matrix.api.timeline.item.event.MessageShield
|
import io.element.android.libraries.matrix.api.timeline.item.event.MessageShield
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
|
@ -67,5 +68,6 @@ data class TimelineRoomInfo(
|
||||||
val userHasPermissionToSendMessage: Boolean,
|
val userHasPermissionToSendMessage: Boolean,
|
||||||
val userHasPermissionToSendReaction: Boolean,
|
val userHasPermissionToSendReaction: Boolean,
|
||||||
val isCallOngoing: Boolean,
|
val isCallOngoing: Boolean,
|
||||||
val pinnedEventIds: List<EventId>
|
val pinnedEventIds: List<EventId>,
|
||||||
|
val typingNotificationState: TypingNotificationState,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,8 @@ 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.aTimelineItemStateEventContent
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemTextContent
|
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.model.virtual.aTimelineItemDaySeparatorModel
|
||||||
|
import io.element.android.features.messages.impl.typing.TypingNotificationState
|
||||||
|
import io.element.android.features.messages.impl.typing.aTypingNotificationState
|
||||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||||
import io.element.android.libraries.matrix.api.core.EventId
|
import io.element.android.libraries.matrix.api.core.EventId
|
||||||
|
|
@ -241,6 +243,7 @@ internal fun aTimelineRoomInfo(
|
||||||
isDm: Boolean = false,
|
isDm: Boolean = false,
|
||||||
userHasPermissionToSendMessage: Boolean = true,
|
userHasPermissionToSendMessage: Boolean = true,
|
||||||
pinnedEventIds: List<EventId> = emptyList(),
|
pinnedEventIds: List<EventId> = emptyList(),
|
||||||
|
typingNotificationState: TypingNotificationState = aTypingNotificationState(),
|
||||||
) = TimelineRoomInfo(
|
) = TimelineRoomInfo(
|
||||||
isDm = isDm,
|
isDm = isDm,
|
||||||
name = name,
|
name = name,
|
||||||
|
|
@ -248,4 +251,5 @@ internal fun aTimelineRoomInfo(
|
||||||
userHasPermissionToSendReaction = true,
|
userHasPermissionToSendReaction = true,
|
||||||
isCallOngoing = false,
|
isCallOngoing = false,
|
||||||
pinnedEventIds = pinnedEventIds,
|
pinnedEventIds = pinnedEventIds,
|
||||||
|
typingNotificationState = typingNotificationState,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -55,9 +55,6 @@ 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.TimelineItem
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent
|
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.TimelineItemEventContentProvider
|
||||||
import io.element.android.features.messages.impl.typing.TypingNotificationState
|
|
||||||
import io.element.android.features.messages.impl.typing.TypingNotificationView
|
|
||||||
import io.element.android.features.messages.impl.typing.aTypingNotificationState
|
|
||||||
import io.element.android.libraries.designsystem.components.dialogs.AlertDialog
|
import io.element.android.libraries.designsystem.components.dialogs.AlertDialog
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
|
|
@ -73,7 +70,6 @@ import kotlin.math.abs
|
||||||
@Composable
|
@Composable
|
||||||
fun TimelineView(
|
fun TimelineView(
|
||||||
state: TimelineState,
|
state: TimelineState,
|
||||||
typingNotificationState: TypingNotificationState,
|
|
||||||
onUserDataClick: (UserId) -> Unit,
|
onUserDataClick: (UserId) -> Unit,
|
||||||
onLinkClick: (String) -> Unit,
|
onLinkClick: (String) -> Unit,
|
||||||
onMessageClick: (TimelineItem.Event) -> Unit,
|
onMessageClick: (TimelineItem.Event) -> Unit,
|
||||||
|
|
@ -131,11 +127,6 @@ fun TimelineView(
|
||||||
reverseLayout = useReverseLayout,
|
reverseLayout = useReverseLayout,
|
||||||
contentPadding = PaddingValues(vertical = 8.dp),
|
contentPadding = PaddingValues(vertical = 8.dp),
|
||||||
) {
|
) {
|
||||||
if (state.isLive) {
|
|
||||||
item {
|
|
||||||
TypingNotificationView(state = typingNotificationState)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
items(
|
items(
|
||||||
items = state.timelineItems,
|
items = state.timelineItems,
|
||||||
contentType = { timelineItem -> timelineItem.contentType() },
|
contentType = { timelineItem -> timelineItem.contentType() },
|
||||||
|
|
@ -323,7 +314,6 @@ internal fun TimelineViewPreview(
|
||||||
),
|
),
|
||||||
focusedEventIndex = 0,
|
focusedEventIndex = 0,
|
||||||
),
|
),
|
||||||
typingNotificationState = aTypingNotificationState(),
|
|
||||||
onUserDataClick = {},
|
onUserDataClick = {},
|
||||||
onLinkClick = {},
|
onLinkClick = {},
|
||||||
onMessageClick = {},
|
onMessageClick = {},
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,6 @@ internal fun TimelineViewMessageShieldPreview() = ElementPreview {
|
||||||
timelineItems = items.toImmutableList(),
|
timelineItems = items.toImmutableList(),
|
||||||
messageShield = messageShield,
|
messageShield = messageShield,
|
||||||
),
|
),
|
||||||
typingNotificationState = aTypingNotificationState(),
|
|
||||||
onUserDataClick = {},
|
onUserDataClick = {},
|
||||||
onLinkClick = {},
|
onLinkClick = {},
|
||||||
onMessageClick = {},
|
onMessageClick = {},
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,8 @@ import io.element.android.features.messages.impl.timeline.model.virtual.Timeline
|
||||||
import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemLoadingIndicatorModel
|
import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemLoadingIndicatorModel
|
||||||
import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemReadMarkerModel
|
import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemReadMarkerModel
|
||||||
import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemRoomBeginningModel
|
import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemRoomBeginningModel
|
||||||
|
import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemTypingNotificationModel
|
||||||
|
import io.element.android.features.messages.impl.typing.TypingNotificationView
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun TimelineItemVirtualRow(
|
fun TimelineItemVirtualRow(
|
||||||
|
|
@ -46,9 +48,15 @@ fun TimelineItemVirtualRow(
|
||||||
latestEventSink(TimelineEvents.LoadMore(virtual.model.direction))
|
latestEventSink(TimelineEvents.LoadMore(virtual.model.direction))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Empty model trick to avoid timeline jumping during forward pagination.
|
||||||
is TimelineItemLastForwardIndicatorModel -> {
|
is TimelineItemLastForwardIndicatorModel -> {
|
||||||
Spacer(modifier = Modifier)
|
Spacer(modifier = Modifier)
|
||||||
}
|
}
|
||||||
|
is TimelineItemTypingNotificationModel -> {
|
||||||
|
TypingNotificationView(
|
||||||
|
state = timelineRoomInfo.typingNotificationState,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import io.element.android.features.messages.impl.timeline.model.virtual.Timeline
|
||||||
import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemLoadingIndicatorModel
|
import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemLoadingIndicatorModel
|
||||||
import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemReadMarkerModel
|
import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemReadMarkerModel
|
||||||
import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemRoomBeginningModel
|
import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemRoomBeginningModel
|
||||||
|
import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemTypingNotificationModel
|
||||||
import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemVirtualModel
|
import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemVirtualModel
|
||||||
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
|
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
|
||||||
import io.element.android.libraries.matrix.api.timeline.item.virtual.VirtualTimelineItem
|
import io.element.android.libraries.matrix.api.timeline.item.virtual.VirtualTimelineItem
|
||||||
|
|
@ -39,6 +40,7 @@ class TimelineItemVirtualFactory @Inject constructor(
|
||||||
timestamp = inner.timestamp
|
timestamp = inner.timestamp
|
||||||
)
|
)
|
||||||
is VirtualTimelineItem.LastForwardIndicator -> TimelineItemLastForwardIndicatorModel
|
is VirtualTimelineItem.LastForwardIndicator -> TimelineItemLastForwardIndicatorModel
|
||||||
|
VirtualTimelineItem.TypingNotification -> TimelineItemTypingNotificationModel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
* Please see LICENSE in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.messages.impl.timeline.model.virtual
|
||||||
|
|
||||||
|
data object TimelineItemTypingNotificationModel : TimelineItemVirtualModel {
|
||||||
|
override val type: String = "TimelineItemTypingNotificationModel"
|
||||||
|
}
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2024 New Vector Ltd.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
* Please see LICENSE in the repository root for full details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.element.android.features.messages.impl.typing
|
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
|
||||||
import io.element.android.features.messages.impl.MessagesView
|
|
||||||
import io.element.android.features.messages.impl.aMessagesState
|
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
|
||||||
|
|
||||||
@PreviewsDayNight
|
|
||||||
@Composable
|
|
||||||
internal fun MessagesViewWithTypingPreview(
|
|
||||||
@PreviewParameter(TypingNotificationStateForMessagesProvider::class) typingState: TypingNotificationState
|
|
||||||
) = ElementPreview {
|
|
||||||
MessagesView(
|
|
||||||
state = aMessagesState().copy(typingNotificationState = typingState),
|
|
||||||
onBackClick = {},
|
|
||||||
onRoomDetailsClick = {},
|
|
||||||
onEventClick = { false },
|
|
||||||
onUserDataClick = {},
|
|
||||||
onLinkClick = {},
|
|
||||||
onPreviewAttachments = {},
|
|
||||||
onSendLocationClick = {},
|
|
||||||
onCreatePollClick = {},
|
|
||||||
onJoinCallClick = {},
|
|
||||||
onViewAllPinnedMessagesClick = {},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
@ -40,7 +40,6 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVideoContent
|
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVideoContent
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemPollContent
|
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemPollContent
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemTextContent
|
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemTextContent
|
||||||
import io.element.android.features.messages.impl.typing.TypingNotificationPresenter
|
|
||||||
import io.element.android.features.messages.impl.utils.FakeTextPillificationHelper
|
import io.element.android.features.messages.impl.utils.FakeTextPillificationHelper
|
||||||
import io.element.android.features.messages.impl.voicemessages.composer.VoiceMessageComposerPlayer
|
import io.element.android.features.messages.impl.voicemessages.composer.VoiceMessageComposerPlayer
|
||||||
import io.element.android.features.messages.impl.voicemessages.composer.VoiceMessageComposerPresenter
|
import io.element.android.features.messages.impl.voicemessages.composer.VoiceMessageComposerPresenter
|
||||||
|
|
@ -1055,11 +1054,6 @@ class MessagesPresenterTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val featureFlagService = FakeFeatureFlagService()
|
val featureFlagService = FakeFeatureFlagService()
|
||||||
val typingNotificationPresenter = TypingNotificationPresenter(
|
|
||||||
room = matrixRoom,
|
|
||||||
sessionPreferencesStore = sessionPreferencesStore,
|
|
||||||
)
|
|
||||||
|
|
||||||
val readReceiptBottomSheetPresenter = ReadReceiptBottomSheetPresenter()
|
val readReceiptBottomSheetPresenter = ReadReceiptBottomSheetPresenter()
|
||||||
val customReactionPresenter = CustomReactionPresenter(emojibaseProvider = FakeEmojibaseProvider())
|
val customReactionPresenter = CustomReactionPresenter(emojibaseProvider = FakeEmojibaseProvider())
|
||||||
val reactionSummaryPresenter = ReactionSummaryPresenter(room = matrixRoom)
|
val reactionSummaryPresenter = ReactionSummaryPresenter(room = matrixRoom)
|
||||||
|
|
@ -1069,7 +1063,6 @@ class MessagesPresenterTest {
|
||||||
composerPresenter = messageComposerPresenter,
|
composerPresenter = messageComposerPresenter,
|
||||||
voiceMessageComposerPresenter = voiceMessageComposerPresenter,
|
voiceMessageComposerPresenter = voiceMessageComposerPresenter,
|
||||||
timelinePresenterFactory = timelinePresenterFactory,
|
timelinePresenterFactory = timelinePresenterFactory,
|
||||||
typingNotificationPresenter = typingNotificationPresenter,
|
|
||||||
actionListPresenterFactory = FakeActionListPresenter.Factory,
|
actionListPresenterFactory = FakeActionListPresenter.Factory,
|
||||||
customReactionPresenter = customReactionPresenter,
|
customReactionPresenter = customReactionPresenter,
|
||||||
reactionSummaryPresenter = reactionSummaryPresenter,
|
reactionSummaryPresenter = reactionSummaryPresenter,
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ import io.element.android.features.messages.impl.fixtures.aTimelineItemsFactoryC
|
||||||
import io.element.android.features.messages.impl.timeline.components.aCriticalShield
|
import io.element.android.features.messages.impl.timeline.components.aCriticalShield
|
||||||
import io.element.android.features.messages.impl.timeline.model.NewEventState
|
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.TimelineItem
|
||||||
|
import io.element.android.features.messages.impl.typing.aTypingNotificationState
|
||||||
import io.element.android.features.messages.impl.voicemessages.timeline.FakeRedactedVoiceMessageManager
|
import io.element.android.features.messages.impl.voicemessages.timeline.FakeRedactedVoiceMessageManager
|
||||||
import io.element.android.features.messages.impl.voicemessages.timeline.RedactedVoiceMessageManager
|
import io.element.android.features.messages.impl.voicemessages.timeline.RedactedVoiceMessageManager
|
||||||
import io.element.android.features.messages.impl.voicemessages.timeline.aRedactedMatrixTimeline
|
import io.element.android.features.messages.impl.voicemessages.timeline.aRedactedMatrixTimeline
|
||||||
|
|
@ -503,7 +504,7 @@ import kotlin.time.Duration.Companion.seconds
|
||||||
assertThat(state.timelineItems).isNotEmpty()
|
assertThat(state.timelineItems).isNotEmpty()
|
||||||
}
|
}
|
||||||
initialState.eventSink.invoke(TimelineEvents.JumpToLive)
|
initialState.eventSink.invoke(TimelineEvents.JumpToLive)
|
||||||
skipItems(1)
|
skipItems(2)
|
||||||
awaitItem().also { state ->
|
awaitItem().also { state ->
|
||||||
// Event stays focused
|
// Event stays focused
|
||||||
assertThat(state.focusedEventId).isEqualTo(AN_EVENT_ID)
|
assertThat(state.focusedEventId).isEqualTo(AN_EVENT_ID)
|
||||||
|
|
@ -670,7 +671,7 @@ import kotlin.time.Duration.Companion.seconds
|
||||||
timelineItemIndexer: TimelineItemIndexer = TimelineItemIndexer(),
|
timelineItemIndexer: TimelineItemIndexer = TimelineItemIndexer(),
|
||||||
): TimelinePresenter {
|
): TimelinePresenter {
|
||||||
return TimelinePresenter(
|
return TimelinePresenter(
|
||||||
timelineItemsFactoryCreator = aTimelineItemsFactoryCreator(),
|
timelineItemsFactoryCreator = aTimelineItemsFactoryCreator(timelineItemIndexer),
|
||||||
room = room,
|
room = room,
|
||||||
dispatchers = testCoroutineDispatchers(),
|
dispatchers = testCoroutineDispatchers(),
|
||||||
appScope = this,
|
appScope = this,
|
||||||
|
|
@ -682,6 +683,7 @@ import kotlin.time.Duration.Companion.seconds
|
||||||
timelineItemIndexer = timelineItemIndexer,
|
timelineItemIndexer = timelineItemIndexer,
|
||||||
timelineController = TimelineController(room),
|
timelineController = TimelineController(room),
|
||||||
resolveVerifiedUserSendFailurePresenter = { aResolveVerifiedUserSendFailureState() },
|
resolveVerifiedUserSendFailurePresenter = { aResolveVerifiedUserSendFailureState() },
|
||||||
|
typingNotificationPresenter = { aTypingNotificationState() },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -155,7 +155,6 @@ private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setTimel
|
||||||
setSafeContent {
|
setSafeContent {
|
||||||
TimelineView(
|
TimelineView(
|
||||||
state = state,
|
state = state,
|
||||||
typingNotificationState = typingNotificationState,
|
|
||||||
onUserDataClick = onUserDataClick,
|
onUserDataClick = onUserDataClick,
|
||||||
onLinkClick = onLinkClick,
|
onLinkClick = onLinkClick,
|
||||||
onMessageClick = onMessageClick,
|
onMessageClick = onMessageClick,
|
||||||
|
|
|
||||||
|
|
@ -24,4 +24,7 @@ sealed interface VirtualTimelineItem {
|
||||||
val direction: Timeline.PaginationDirection,
|
val direction: Timeline.PaginationDirection,
|
||||||
val timestamp: Long,
|
val timestamp: Long,
|
||||||
) : VirtualTimelineItem
|
) : VirtualTimelineItem
|
||||||
|
|
||||||
|
data object TypingNotification : VirtualTimelineItem
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@ import io.element.android.libraries.matrix.impl.timeline.item.virtual.VirtualTim
|
||||||
import io.element.android.libraries.matrix.impl.timeline.postprocessor.LastForwardIndicatorsPostProcessor
|
import io.element.android.libraries.matrix.impl.timeline.postprocessor.LastForwardIndicatorsPostProcessor
|
||||||
import io.element.android.libraries.matrix.impl.timeline.postprocessor.LoadingIndicatorsPostProcessor
|
import io.element.android.libraries.matrix.impl.timeline.postprocessor.LoadingIndicatorsPostProcessor
|
||||||
import io.element.android.libraries.matrix.impl.timeline.postprocessor.RoomBeginningPostProcessor
|
import io.element.android.libraries.matrix.impl.timeline.postprocessor.RoomBeginningPostProcessor
|
||||||
|
import io.element.android.libraries.matrix.impl.timeline.postprocessor.TypingNotificationPostProcessor
|
||||||
import io.element.android.libraries.matrix.impl.timeline.reply.InReplyToMapper
|
import io.element.android.libraries.matrix.impl.timeline.reply.InReplyToMapper
|
||||||
import io.element.android.libraries.matrix.impl.util.MessageEventContent
|
import io.element.android.libraries.matrix.impl.util.MessageEventContent
|
||||||
import io.element.android.services.toolbox.api.systemclock.SystemClock
|
import io.element.android.services.toolbox.api.systemclock.SystemClock
|
||||||
|
|
@ -121,6 +122,7 @@ class RustTimeline(
|
||||||
private val roomBeginningPostProcessor = RoomBeginningPostProcessor(mode)
|
private val roomBeginningPostProcessor = RoomBeginningPostProcessor(mode)
|
||||||
private val loadingIndicatorsPostProcessor = LoadingIndicatorsPostProcessor(systemClock)
|
private val loadingIndicatorsPostProcessor = LoadingIndicatorsPostProcessor(systemClock)
|
||||||
private val lastForwardIndicatorsPostProcessor = LastForwardIndicatorsPostProcessor(mode)
|
private val lastForwardIndicatorsPostProcessor = LastForwardIndicatorsPostProcessor(mode)
|
||||||
|
private val typingNotificationPostProcessor = TypingNotificationPostProcessor(mode)
|
||||||
|
|
||||||
private val backPaginationStatus = MutableStateFlow(
|
private val backPaginationStatus = MutableStateFlow(
|
||||||
Timeline.PaginationStatus(isPaginating = false, hasMoreToLoad = mode != Timeline.Mode.PINNED_EVENTS)
|
Timeline.PaginationStatus(isPaginating = false, hasMoreToLoad = mode != Timeline.Mode.PINNED_EVENTS)
|
||||||
|
|
@ -235,6 +237,9 @@ class RustTimeline(
|
||||||
hasMoreToLoadForward = hasMoreToLoadForward
|
hasMoreToLoadForward = hasMoreToLoadForward
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
.let { items ->
|
||||||
|
typingNotificationPostProcessor.process(items = items)
|
||||||
|
}
|
||||||
// Keep lastForwardIndicatorsPostProcessor last
|
// Keep lastForwardIndicatorsPostProcessor last
|
||||||
.let { items ->
|
.let { items ->
|
||||||
lastForwardIndicatorsPostProcessor.process(
|
lastForwardIndicatorsPostProcessor.process(
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
* Please see LICENSE in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.matrix.impl.timeline.postprocessor
|
||||||
|
|
||||||
|
import io.element.android.libraries.matrix.api.core.UniqueId
|
||||||
|
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
|
||||||
|
import io.element.android.libraries.matrix.api.timeline.Timeline
|
||||||
|
import io.element.android.libraries.matrix.api.timeline.item.virtual.VirtualTimelineItem
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This post processor is responsible for adding a typing notification item to the timeline items when the timeline is in live mode.
|
||||||
|
*/
|
||||||
|
class TypingNotificationPostProcessor(private val mode: Timeline.Mode) {
|
||||||
|
|
||||||
|
fun process(items: List<MatrixTimelineItem>): List<MatrixTimelineItem> {
|
||||||
|
return if (mode == Timeline.Mode.LIVE) {
|
||||||
|
buildList {
|
||||||
|
addAll(items)
|
||||||
|
add(
|
||||||
|
MatrixTimelineItem.Virtual(
|
||||||
|
uniqueId = UniqueId("TypingNotification"),
|
||||||
|
virtual = VirtualTimelineItem.TypingNotification
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
items
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue