Skip to content

Commit ba7baea

Browse files
authored
Merge pull request #3432 from element-hq/feature/fga/pinned_messages_fix_timeline_provider
Feature/fga/pinned messages fix timeline provider
2 parents 4690cd8 + 99158da commit ba7baea

File tree

3 files changed

+46
-13
lines changed

3 files changed

+46
-13
lines changed

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/PinnedEventsTimelineProvider.kt

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,13 @@ import io.element.android.libraries.matrix.api.room.MatrixRoom
1818
import io.element.android.libraries.matrix.api.timeline.Timeline
1919
import io.element.android.libraries.matrix.api.timeline.TimelineProvider
2020
import kotlinx.coroutines.CoroutineScope
21+
import kotlinx.coroutines.coroutineScope
2122
import kotlinx.coroutines.flow.MutableStateFlow
2223
import kotlinx.coroutines.flow.StateFlow
2324
import kotlinx.coroutines.flow.combine
25+
import kotlinx.coroutines.flow.distinctUntilChanged
2426
import kotlinx.coroutines.flow.launchIn
25-
import kotlinx.coroutines.flow.onCompletion
27+
import kotlinx.coroutines.flow.map
2628
import kotlinx.coroutines.flow.onEach
2729
import javax.inject.Inject
2830

@@ -32,7 +34,8 @@ class PinnedEventsTimelineProvider @Inject constructor(
3234
private val networkMonitor: NetworkMonitor,
3335
private val featureFlagService: FeatureFlagService,
3436
) : TimelineProvider {
35-
private val _timelineStateFlow: MutableStateFlow<AsyncData<Timeline>> = MutableStateFlow(AsyncData.Uninitialized)
37+
private val _timelineStateFlow: MutableStateFlow<AsyncData<Timeline>> =
38+
MutableStateFlow(AsyncData.Uninitialized)
3639

3740
override fun activeTimelineFlow(): StateFlow<Timeline?> {
3841
return _timelineStateFlow
@@ -44,25 +47,46 @@ class PinnedEventsTimelineProvider @Inject constructor(
4447
val timelineStateFlow = _timelineStateFlow
4548

4649
fun launchIn(scope: CoroutineScope) {
50+
_timelineStateFlow.subscriptionCount
51+
.map { count -> count > 0 }
52+
.distinctUntilChanged()
53+
.onEach { isActive ->
54+
if (isActive) {
55+
onActive()
56+
} else {
57+
onInactive()
58+
}
59+
}
60+
.launchIn(scope)
61+
}
62+
63+
private suspend fun onActive() = coroutineScope {
4764
combine(
4865
featureFlagService.isFeatureEnabledFlow(FeatureFlags.PinnedEvents),
4966
networkMonitor.connectivity
50-
) {
51-
// do not use connectivity here as data can be loaded from cache, it's just to trigger retry if needed
52-
isEnabled, _ ->
67+
) { isEnabled, _ ->
68+
// do not use connectivity here as data can be loaded from cache, it's just to trigger retry if needed
5369
isEnabled
5470
}
5571
.onEach { isFeatureEnabled ->
5672
if (isFeatureEnabled) {
5773
loadTimelineIfNeeded()
5874
} else {
59-
_timelineStateFlow.value = AsyncData.Uninitialized
75+
resetTimeline()
6076
}
6177
}
62-
.onCompletion {
63-
invokeOnTimeline { close() }
64-
}
65-
.launchIn(scope)
78+
.launchIn(this)
79+
}
80+
81+
private suspend fun onInactive() {
82+
resetTimeline()
83+
}
84+
85+
private suspend fun resetTimeline() {
86+
invokeOnTimeline {
87+
close()
88+
}
89+
_timelineStateFlow.emit(AsyncData.Uninitialized)
6690
}
6791

6892
suspend fun invokeOnTimeline(action: suspend Timeline.() -> Unit) {

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListPresenter.kt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import androidx.compose.runtime.getValue
1515
import androidx.compose.runtime.mutableStateOf
1616
import androidx.compose.runtime.produceState
1717
import androidx.compose.runtime.remember
18-
import androidx.compose.runtime.rememberCoroutineScope
1918
import androidx.compose.runtime.rememberUpdatedState
2019
import androidx.compose.runtime.setValue
2120
import dagger.assisted.Assisted
@@ -59,6 +58,7 @@ class PinnedMessagesListPresenter @AssistedInject constructor(
5958
private val timelineProvider: PinnedEventsTimelineProvider,
6059
private val snackbarDispatcher: SnackbarDispatcher,
6160
actionListPresenterFactory: ActionListPresenter.Factory,
61+
private val appCoroutineScope: CoroutineScope,
6262
) : Presenter<PinnedMessagesListState> {
6363
@AssistedFactory
6464
interface Factory {
@@ -93,10 +93,9 @@ class PinnedMessagesListPresenter @AssistedInject constructor(
9393
}
9494
)
9595

96-
val coroutineScope = rememberCoroutineScope()
9796
fun handleEvents(event: PinnedMessagesListEvents) {
9897
when (event) {
99-
is PinnedMessagesListEvents.HandleAction -> coroutineScope.handleTimelineAction(event.action, event.event)
98+
is PinnedMessagesListEvents.HandleAction -> appCoroutineScope.handleTimelineAction(event.action, event.event)
10099
}
101100
}
102101

features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListPresenterTest.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,14 @@ import io.element.android.tests.testutils.lambda.assert
3535
import io.element.android.tests.testutils.lambda.lambdaRecorder
3636
import io.element.android.tests.testutils.lambda.value
3737
import io.element.android.tests.testutils.test
38+
import kotlinx.coroutines.ExperimentalCoroutinesApi
3839
import kotlinx.coroutines.flow.flowOf
3940
import kotlinx.coroutines.test.TestScope
41+
import kotlinx.coroutines.test.advanceUntilIdle
4042
import kotlinx.coroutines.test.runTest
4143
import org.junit.Test
4244

45+
@OptIn(ExperimentalCoroutinesApi::class)
4346
class PinnedMessagesListPresenterTest {
4447
@Test
4548
fun `present - initial state feature disabled`() = runTest {
@@ -155,6 +158,7 @@ class PinnedMessagesListPresenterTest {
155158
val filledState = awaitItem() as PinnedMessagesListState.Filled
156159
val eventItem = filledState.timelineItems.first() as TimelineItem.Event
157160
filledState.eventSink(PinnedMessagesListEvents.HandleAction(TimelineItemAction.Redact, eventItem))
161+
advanceUntilIdle()
158162
cancelAndIgnoreRemainingEvents()
159163
assert(redactEventLambda)
160164
.isCalledOnce()
@@ -184,9 +188,11 @@ class PinnedMessagesListPresenterTest {
184188

185189
pinnedEventsTimeline.unpinEventLambda = successUnpinEventLambda
186190
filledState.eventSink(PinnedMessagesListEvents.HandleAction(TimelineItemAction.Unpin, eventItem))
191+
advanceUntilIdle()
187192

188193
pinnedEventsTimeline.unpinEventLambda = failureUnpinEventLambda
189194
filledState.eventSink(PinnedMessagesListEvents.HandleAction(TimelineItemAction.Unpin, eventItem))
195+
advanceUntilIdle()
190196

191197
cancelAndIgnoreRemainingEvents()
192198

@@ -221,6 +227,7 @@ class PinnedMessagesListPresenterTest {
221227
val filledState = awaitItem() as PinnedMessagesListState.Filled
222228
val eventItem = filledState.timelineItems.first() as TimelineItem.Event
223229
filledState.eventSink(PinnedMessagesListEvents.HandleAction(TimelineItemAction.ViewInTimeline, eventItem))
230+
advanceUntilIdle()
224231
cancelAndIgnoreRemainingEvents()
225232
assert(onViewInTimelineClickLambda)
226233
.isCalledOnce()
@@ -249,6 +256,7 @@ class PinnedMessagesListPresenterTest {
249256
val filledState = awaitItem() as PinnedMessagesListState.Filled
250257
val eventItem = filledState.timelineItems.first() as TimelineItem.Event
251258
filledState.eventSink(PinnedMessagesListEvents.HandleAction(TimelineItemAction.ViewSource, eventItem))
259+
advanceUntilIdle()
252260
cancelAndIgnoreRemainingEvents()
253261
assert(onShowEventDebugInfoClickLambda)
254262
.isCalledOnce()
@@ -277,6 +285,7 @@ class PinnedMessagesListPresenterTest {
277285
val filledState = awaitItem() as PinnedMessagesListState.Filled
278286
val eventItem = filledState.timelineItems.first() as TimelineItem.Event
279287
filledState.eventSink(PinnedMessagesListEvents.HandleAction(TimelineItemAction.Forward, eventItem))
288+
advanceUntilIdle()
280289
cancelAndIgnoreRemainingEvents()
281290
assert(onForwardEventClickLambda)
282291
.isCalledOnce()
@@ -322,6 +331,7 @@ class PinnedMessagesListPresenterTest {
322331
timelineProvider = timelineProvider,
323332
snackbarDispatcher = SnackbarDispatcher(),
324333
actionListPresenterFactory = FakeActionListPresenter.Factory,
334+
appCoroutineScope = this,
325335
)
326336
}
327337
}

0 commit comments

Comments
 (0)