Pinned events : only load data if feature is enabled

This commit is contained in:
ganfra 2024-08-02 11:14:24 +02:00
parent f63b59e118
commit 8e8c271bc2
3 changed files with 113 additions and 50 deletions

View file

@ -0,0 +1,47 @@
/*
* Copyright (c) 2024 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.messages.impl.pinned.banner
import androidx.compose.ui.text.AnnotatedString
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
import kotlinx.coroutines.withContext
import javax.inject.Inject
class PinnedMessagesBannerItemFactory @Inject constructor(
private val coroutineDispatchers: CoroutineDispatchers,
private val formatter: PinnedMessagesBannerFormatter,
) {
suspend fun create(timelineItem: MatrixTimelineItem): PinnedMessagesBannerItem? = withContext(coroutineDispatchers.computation) {
when (timelineItem) {
is MatrixTimelineItem.Event -> {
val eventId = timelineItem.eventId ?: return@withContext null
val formatted = formatter.format(timelineItem.event)
PinnedMessagesBannerItem(
eventId = eventId,
formatted = if (formatted is AnnotatedString) {
formatted
} else {
AnnotatedString(formatted.toString())
},
)
}
else -> null
}
}
}

View file

@ -18,21 +18,20 @@ package io.element.android.features.messages.impl.pinned.banner
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.text.AnnotatedString
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.eventformatter.api.PinnedMessagesBannerFormatter
import io.element.android.libraries.featureflag.api.FeatureFlagService
import io.element.android.libraries.featureflag.api.FeatureFlags
import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onCompletion
@ -42,61 +41,37 @@ import kotlin.time.Duration.Companion.milliseconds
class PinnedMessagesBannerPresenter @Inject constructor(
private val room: MatrixRoom,
private val pinnedMessagesBannerFormatter: PinnedMessagesBannerFormatter,
private val itemFactory: PinnedMessagesBannerItemFactory,
private val featureFlagService: FeatureFlagService,
) : Presenter<PinnedMessagesBannerState> {
@OptIn(FlowPreview::class)
@Composable
override fun present(): PinnedMessagesBannerState {
var pinnedMessages by remember {
var pinnedItems by remember {
mutableStateOf<List<PinnedMessagesBannerItem>>(emptyList())
}
fun onItemsChange(newItems: List<PinnedMessagesBannerItem>) {
pinnedItems = newItems
}
var currentPinnedMessageIndex by rememberSaveable {
mutableIntStateOf(0)
}
LaunchedEffect(pinnedMessages) {
val pinnedMessageCount = pinnedMessages.size
LaunchedEffect(pinnedItems) {
val pinnedMessageCount = pinnedItems.size
if (currentPinnedMessageIndex >= pinnedMessageCount) {
currentPinnedMessageIndex = (pinnedMessageCount - 1).coerceAtLeast(0)
}
}
LaunchedEffect(Unit) {
val pinnedEventsTimeline = room.pinnedEventsTimeline().getOrNull() ?: return@LaunchedEffect
pinnedEventsTimeline.timelineItems
.debounce(300.milliseconds)
.map { timelineItems ->
timelineItems.mapNotNull { timelineItem ->
when (timelineItem) {
is MatrixTimelineItem.Event -> {
val eventId = timelineItem.eventId ?: return@mapNotNull null
val formatted = pinnedMessagesBannerFormatter.format(timelineItem.event)
PinnedMessagesBannerItem(
eventId = eventId,
formatted = if (formatted is AnnotatedString) {
formatted
} else {
AnnotatedString(formatted.toString())
},
)
}
else -> null
}
}
}
.flowOn(Dispatchers.Default)
.onEach { newPinnedMessages ->
pinnedMessages = newPinnedMessages
}.onCompletion {
pinnedEventsTimeline.close()
}
.launchIn(this)
}
PinnedMessagesBannerItemsEffect(::onItemsChange)
fun handleEvent(event: PinnedMessagesBannerEvents) {
when (event) {
is PinnedMessagesBannerEvents.MoveToNextPinned -> {
if (currentPinnedMessageIndex < pinnedMessages.size - 1) {
if (currentPinnedMessageIndex < pinnedItems.size - 1) {
currentPinnedMessageIndex++
} else {
currentPinnedMessageIndex = 0
@ -106,10 +81,38 @@ class PinnedMessagesBannerPresenter @Inject constructor(
}
return PinnedMessagesBannerState(
pinnedMessagesCount = pinnedMessages.size,
currentPinnedMessage = pinnedMessages.getOrNull(currentPinnedMessageIndex),
pinnedMessagesCount = pinnedItems.size,
currentPinnedMessage = pinnedItems.getOrNull(currentPinnedMessageIndex),
currentPinnedMessageIndex = currentPinnedMessageIndex,
eventSink = ::handleEvent
)
}
@OptIn(FlowPreview::class)
@Composable
private fun PinnedMessagesBannerItemsEffect(
onItemsChange: (List<PinnedMessagesBannerItem>) -> Unit,
) {
val isFeatureEnabled by featureFlagService.isFeatureEnabledFlow(FeatureFlags.PinnedEvents).collectAsState(initial = false)
val updatedOnItemsChange by rememberUpdatedState(onItemsChange)
LaunchedEffect(isFeatureEnabled) {
if (!isFeatureEnabled) return@LaunchedEffect
val pinnedEventsTimeline = room.pinnedEventsTimeline().getOrNull() ?: return@LaunchedEffect
pinnedEventsTimeline.timelineItems
.debounce(300.milliseconds)
.map { timelineItems ->
timelineItems.mapNotNull { timelineItem ->
itemFactory.create(timelineItem)
}
}
.onEach { newItems ->
updatedOnItemsChange(newItems)
}.onCompletion {
pinnedEventsTimeline.close()
}
.launchIn(this)
}
}
}

View file

@ -17,11 +17,14 @@
package io.element.android.features.messages.impl.pinned.banner
import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.eventformatter.api.PinnedMessagesBannerFormatter
import io.element.android.libraries.eventformatter.test.FakePinnedMessagesBannerFormatter
import io.element.android.libraries.featureflag.api.FeatureFlags
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
import io.element.android.tests.testutils.test
import io.element.android.tests.testutils.testCoroutineDispatchers
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Test
@ -48,15 +51,25 @@ class PinnedMessagesBannerPresenterTest {
}
}
private fun createPinnedMessagesBannerPresenter(
private fun TestScope.createPinnedMessagesBannerPresenter(
room: MatrixRoom = FakeMatrixRoom(),
formatter: PinnedMessagesBannerFormatter = FakePinnedMessagesBannerFormatter(
formatLambda = { event -> "Content ${event.content}" }
)
itemFactory: PinnedMessagesBannerItemFactory = PinnedMessagesBannerItemFactory(
coroutineDispatchers = testCoroutineDispatchers(),
formatter = FakePinnedMessagesBannerFormatter(
formatLambda = { event -> "Content ${event.content}" }
)
),
isFeatureEnabled: Boolean = true,
): PinnedMessagesBannerPresenter {
val featureFlagService = FakeFeatureFlagService(
initialState = mapOf(
FeatureFlags.PinnedEvents.key to isFeatureEnabled
)
)
return PinnedMessagesBannerPresenter(
room = room,
pinnedMessagesBannerFormatter = formatter
itemFactory = itemFactory,
featureFlagService = featureFlagService
)
}
}