Pinned events : handle focus on pinned event
This commit is contained in:
parent
c820003414
commit
7d177b4b4d
11 changed files with 101 additions and 40 deletions
|
|
@ -17,7 +17,6 @@
|
||||||
package io.element.android.features.messages.impl
|
package io.element.android.features.messages.impl
|
||||||
|
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,6 @@ import androidx.compose.foundation.layout.navigationBarsPadding
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.statusBars
|
import androidx.compose.foundation.layout.statusBars
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
|
@ -73,6 +72,8 @@ import io.element.android.features.messages.impl.messagecomposer.AttachmentsStat
|
||||||
import io.element.android.features.messages.impl.messagecomposer.MessageComposerEvents
|
import io.element.android.features.messages.impl.messagecomposer.MessageComposerEvents
|
||||||
import io.element.android.features.messages.impl.messagecomposer.MessageComposerView
|
import io.element.android.features.messages.impl.messagecomposer.MessageComposerView
|
||||||
import io.element.android.features.messages.impl.pinned.banner.PinnedMessagesBannerView
|
import io.element.android.features.messages.impl.pinned.banner.PinnedMessagesBannerView
|
||||||
|
import io.element.android.features.messages.impl.pinned.banner.PinnedMessagesBannerViewDefaults
|
||||||
|
import io.element.android.features.messages.impl.timeline.TimelineEvents
|
||||||
import io.element.android.features.messages.impl.timeline.TimelineView
|
import io.element.android.features.messages.impl.timeline.TimelineView
|
||||||
import io.element.android.features.messages.impl.timeline.components.JoinCallMenuItem
|
import io.element.android.features.messages.impl.timeline.components.JoinCallMenuItem
|
||||||
import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionBottomSheet
|
import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionBottomSheet
|
||||||
|
|
@ -105,14 +106,15 @@ import io.element.android.libraries.designsystem.theme.components.Text
|
||||||
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||||
import io.element.android.libraries.designsystem.utils.KeepScreenOn
|
import io.element.android.libraries.designsystem.utils.KeepScreenOn
|
||||||
import io.element.android.libraries.designsystem.utils.OnLifecycleEvent
|
import io.element.android.libraries.designsystem.utils.OnLifecycleEvent
|
||||||
import io.element.android.libraries.designsystem.utils.isScrollingUp
|
|
||||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarHost
|
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarHost
|
||||||
import io.element.android.libraries.designsystem.utils.snackbar.rememberSnackbarHostState
|
import io.element.android.libraries.designsystem.utils.snackbar.rememberSnackbarHostState
|
||||||
|
import io.element.android.libraries.matrix.api.core.EventId
|
||||||
import io.element.android.libraries.matrix.api.core.UserId
|
import io.element.android.libraries.matrix.api.core.UserId
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
import kotlin.time.Duration.Companion.milliseconds
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MessagesView(
|
fun MessagesView(
|
||||||
|
|
@ -380,7 +382,7 @@ private fun MessagesViewContent(
|
||||||
},
|
},
|
||||||
content = { paddingValues ->
|
content = { paddingValues ->
|
||||||
Box(modifier = Modifier.padding(paddingValues)) {
|
Box(modifier = Modifier.padding(paddingValues)) {
|
||||||
val timelineLazyListState = rememberLazyListState()
|
val scrollBehavior = PinnedMessagesBannerViewDefaults.rememberExitOnScrollBehavior()
|
||||||
TimelineView(
|
TimelineView(
|
||||||
state = state.timelineState,
|
state = state.timelineState,
|
||||||
typingNotificationState = state.typingNotificationState,
|
typingNotificationState = state.typingNotificationState,
|
||||||
|
|
@ -395,18 +397,21 @@ private fun MessagesViewContent(
|
||||||
onReadReceiptClick = onReadReceiptClick,
|
onReadReceiptClick = onReadReceiptClick,
|
||||||
forceJumpToBottomVisibility = forceJumpToBottomVisibility,
|
forceJumpToBottomVisibility = forceJumpToBottomVisibility,
|
||||||
onJoinCallClick = onJoinCallClick,
|
onJoinCallClick = onJoinCallClick,
|
||||||
lazyListState = timelineLazyListState,
|
nestedScrollConnection = scrollBehavior.nestedScrollConnection,
|
||||||
)
|
)
|
||||||
AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
visible = state.pinnedMessagesBannerState.displayBanner && timelineLazyListState.isScrollingUp(),
|
visible = state.pinnedMessagesBannerState.displayBanner && scrollBehavior.isVisible,
|
||||||
enter = expandVertically(),
|
enter = expandVertically(),
|
||||||
exit = shrinkVertically(),
|
exit = shrinkVertically(),
|
||||||
) {
|
) {
|
||||||
|
fun focusOnPinnedEvent(eventId: EventId) {
|
||||||
|
state.timelineState.eventSink(
|
||||||
|
TimelineEvents.FocusOnEvent(eventId = eventId, debounce = 200.milliseconds)
|
||||||
|
)
|
||||||
|
}
|
||||||
PinnedMessagesBannerView(
|
PinnedMessagesBannerView(
|
||||||
state = state.pinnedMessagesBannerState,
|
state = state.pinnedMessagesBannerState,
|
||||||
onClick = { pinnedEventId ->
|
onClick = ::focusOnPinnedEvent,
|
||||||
//state.timelineState.eventSink(TimelineEvents.FocusOnEvent(pinnedEventId))
|
|
||||||
},
|
|
||||||
onViewAllClick = onViewAllPinnedMessagesClick,
|
onViewAllClick = onViewAllPinnedMessagesClick,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,6 @@ class PinnedMessagesBannerPresenter @Inject constructor(
|
||||||
private val itemFactory: PinnedMessagesBannerItemFactory,
|
private val itemFactory: PinnedMessagesBannerItemFactory,
|
||||||
private val featureFlagService: FeatureFlagService,
|
private val featureFlagService: FeatureFlagService,
|
||||||
) : Presenter<PinnedMessagesBannerState> {
|
) : Presenter<PinnedMessagesBannerState> {
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun present(): PinnedMessagesBannerState {
|
override fun present(): PinnedMessagesBannerState {
|
||||||
var pinnedItems by remember {
|
var pinnedItems by remember {
|
||||||
|
|
@ -109,7 +108,8 @@ class PinnedMessagesBannerPresenter @Inject constructor(
|
||||||
}
|
}
|
||||||
.onEach { newItems ->
|
.onEach { newItems ->
|
||||||
updatedOnItemsChange(newItems)
|
updatedOnItemsChange(newItems)
|
||||||
}.onCompletion {
|
}
|
||||||
|
.onCompletion {
|
||||||
pinnedEventsTimeline.close()
|
pinnedEventsTimeline.close()
|
||||||
}
|
}
|
||||||
.launchIn(this)
|
.launchIn(this)
|
||||||
|
|
|
||||||
|
|
@ -33,13 +33,19 @@ import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.drawBehind
|
import androidx.compose.ui.draw.drawBehind
|
||||||
import androidx.compose.ui.draw.shadow
|
import androidx.compose.ui.draw.shadow
|
||||||
import androidx.compose.ui.geometry.Offset
|
import androidx.compose.ui.geometry.Offset
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
||||||
|
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.AnnotatedString
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
|
@ -197,6 +203,34 @@ private fun PinnedMessageItem(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
internal interface PinnedMessagesBannerViewScrollBehavior {
|
||||||
|
val isVisible: Boolean
|
||||||
|
val nestedScrollConnection: NestedScrollConnection
|
||||||
|
}
|
||||||
|
|
||||||
|
internal object PinnedMessagesBannerViewDefaults {
|
||||||
|
@Composable
|
||||||
|
fun rememberExitOnScrollBehavior(): PinnedMessagesBannerViewScrollBehavior = remember {
|
||||||
|
ExitOnScrollBehavior()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ExitOnScrollBehavior : PinnedMessagesBannerViewScrollBehavior {
|
||||||
|
override var isVisible by mutableStateOf(true)
|
||||||
|
override val nestedScrollConnection: NestedScrollConnection = object : NestedScrollConnection {
|
||||||
|
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
|
||||||
|
if (available.y < -1) {
|
||||||
|
isVisible = true
|
||||||
|
}
|
||||||
|
if (available.y > 1) {
|
||||||
|
isVisible = false
|
||||||
|
}
|
||||||
|
return Offset.Zero
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@PreviewsDayNight
|
@PreviewsDayNight
|
||||||
@Composable
|
@Composable
|
||||||
internal fun PinnedMessagesBannerViewPreview(@PreviewParameter(PinnedMessagesBannerStateProvider::class) state: PinnedMessagesBannerState) = ElementPreview {
|
internal fun PinnedMessagesBannerViewPreview(@PreviewParameter(PinnedMessagesBannerStateProvider::class) state: PinnedMessagesBannerState) = ElementPreview {
|
||||||
|
|
|
||||||
|
|
@ -18,10 +18,11 @@ package io.element.android.features.messages.impl.timeline
|
||||||
|
|
||||||
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.Timeline
|
import io.element.android.libraries.matrix.api.timeline.Timeline
|
||||||
|
import kotlin.time.Duration
|
||||||
|
|
||||||
sealed interface TimelineEvents {
|
sealed interface TimelineEvents {
|
||||||
data class OnScrollFinished(val firstIndex: Int) : TimelineEvents
|
data class OnScrollFinished(val firstIndex: Int) : TimelineEvents
|
||||||
data class FocusOnEvent(val eventId: EventId) : TimelineEvents
|
data class FocusOnEvent(val eventId: EventId, val debounce: Duration = Duration.ZERO) : TimelineEvents
|
||||||
data object ClearFocusRequestState : TimelineEvents
|
data object ClearFocusRequestState : TimelineEvents
|
||||||
data object OnFocusEventRender : TimelineEvents
|
data object OnFocusEventRender : TimelineEvents
|
||||||
data object JumpToLive : TimelineEvents
|
data object JumpToLive : TimelineEvents
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,7 @@ import io.element.android.libraries.matrix.ui.room.canSendMessageAsState
|
||||||
import io.element.android.libraries.preferences.api.store.SessionPreferencesStore
|
import io.element.android.libraries.preferences.api.store.SessionPreferencesStore
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
|
@ -136,13 +137,8 @@ class TimelinePresenter @AssistedInject constructor(
|
||||||
is TimelineEvents.EditPoll -> {
|
is TimelineEvents.EditPoll -> {
|
||||||
navigator.onEditPollClick(event.pollStartId)
|
navigator.onEditPollClick(event.pollStartId)
|
||||||
}
|
}
|
||||||
is TimelineEvents.FocusOnEvent -> localScope.launch {
|
is TimelineEvents.FocusOnEvent -> {
|
||||||
if (timelineItemIndexer.isKnown(event.eventId)) {
|
focusRequestState.value = FocusRequestState.Requested(event.eventId, event.debounce)
|
||||||
val index = timelineItemIndexer.indexOf(event.eventId)
|
|
||||||
focusRequestState.value = FocusRequestState.Success(eventId = event.eventId, index = index)
|
|
||||||
} else {
|
|
||||||
focusRequestState.value = FocusRequestState.Loading(eventId = event.eventId)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
is TimelineEvents.OnFocusEventRender -> {
|
is TimelineEvents.OnFocusEventRender -> {
|
||||||
focusRequestState.value = focusRequestState.value.onFocusEventRender()
|
focusRequestState.value = focusRequestState.value.onFocusEventRender()
|
||||||
|
|
@ -157,18 +153,29 @@ class TimelinePresenter @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(focusRequestState.value) {
|
LaunchedEffect(focusRequestState.value) {
|
||||||
val currentFocusRequestState = focusRequestState.value
|
when (val currentFocusRequestState = focusRequestState.value) {
|
||||||
if (currentFocusRequestState is FocusRequestState.Loading) {
|
is FocusRequestState.Requested -> {
|
||||||
val eventId = currentFocusRequestState.eventId
|
delay(currentFocusRequestState.debounce)
|
||||||
timelineController.focusOnEvent(eventId)
|
if (timelineItemIndexer.isKnown(currentFocusRequestState.eventId)) {
|
||||||
.fold(
|
val index = timelineItemIndexer.indexOf(currentFocusRequestState.eventId)
|
||||||
onSuccess = {
|
focusRequestState.value = FocusRequestState.Success(eventId = currentFocusRequestState.eventId, index = index)
|
||||||
focusRequestState.value = FocusRequestState.Success(eventId = eventId)
|
} else {
|
||||||
},
|
focusRequestState.value = FocusRequestState.Loading(eventId = currentFocusRequestState.eventId)
|
||||||
onFailure = {
|
}
|
||||||
focusRequestState.value = FocusRequestState.Failure(throwable = it)
|
}
|
||||||
}
|
is FocusRequestState.Loading -> {
|
||||||
)
|
val eventId = currentFocusRequestState.eventId
|
||||||
|
timelineController.focusOnEvent(eventId)
|
||||||
|
.fold(
|
||||||
|
onSuccess = {
|
||||||
|
focusRequestState.value = FocusRequestState.Success(eventId = eventId)
|
||||||
|
},
|
||||||
|
onFailure = {
|
||||||
|
focusRequestState.value = FocusRequestState.Failure(throwable = it)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else -> Unit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ 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.libraries.matrix.api.core.EventId
|
import io.element.android.libraries.matrix.api.core.EventId
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
import kotlin.time.Duration
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
data class TimelineState(
|
data class TimelineState(
|
||||||
|
|
@ -39,6 +40,7 @@ data class TimelineState(
|
||||||
@Immutable
|
@Immutable
|
||||||
sealed interface FocusRequestState {
|
sealed interface FocusRequestState {
|
||||||
data object None : FocusRequestState
|
data object None : FocusRequestState
|
||||||
|
data class Requested(val eventId: EventId, val debounce: Duration) : FocusRequestState
|
||||||
data class Loading(val eventId: EventId) : FocusRequestState
|
data class Loading(val eventId: EventId) : FocusRequestState
|
||||||
data class Success(
|
data class Success(
|
||||||
val eventId: EventId,
|
val eventId: EventId,
|
||||||
|
|
@ -54,6 +56,7 @@ sealed interface FocusRequestState {
|
||||||
|
|
||||||
fun eventId(): EventId? {
|
fun eventId(): EventId? {
|
||||||
return when (this) {
|
return when (this) {
|
||||||
|
is Requested -> eventId
|
||||||
is Loading -> eventId
|
is Loading -> eventId
|
||||||
is Success -> eventId
|
is Success -> eventId
|
||||||
else -> null
|
else -> null
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,10 @@ import androidx.compose.runtime.rememberUpdatedState
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.rotate
|
import androidx.compose.ui.draw.rotate
|
||||||
|
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
||||||
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.platform.rememberNestedScrollInteropConnection
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
@ -91,7 +94,8 @@ fun TimelineView(
|
||||||
onJoinCallClick: () -> Unit,
|
onJoinCallClick: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
lazyListState: LazyListState = rememberLazyListState(),
|
lazyListState: LazyListState = rememberLazyListState(),
|
||||||
forceJumpToBottomVisibility: Boolean = false
|
forceJumpToBottomVisibility: Boolean = false,
|
||||||
|
nestedScrollConnection: NestedScrollConnection = rememberNestedScrollInteropConnection(),
|
||||||
) {
|
) {
|
||||||
fun clearFocusRequestState() {
|
fun clearFocusRequestState() {
|
||||||
state.eventSink(TimelineEvents.ClearFocusRequestState)
|
state.eventSink(TimelineEvents.ClearFocusRequestState)
|
||||||
|
|
@ -124,7 +128,9 @@ fun TimelineView(
|
||||||
AnimatedVisibility(visible = true, enter = fadeIn()) {
|
AnimatedVisibility(visible = true, enter = fadeIn()) {
|
||||||
Box(modifier) {
|
Box(modifier) {
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.nestedScroll(nestedScrollConnection),
|
||||||
state = lazyListState,
|
state = lazyListState,
|
||||||
reverseLayout = useReverseLayout,
|
reverseLayout = useReverseLayout,
|
||||||
contentPadding = PaddingValues(vertical = 8.dp),
|
contentPadding = PaddingValues(vertical = 8.dp),
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,7 @@ import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
|
import kotlin.time.Duration
|
||||||
import kotlin.time.Duration.Companion.seconds
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
private const val FAKE_UNIQUE_ID = "FAKE_UNIQUE_ID"
|
private const val FAKE_UNIQUE_ID = "FAKE_UNIQUE_ID"
|
||||||
|
|
@ -496,6 +497,10 @@ private const val FAKE_UNIQUE_ID_2 = "FAKE_UNIQUE_ID_2"
|
||||||
}.test {
|
}.test {
|
||||||
val initialState = awaitFirstItem()
|
val initialState = awaitFirstItem()
|
||||||
initialState.eventSink.invoke(TimelineEvents.FocusOnEvent(AN_EVENT_ID))
|
initialState.eventSink.invoke(TimelineEvents.FocusOnEvent(AN_EVENT_ID))
|
||||||
|
awaitItem().also { state ->
|
||||||
|
assertThat(state.focusedEventId).isEqualTo(AN_EVENT_ID)
|
||||||
|
assertThat(state.focusRequestState).isEqualTo(FocusRequestState.Requested(AN_EVENT_ID, Duration.ZERO))
|
||||||
|
}
|
||||||
awaitItem().also { state ->
|
awaitItem().also { state ->
|
||||||
assertThat(state.focusedEventId).isEqualTo(AN_EVENT_ID)
|
assertThat(state.focusedEventId).isEqualTo(AN_EVENT_ID)
|
||||||
assertThat(state.focusRequestState).isEqualTo(FocusRequestState.Loading(AN_EVENT_ID))
|
assertThat(state.focusRequestState).isEqualTo(FocusRequestState.Loading(AN_EVENT_ID))
|
||||||
|
|
@ -541,6 +546,10 @@ private const val FAKE_UNIQUE_ID_2 = "FAKE_UNIQUE_ID_2"
|
||||||
}.test {
|
}.test {
|
||||||
val initialState = awaitFirstItem()
|
val initialState = awaitFirstItem()
|
||||||
initialState.eventSink.invoke(TimelineEvents.FocusOnEvent(AN_EVENT_ID))
|
initialState.eventSink.invoke(TimelineEvents.FocusOnEvent(AN_EVENT_ID))
|
||||||
|
awaitItem().also { state ->
|
||||||
|
assertThat(state.focusedEventId).isEqualTo(AN_EVENT_ID)
|
||||||
|
assertThat(state.focusRequestState).isEqualTo(FocusRequestState.Requested(AN_EVENT_ID, Duration.ZERO))
|
||||||
|
}
|
||||||
awaitItem().also { state ->
|
awaitItem().also { state ->
|
||||||
assertThat(state.focusedEventId).isEqualTo(AN_EVENT_ID)
|
assertThat(state.focusedEventId).isEqualTo(AN_EVENT_ID)
|
||||||
assertThat(state.focusRequestState).isEqualTo(FocusRequestState.Success(AN_EVENT_ID, 0))
|
assertThat(state.focusRequestState).isEqualTo(FocusRequestState.Success(AN_EVENT_ID, 0))
|
||||||
|
|
@ -564,6 +573,10 @@ private const val FAKE_UNIQUE_ID_2 = "FAKE_UNIQUE_ID_2"
|
||||||
}.test {
|
}.test {
|
||||||
val initialState = awaitFirstItem()
|
val initialState = awaitFirstItem()
|
||||||
initialState.eventSink(TimelineEvents.FocusOnEvent(AN_EVENT_ID))
|
initialState.eventSink(TimelineEvents.FocusOnEvent(AN_EVENT_ID))
|
||||||
|
awaitItem().also { state ->
|
||||||
|
assertThat(state.focusedEventId).isEqualTo(AN_EVENT_ID)
|
||||||
|
assertThat(state.focusRequestState).isEqualTo(FocusRequestState.Requested(AN_EVENT_ID, Duration.ZERO))
|
||||||
|
}
|
||||||
awaitItem().also { state ->
|
awaitItem().also { state ->
|
||||||
assertThat(state.focusedEventId).isEqualTo(AN_EVENT_ID)
|
assertThat(state.focusedEventId).isEqualTo(AN_EVENT_ID)
|
||||||
assertThat(state.focusRequestState).isEqualTo(FocusRequestState.Loading(AN_EVENT_ID))
|
assertThat(state.focusRequestState).isEqualTo(FocusRequestState.Loading(AN_EVENT_ID))
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,6 @@ class DefaultPinnedMessagesBannerFormatter @Inject constructor(
|
||||||
private val sp: StringProvider,
|
private val sp: StringProvider,
|
||||||
private val permalinkParser: PermalinkParser,
|
private val permalinkParser: PermalinkParser,
|
||||||
) : PinnedMessagesBannerFormatter {
|
) : PinnedMessagesBannerFormatter {
|
||||||
|
|
||||||
override fun format(event: EventTimelineItem): CharSequence {
|
override fun format(event: EventTimelineItem): CharSequence {
|
||||||
return when (val content = event.content) {
|
return when (val content = event.content) {
|
||||||
is MessageContent -> processMessageContents(event, content)
|
is MessageContent -> processMessageContents(event, content)
|
||||||
|
|
@ -77,7 +76,6 @@ class DefaultPinnedMessagesBannerFormatter @Inject constructor(
|
||||||
event: EventTimelineItem,
|
event: EventTimelineItem,
|
||||||
messageContent: MessageContent,
|
messageContent: MessageContent,
|
||||||
): CharSequence {
|
): CharSequence {
|
||||||
|
|
||||||
return when (val messageType: MessageType = messageContent.type) {
|
return when (val messageType: MessageType = messageContent.type) {
|
||||||
is EmoteMessageType -> {
|
is EmoteMessageType -> {
|
||||||
val senderDisambiguatedDisplayName = event.senderProfile.getDisambiguatedDisplayName(event.sender)
|
val senderDisambiguatedDisplayName = event.senderProfile.getDisambiguatedDisplayName(event.sender)
|
||||||
|
|
|
||||||
|
|
@ -16,11 +16,6 @@
|
||||||
|
|
||||||
package io.element.android.libraries.eventformatter.impl
|
package io.element.android.libraries.eventformatter.impl
|
||||||
|
|
||||||
import androidx.compose.ui.text.AnnotatedString
|
|
||||||
import androidx.compose.ui.text.SpanStyle
|
|
||||||
import androidx.compose.ui.text.buildAnnotatedString
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
|
||||||
import androidx.compose.ui.text.withStyle
|
|
||||||
import com.squareup.anvil.annotations.ContributesBinding
|
import com.squareup.anvil.annotations.ContributesBinding
|
||||||
import io.element.android.libraries.di.SessionScope
|
import io.element.android.libraries.di.SessionScope
|
||||||
import io.element.android.libraries.eventformatter.api.RoomLastMessageFormatter
|
import io.element.android.libraries.eventformatter.api.RoomLastMessageFormatter
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue