mirror of
https://github.com/Novaloop-AG/element-x-android.git
synced 2026-10-31 08:15:02 +00:00
Add MatrixClient.totalUnreadCountFlow() extension
Extract the logic to observe the total unread count of a MatrixClient, refreshed on each sync heartbeat, to a shared extension and use it in HomePresenter and PreferencesRootPresenter. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+4
-16
@@ -14,7 +14,6 @@ import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.produceState
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
@@ -33,16 +32,12 @@ import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||
import io.element.android.libraries.indicator.api.IndicatorService
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.sync.SyncService
|
||||
import io.element.android.libraries.matrix.api.totalUnreadCountFlow
|
||||
import io.element.android.libraries.sessionstorage.api.SessionStore
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.debounce
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.mapLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
|
||||
@Inject
|
||||
class HomePresenter(
|
||||
@@ -59,20 +54,13 @@ class HomePresenter(
|
||||
) : Presenter<HomeState> {
|
||||
private val currentUserWithNeighborsBuilder = CurrentUserWithNeighborsBuilder()
|
||||
|
||||
@OptIn(FlowPreview::class, ExperimentalCoroutinesApi::class)
|
||||
@Composable
|
||||
override fun present(): HomeState {
|
||||
val coroutineState = rememberCoroutineScope()
|
||||
val matrixUser by client.userProfile.collectAsState()
|
||||
|
||||
val totalUnreadCount by produceState(client.totalUnreadCount().getOrDefault(0).toInt()) {
|
||||
syncService.heartBeat
|
||||
// Delay the heartbeat so the SDK has time to update the unread count before we read it.
|
||||
.debounce(100.milliseconds)
|
||||
.mapLatest { client.totalUnreadCount().getOrDefault(0).toInt() }
|
||||
.collect { value = it }
|
||||
}
|
||||
|
||||
val totalUnreadCount by remember { client.totalUnreadCountFlow() }
|
||||
// The initial value is only used for the first composition, remember it to avoid calling the SDK on every recomposition.
|
||||
.collectAsState(initial = remember { client.totalUnreadCount().getOrDefault(0).toInt() })
|
||||
val currentUserAndNeighbors by remember {
|
||||
combine(
|
||||
client.userProfile,
|
||||
|
||||
+11
-2
@@ -6,6 +6,8 @@
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
@file:OptIn(ExperimentalCoroutinesApi::class)
|
||||
|
||||
package io.element.android.features.home.impl
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
@@ -39,6 +41,7 @@ import io.element.android.tests.testutils.WarmUpRule
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import io.element.android.tests.testutils.lambda.value
|
||||
import io.element.android.tests.testutils.test
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.test.advanceTimeBy
|
||||
import kotlinx.coroutines.test.runTest
|
||||
@@ -124,7 +127,10 @@ class HomePresenterTest {
|
||||
var unreadCount = 0L
|
||||
val syncService = FakeSyncService()
|
||||
val presenter = createHomePresenter(
|
||||
client = FakeMatrixClient(totalUnreadCountResult = { Result.success(unreadCount) }),
|
||||
client = FakeMatrixClient(
|
||||
syncService = syncService,
|
||||
totalUnreadCountResult = { Result.success(unreadCount) },
|
||||
),
|
||||
syncService = syncService,
|
||||
)
|
||||
presenter.test {
|
||||
@@ -140,7 +146,10 @@ class HomePresenterTest {
|
||||
fun `present - total unread count defaults to 0 when the SDK call fails`() = runTest {
|
||||
val syncService = FakeSyncService()
|
||||
val presenter = createHomePresenter(
|
||||
client = FakeMatrixClient(totalUnreadCountResult = { Result.failure(AN_EXCEPTION) }),
|
||||
client = FakeMatrixClient(
|
||||
syncService = syncService,
|
||||
totalUnreadCountResult = { Result.failure(AN_EXCEPTION) },
|
||||
),
|
||||
syncService = syncService,
|
||||
)
|
||||
presenter.test {
|
||||
|
||||
+2
-12
@@ -32,6 +32,7 @@ import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.MatrixClientProvider
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.matrix.api.totalUnreadCountFlow
|
||||
import io.element.android.libraries.matrix.api.user.toMatrixUser
|
||||
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
|
||||
import io.element.android.libraries.sessionstorage.api.SessionStore
|
||||
@@ -40,17 +41,12 @@ import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.debounce
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onStart
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@Inject
|
||||
@@ -182,14 +178,8 @@ class PreferencesRootPresenter(
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(FlowPreview::class)
|
||||
private fun unreadCountFlow(sessionId: SessionId): Flow<Int> {
|
||||
val client = matrixClientProvider.getOrNull(sessionId) ?: return flowOf(0)
|
||||
return client.syncService.heartBeat
|
||||
// Delay the heartbeat so the SDK has time to update the unread count before we read it.
|
||||
.debounce(100.milliseconds)
|
||||
.onStart { emit(Unit) }
|
||||
.map { client.totalUnreadCount().getOrDefault(0).toInt() }
|
||||
.distinctUntilChanged()
|
||||
return client.totalUnreadCountFlow()
|
||||
}
|
||||
}
|
||||
|
||||
+21
@@ -51,9 +51,15 @@ import io.element.android.libraries.matrix.api.user.UserStatus
|
||||
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.debounce
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onStart
|
||||
import java.util.Optional
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
|
||||
/**
|
||||
* Entry point for everything scoped to a single logged in session: the sub-services, room access, and account level operations.
|
||||
@@ -523,3 +529,18 @@ fun MatrixClient.roomAliasFromName(name: String): RoomAlias? {
|
||||
?.takeIf { MatrixPatterns.isRoomAlias(it) }
|
||||
?.let { tryOrNull { RoomAlias(it) } }
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a flow of the total number of unread notifications across all rooms.
|
||||
* The first value is emitted immediately, then the value is refreshed on each sync heartbeat.
|
||||
* In case of error, 0 is emitted.
|
||||
*/
|
||||
@OptIn(FlowPreview::class)
|
||||
fun MatrixClient.totalUnreadCountFlow(): Flow<Int> {
|
||||
return syncService.heartBeat
|
||||
// Delay the heartbeat so the SDK has time to update the unread count before we read it.
|
||||
.debounce(100.milliseconds)
|
||||
.onStart { emit(Unit) }
|
||||
.map { totalUnreadCount().getOrDefault(0).toInt() }
|
||||
.distinctUntilChanged()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user