Skip to content

Commit 331413e

Browse files
committed
Pinned events: make sure state is preserved
1 parent c965391 commit 331413e

File tree

3 files changed

+76
-31
lines changed

3 files changed

+76
-31
lines changed
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Copyright (c) 2024 New Vector Ltd
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.element.android.features.messages.impl.pinned
18+
19+
import androidx.compose.runtime.Composable
20+
import androidx.compose.runtime.LaunchedEffect
21+
import androidx.compose.runtime.getValue
22+
import androidx.compose.runtime.mutableStateOf
23+
import androidx.compose.runtime.saveable.rememberSaveable
24+
import androidx.compose.runtime.setValue
25+
import com.squareup.anvil.annotations.ContributesBinding
26+
import io.element.android.libraries.di.AppScope
27+
import io.element.android.libraries.featureflag.api.FeatureFlagService
28+
import io.element.android.libraries.featureflag.api.FeatureFlags
29+
import kotlinx.coroutines.flow.launchIn
30+
import kotlinx.coroutines.flow.onEach
31+
import javax.inject.Inject
32+
33+
fun interface IsPinnedMessagesFeatureEnabled {
34+
@Composable
35+
operator fun invoke(): Boolean
36+
}
37+
38+
@ContributesBinding(AppScope::class)
39+
class DefaultIsPinnedMessagesFeatureEnabled @Inject constructor(
40+
private val featureFlagService: FeatureFlagService,
41+
) : IsPinnedMessagesFeatureEnabled {
42+
@Composable
43+
override operator fun invoke(): Boolean {
44+
var isFeatureEnabled by rememberSaveable {
45+
mutableStateOf(false)
46+
}
47+
LaunchedEffect(Unit) {
48+
featureFlagService.isFeatureEnabledFlow(FeatureFlags.PinnedEvents)
49+
.onEach { isFeatureEnabled = it }
50+
.launchIn(this)
51+
}
52+
return isFeatureEnabled
53+
}
54+
}

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

Lines changed: 21 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,9 @@ import androidx.compose.runtime.remember
2626
import androidx.compose.runtime.rememberUpdatedState
2727
import androidx.compose.runtime.saveable.rememberSaveable
2828
import androidx.compose.runtime.setValue
29+
import io.element.android.features.messages.impl.pinned.IsPinnedMessagesFeatureEnabled
2930
import io.element.android.features.networkmonitor.api.NetworkMonitor
3031
import io.element.android.libraries.architecture.Presenter
31-
import io.element.android.libraries.featureflag.api.FeatureFlagService
32-
import io.element.android.libraries.featureflag.api.FeatureFlags
3332
import io.element.android.libraries.matrix.api.room.MatrixRoom
3433
import kotlinx.collections.immutable.ImmutableList
3534
import kotlinx.collections.immutable.persistentListOf
@@ -46,21 +45,20 @@ import kotlin.time.Duration.Companion.milliseconds
4645
class PinnedMessagesBannerPresenter @Inject constructor(
4746
private val room: MatrixRoom,
4847
private val itemFactory: PinnedMessagesBannerItemFactory,
49-
private val featureFlagService: FeatureFlagService,
48+
private val isFeatureEnabled: IsPinnedMessagesFeatureEnabled,
5049
private val networkMonitor: NetworkMonitor,
5150
) : Presenter<PinnedMessagesBannerState> {
51+
private val pinnedItems = mutableStateOf<ImmutableList<PinnedMessagesBannerItem>>(persistentListOf())
52+
5253
@Composable
5354
override fun present(): PinnedMessagesBannerState {
54-
val isFeatureEnabled by featureFlagService.isFeatureEnabledFlow(FeatureFlags.PinnedEvents).collectAsState(initial = false)
55-
var hasTimelineFailedToLoad by rememberSaveable { mutableStateOf(false) }
56-
var currentPinnedMessageIndex by rememberSaveable { mutableIntStateOf(-1) }
55+
val isFeatureEnabled = isFeatureEnabled()
5756
val knownPinnedMessagesCount by remember {
5857
room.roomInfoFlow.map { roomInfo -> roomInfo.pinnedEventIds.size }
5958
}.collectAsState(initial = 0)
6059

61-
var pinnedItems by remember {
62-
mutableStateOf<ImmutableList<PinnedMessagesBannerItem>>(persistentListOf())
63-
}
60+
var hasTimelineFailedToLoad by rememberSaveable { mutableStateOf(false) }
61+
var currentPinnedMessageIndex by rememberSaveable { mutableIntStateOf(-1) }
6462

6563
PinnedMessagesBannerItemsEffect(
6664
isFeatureEnabled = isFeatureEnabled,
@@ -69,7 +67,7 @@ class PinnedMessagesBannerPresenter @Inject constructor(
6967
if (currentPinnedMessageIndex >= pinnedMessageCount || currentPinnedMessageIndex < 0) {
7068
currentPinnedMessageIndex = pinnedMessageCount - 1
7169
}
72-
pinnedItems = newItems
70+
pinnedItems.value = newItems
7371
},
7472
onTimelineFail = { hasTimelineFailed ->
7573
hasTimelineFailedToLoad = hasTimelineFailed
@@ -82,7 +80,7 @@ class PinnedMessagesBannerPresenter @Inject constructor(
8280
if (currentPinnedMessageIndex > 0) {
8381
currentPinnedMessageIndex--
8482
} else {
85-
currentPinnedMessageIndex = pinnedItems.size - 1
83+
currentPinnedMessageIndex = pinnedItems.value.size - 1
8684
}
8785
}
8886
}
@@ -92,7 +90,7 @@ class PinnedMessagesBannerPresenter @Inject constructor(
9290
isFeatureEnabled = isFeatureEnabled,
9391
hasTimelineFailed = hasTimelineFailedToLoad,
9492
realPinnedMessagesCount = knownPinnedMessagesCount,
95-
pinnedItems = pinnedItems,
93+
pinnedItems = pinnedItems.value,
9694
currentPinnedMessageIndex = currentPinnedMessageIndex,
9795
eventSink = ::handleEvent
9896
)
@@ -111,16 +109,14 @@ class PinnedMessagesBannerPresenter @Inject constructor(
111109
return when {
112110
!isFeatureEnabled -> PinnedMessagesBannerState.Hidden
113111
hasTimelineFailed -> PinnedMessagesBannerState.Hidden
112+
currentPinnedMessage != null -> PinnedMessagesBannerState.Loaded(
113+
currentPinnedMessage = currentPinnedMessage,
114+
currentPinnedMessageIndex = currentPinnedMessageIndex,
115+
knownPinnedMessagesCount = pinnedItems.size,
116+
eventSink = eventSink
117+
)
114118
realPinnedMessagesCount == 0 -> PinnedMessagesBannerState.Hidden
115-
currentPinnedMessage == null -> PinnedMessagesBannerState.Loading(realPinnedMessagesCount = realPinnedMessagesCount)
116-
else -> {
117-
PinnedMessagesBannerState.Loaded(
118-
currentPinnedMessage = currentPinnedMessage,
119-
currentPinnedMessageIndex = currentPinnedMessageIndex,
120-
knownPinnedMessagesCount = pinnedItems.size,
121-
eventSink = eventSink
122-
)
123-
}
119+
else -> PinnedMessagesBannerState.Loading(realPinnedMessagesCount = realPinnedMessagesCount)
124120
}
125121
}
126122

@@ -136,8 +132,10 @@ class PinnedMessagesBannerPresenter @Inject constructor(
136132
val networkStatus by networkMonitor.connectivity.collectAsState()
137133

138134
LaunchedEffect(isFeatureEnabled, networkStatus) {
139-
if (!isFeatureEnabled) return@LaunchedEffect
140-
135+
if (!isFeatureEnabled) {
136+
updatedOnItemsChange(persistentListOf())
137+
return@LaunchedEffect
138+
}
141139
val pinnedEventsTimeline = room.pinnedEventsTimeline()
142140
.onFailure { updatedOnTimelineFail(true) }
143141
.onSuccess { updatedOnTimelineFail(false) }

features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerPresenterTest.kt

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,6 @@ import com.google.common.truth.Truth.assertThat
2020
import io.element.android.features.networkmonitor.api.NetworkMonitor
2121
import io.element.android.features.networkmonitor.test.FakeNetworkMonitor
2222
import io.element.android.libraries.eventformatter.test.FakePinnedMessagesBannerFormatter
23-
import io.element.android.libraries.featureflag.api.FeatureFlags
24-
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
2523
import io.element.android.libraries.matrix.api.room.MatrixRoom
2624
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
2725
import io.element.android.libraries.matrix.test.AN_EVENT_ID
@@ -195,15 +193,10 @@ class PinnedMessagesBannerPresenterTest {
195193
networkMonitor: NetworkMonitor = FakeNetworkMonitor(),
196194
isFeatureEnabled: Boolean = true,
197195
): PinnedMessagesBannerPresenter {
198-
val featureFlagService = FakeFeatureFlagService(
199-
initialState = mapOf(
200-
FeatureFlags.PinnedEvents.key to isFeatureEnabled
201-
)
202-
)
203196
return PinnedMessagesBannerPresenter(
204197
room = room,
205198
itemFactory = itemFactory,
206-
featureFlagService = featureFlagService,
199+
isFeatureEnabled = { isFeatureEnabled },
207200
networkMonitor = networkMonitor,
208201
)
209202
}

0 commit comments

Comments
 (0)