mirror of
https://github.com/Novaloop-AG/element-x-android.git
synced 2026-10-31 08:15:02 +00:00
Merge pull request #7922 from element-hq/feature/bma/otherAccountUnreadCount
Show the unread count of the other accounts in the multi-account section
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
|
||||
@@ -34,17 +33,13 @@ 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.CoroutineScope
|
||||
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(
|
||||
@@ -63,20 +58,13 @@ class HomePresenter(
|
||||
) : Presenter<HomeState> {
|
||||
private val currentUserWithNeighborsBuilder = CurrentUserWithNeighborsBuilder()
|
||||
|
||||
@OptIn(FlowPreview::class, ExperimentalCoroutinesApi::class)
|
||||
@Composable
|
||||
override fun present(): HomeState {
|
||||
val coroutineScope = 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,
|
||||
|
||||
+10
-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
|
||||
@@ -129,7 +131,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 {
|
||||
@@ -146,7 +151,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 {
|
||||
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright (c) 2026 Element Creations Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.preferences.impl.root
|
||||
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
|
||||
/**
|
||||
* A signed-in account which is not the current one.
|
||||
*
|
||||
* @param user the user of the account.
|
||||
* @param unreadCount the total number of unread notifications for this account.
|
||||
*/
|
||||
data class OtherAccount(
|
||||
val user: MatrixUser,
|
||||
val unreadCount: Int,
|
||||
)
|
||||
+29
-6
@@ -30,6 +30,9 @@ import io.element.android.libraries.di.annotations.SessionCoroutineScope
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||
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
|
||||
@@ -37,9 +40,15 @@ import io.element.android.services.analytics.api.AnalyticsService
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@Inject
|
||||
class PreferencesRootPresenter(
|
||||
private val matrixClient: MatrixClient,
|
||||
@@ -50,6 +59,7 @@ class PreferencesRootPresenter(
|
||||
private val featureFlagService: FeatureFlagService,
|
||||
private val locationService: LocationService,
|
||||
private val sessionStore: SessionStore,
|
||||
private val matrixClientProvider: MatrixClientProvider,
|
||||
private val appPreferencesStore: AppPreferencesStore,
|
||||
private val userStatusPresenter: Presenter<UserStatusState>,
|
||||
private val preferencesAccountPresenter: Presenter<PreferencesAccountState>,
|
||||
@@ -83,12 +93,20 @@ class PreferencesRootPresenter(
|
||||
}.collectAsState(initial = Theme.System)
|
||||
|
||||
val otherSessions by remember {
|
||||
sessionStore.sessionsFlow().map { list ->
|
||||
list
|
||||
.filter { it.userId != matrixClient.sessionId.value }
|
||||
.map { it.toMatrixUser() }
|
||||
.toImmutableList()
|
||||
}
|
||||
sessionStore.sessionsFlow()
|
||||
.map { list -> list.filter { it.userId != matrixClient.sessionId.value } }
|
||||
.flatMapLatest { sessions ->
|
||||
if (sessions.isEmpty()) {
|
||||
flowOf(persistentListOf())
|
||||
} else {
|
||||
combine(
|
||||
sessions.map { sessionData ->
|
||||
val user = sessionData.toMatrixUser()
|
||||
unreadCountFlow(user.userId).map { unreadCount -> OtherAccount(user, unreadCount) }
|
||||
}
|
||||
) { it.toImmutableList() }
|
||||
}
|
||||
}
|
||||
}.collectAsState(initial = persistentListOf())
|
||||
|
||||
val themeOption by remember {
|
||||
@@ -156,4 +174,9 @@ class PreferencesRootPresenter(
|
||||
eventSink = ::handleEvent,
|
||||
)
|
||||
}
|
||||
|
||||
private fun unreadCountFlow(sessionId: SessionId): Flow<Int> {
|
||||
val client = matrixClientProvider.getOrNull(sessionId) ?: return flowOf(0)
|
||||
return client.totalUnreadCountFlow()
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -28,7 +28,7 @@ data class PreferencesRootState(
|
||||
val version: String,
|
||||
val isMultiAccountEnabled: Boolean,
|
||||
val isOtherAccountsSectionExpanded: Boolean,
|
||||
val otherSessions: ImmutableList<MatrixUser>,
|
||||
val otherSessions: ImmutableList<OtherAccount>,
|
||||
val showAnalyticsSettings: Boolean,
|
||||
val showDeveloperSettings: Boolean,
|
||||
val showLabsItem: Boolean,
|
||||
|
||||
+19
-3
@@ -29,7 +29,7 @@ open class PreferencesRootStatePreviewParam : PreviewParameterProvider<Preferenc
|
||||
myUser = aMatrixUser(avatarUrl = "anAvatarUrl"),
|
||||
version = "Version 1.1 (1)",
|
||||
isMultiAccountEnabled = true,
|
||||
otherSessions = aMatrixUserList().drop(1).take(1),
|
||||
otherSessions = aMatrixUserList().drop(1).take(1).map { anOtherAccount(it) },
|
||||
showAnalyticsSettings = true,
|
||||
showLabsItem = true,
|
||||
),
|
||||
@@ -40,7 +40,7 @@ open class PreferencesRootStatePreviewParam : PreviewParameterProvider<Preferenc
|
||||
aPreferencesRootState(
|
||||
isMultiAccountEnabled = true,
|
||||
isOtherAccountsSectionExpanded = true,
|
||||
otherSessions = aMatrixUserList().drop(1).take(3),
|
||||
otherSessions = aMatrixUserList().drop(1).take(3).map { anOtherAccount(it) },
|
||||
),
|
||||
aPreferencesRootState(
|
||||
showLabsItem = true,
|
||||
@@ -55,6 +55,14 @@ open class PreferencesRootStatePreviewParam : PreviewParameterProvider<Preferenc
|
||||
aPreferencesRootState(userStatusState = null),
|
||||
// Location sharing not available
|
||||
aPreferencesRootState(showLocationSettings = false),
|
||||
// Other accounts with unread notifications
|
||||
aPreferencesRootState(
|
||||
isMultiAccountEnabled = true,
|
||||
isOtherAccountsSectionExpanded = true,
|
||||
otherSessions = aMatrixUserList().drop(1).take(3).zip(listOf(3, 0, 120)) { user, unreadCount ->
|
||||
anOtherAccount(user = user, unreadCount = unreadCount)
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -66,7 +74,7 @@ fun aPreferencesRootState(
|
||||
isOtherAccountsSectionExpanded: Boolean = false,
|
||||
theme: ThemeOption = ThemeOption.System,
|
||||
availableThemeOptions: List<ThemeOption> = ThemeOption.entries,
|
||||
otherSessions: List<MatrixUser> = emptyList(),
|
||||
otherSessions: List<OtherAccount> = emptyList(),
|
||||
showAnalyticsSettings: Boolean = false,
|
||||
showDeveloperSettings: Boolean = false,
|
||||
showLabsItem: Boolean = false,
|
||||
@@ -91,3 +99,11 @@ fun aPreferencesRootState(
|
||||
snackbarMessage = snackbarMessage,
|
||||
eventSink = eventSink,
|
||||
)
|
||||
|
||||
fun anOtherAccount(
|
||||
user: MatrixUser = aMatrixUser(),
|
||||
unreadCount: Int = 0,
|
||||
) = OtherAccount(
|
||||
user = user,
|
||||
unreadCount = unreadCount,
|
||||
)
|
||||
|
||||
+10
-4
@@ -43,6 +43,7 @@ import io.element.android.features.preferences.impl.userstatus.UserStatusState
|
||||
import io.element.android.features.preferences.impl.userstatus.UserStatusView
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.architecture.coverage.ExcludeFromCoverage
|
||||
import io.element.android.libraries.designsystem.atomic.atoms.CounterAtom
|
||||
import io.element.android.libraries.designsystem.components.async.AsyncActionIndicator
|
||||
import io.element.android.libraries.designsystem.components.async.AsyncIndicator
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarRow
|
||||
@@ -244,7 +245,7 @@ private fun ColumnScope.MultiAccountSection(
|
||||
AvatarRow(
|
||||
avatarDataList = state.otherSessions
|
||||
.take(3)
|
||||
.map { it.getAvatarData(AvatarSize.OtherAccountItem) }
|
||||
.map { it.user.getAvatarData(AvatarSize.OtherAccountItem) }
|
||||
.toImmutableList(),
|
||||
avatarType = AvatarType.User,
|
||||
lastOnTop = true,
|
||||
@@ -275,16 +276,21 @@ private fun ColumnScope.MultiAccountSection(
|
||||
thickness = 1.dp,
|
||||
color = ElementTheme.colors.bgSubtleSecondary,
|
||||
)
|
||||
state.otherSessions.forEach { matrixUser ->
|
||||
state.otherSessions.forEach { otherAccount ->
|
||||
MatrixUserRow(
|
||||
modifier = Modifier
|
||||
.clickable {
|
||||
onSwitchToSession(matrixUser.userId)
|
||||
onSwitchToSession(otherAccount.user.userId)
|
||||
}
|
||||
.padding(top = 2.dp, bottom = 2.dp, end = 8.dp),
|
||||
matrixUser = matrixUser,
|
||||
matrixUser = otherAccount.user,
|
||||
avatarSize = AvatarSize.AccountItem,
|
||||
verticalSpaceWidth = 16.dp,
|
||||
trailingContent = if (otherAccount.unreadCount > 0) {
|
||||
{ CounterAtom(count = otherAccount.unreadCount) }
|
||||
} else {
|
||||
null
|
||||
},
|
||||
)
|
||||
}
|
||||
AddAccountItem(onAddAccountClick)
|
||||
|
||||
+58
-5
@@ -24,13 +24,18 @@ import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||
import io.element.android.libraries.featureflag.test.FakeFeature
|
||||
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
|
||||
import io.element.android.libraries.matrix.api.MatrixClientProvider
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
import io.element.android.libraries.matrix.test.AN_AVATAR_URL
|
||||
import io.element.android.libraries.matrix.test.AN_EXCEPTION
|
||||
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
||||
import io.element.android.libraries.matrix.test.A_SESSION_ID_2
|
||||
import io.element.android.libraries.matrix.test.A_USER_ID_3
|
||||
import io.element.android.libraries.matrix.test.A_USER_NAME
|
||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||
import io.element.android.libraries.matrix.test.FakeMatrixClientProvider
|
||||
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
||||
import io.element.android.libraries.matrix.test.sync.FakeSyncService
|
||||
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
|
||||
import io.element.android.libraries.preferences.test.InMemoryAppPreferencesStore
|
||||
import io.element.android.libraries.sessionstorage.api.SessionStore
|
||||
@@ -236,16 +241,62 @@ class PreferencesRootPresenterTest {
|
||||
assertThat(state.isMultiAccountEnabled).isTrue()
|
||||
assertThat(state.otherSessions).hasSize(1)
|
||||
assertThat(state.otherSessions[0]).isEqualTo(
|
||||
MatrixUser(
|
||||
userId = A_SESSION_ID_2,
|
||||
displayName = "Bob",
|
||||
avatarUrl = "avatarUrl",
|
||||
avatarThumbnail = "avatarData",
|
||||
OtherAccount(
|
||||
user = MatrixUser(
|
||||
userId = A_SESSION_ID_2,
|
||||
displayName = "Bob",
|
||||
avatarUrl = "avatarUrl",
|
||||
avatarThumbnail = "avatarData",
|
||||
),
|
||||
unreadCount = 0,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - other accounts keep the session store order and get their unread count`() = runTest {
|
||||
var bobUnreadCount = 3L
|
||||
val bobSyncService = FakeSyncService()
|
||||
val bobClient = FakeMatrixClient(
|
||||
sessionId = A_SESSION_ID_2,
|
||||
syncService = bobSyncService,
|
||||
totalUnreadCountResult = { Result.success(bobUnreadCount) },
|
||||
)
|
||||
createPreferencesRootPresenter(
|
||||
matrixClient = FakeMatrixClient(sessionId = A_SESSION_ID),
|
||||
sessionStore = InMemorySessionStore(
|
||||
initialList = listOf(
|
||||
aSessionData(sessionId = A_USER_ID_3.value),
|
||||
aSessionData(sessionId = A_SESSION_ID.value),
|
||||
aSessionData(sessionId = A_SESSION_ID_2.value),
|
||||
)
|
||||
),
|
||||
matrixClientProvider = FakeMatrixClientProvider { sessionId ->
|
||||
when (sessionId) {
|
||||
A_SESSION_ID_2 -> Result.success(bobClient)
|
||||
// Client for the third account is not in memory
|
||||
else -> Result.failure(AN_EXCEPTION)
|
||||
}
|
||||
},
|
||||
).test {
|
||||
val state = consumeItemsUntilPredicate { it.otherSessions.isNotEmpty() }.last()
|
||||
assertThat(state.otherSessions.map { it.user.userId to it.unreadCount }).containsExactly(
|
||||
A_USER_ID_3 to 0,
|
||||
A_SESSION_ID_2 to 3,
|
||||
).inOrder()
|
||||
// The unread count is refreshed on sync heartbeat
|
||||
bobUnreadCount = 5
|
||||
bobSyncService.emitHeartBeat()
|
||||
val updatedState = consumeItemsUntilPredicate { state -> state.otherSessions.any { it.unreadCount == 5 } }.last()
|
||||
assertThat(updatedState.otherSessions.map { it.user.userId to it.unreadCount }).containsExactly(
|
||||
A_USER_ID_3 to 0,
|
||||
A_SESSION_ID_2 to 5,
|
||||
).inOrder()
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - other accounts section is collapsed if the preference says so`() = runTest {
|
||||
createPreferencesRootPresenter(
|
||||
@@ -285,6 +336,7 @@ class PreferencesRootPresenterTest {
|
||||
sessionStore: SessionStore = InMemorySessionStore(),
|
||||
appPreferencesStore: AppPreferencesStore = InMemoryAppPreferencesStore(),
|
||||
locationService: LocationService = FakeLocationService(isServiceAvailable = true),
|
||||
matrixClientProvider: MatrixClientProvider = FakeMatrixClientProvider(),
|
||||
) = PreferencesRootPresenter(
|
||||
matrixClient = matrixClient,
|
||||
analyticsService = FakeAnalyticsService(),
|
||||
@@ -294,6 +346,7 @@ class PreferencesRootPresenterTest {
|
||||
featureFlagService = featureFlagService,
|
||||
locationService = locationService,
|
||||
sessionStore = sessionStore,
|
||||
matrixClientProvider = matrixClientProvider,
|
||||
appPreferencesStore = appPreferencesStore,
|
||||
userStatusPresenter = { aUserStatusState() },
|
||||
preferencesAccountPresenter = { aPreferencesAccountState() },
|
||||
|
||||
+42
-12
@@ -78,9 +78,11 @@ class PreferencesRootViewTest : RobolectricTest() {
|
||||
isMultiAccountEnabled = true,
|
||||
isOtherAccountsSectionExpanded = true,
|
||||
otherSessions = listOf(
|
||||
aMatrixUser(
|
||||
id = A_USER_ID_2.value,
|
||||
displayName = "Bob",
|
||||
anOtherAccount(
|
||||
user = aMatrixUser(
|
||||
id = A_USER_ID_2.value,
|
||||
displayName = "Bob",
|
||||
),
|
||||
)
|
||||
),
|
||||
eventSink = eventsRecorder,
|
||||
@@ -91,6 +93,28 @@ class PreferencesRootViewTest : RobolectricTest() {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `unread count of other session is displayed`() = runAndroidComposeUiTest {
|
||||
val eventsRecorder = EventsRecorder<PreferencesRootEvent>(expectEvents = false)
|
||||
setView(
|
||||
aPreferencesRootState(
|
||||
isMultiAccountEnabled = true,
|
||||
isOtherAccountsSectionExpanded = true,
|
||||
otherSessions = listOf(
|
||||
anOtherAccount(
|
||||
user = aMatrixUser(
|
||||
id = A_USER_ID_2.value,
|
||||
displayName = "Bob",
|
||||
),
|
||||
unreadCount = 42,
|
||||
)
|
||||
),
|
||||
eventSink = eventsRecorder,
|
||||
),
|
||||
)
|
||||
onNodeWithText("42").assertExists()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking on Switch accounts sends a ToggleOtherAccountsExpanded`() = runAndroidComposeUiTest {
|
||||
val eventsRecorder = EventsRecorder<PreferencesRootEvent>()
|
||||
@@ -99,9 +123,11 @@ class PreferencesRootViewTest : RobolectricTest() {
|
||||
isMultiAccountEnabled = true,
|
||||
isOtherAccountsSectionExpanded = false,
|
||||
otherSessions = listOf(
|
||||
aMatrixUser(
|
||||
id = A_USER_ID_2.value,
|
||||
displayName = "Bob",
|
||||
anOtherAccount(
|
||||
user = aMatrixUser(
|
||||
id = A_USER_ID_2.value,
|
||||
displayName = "Bob",
|
||||
),
|
||||
)
|
||||
),
|
||||
eventSink = eventsRecorder,
|
||||
@@ -119,9 +145,11 @@ class PreferencesRootViewTest : RobolectricTest() {
|
||||
isMultiAccountEnabled = true,
|
||||
isOtherAccountsSectionExpanded = false,
|
||||
otherSessions = listOf(
|
||||
aMatrixUser(
|
||||
id = A_USER_ID_2.value,
|
||||
displayName = "Bob",
|
||||
anOtherAccount(
|
||||
user = aMatrixUser(
|
||||
id = A_USER_ID_2.value,
|
||||
displayName = "Bob",
|
||||
),
|
||||
)
|
||||
),
|
||||
eventSink = eventsRecorder,
|
||||
@@ -141,9 +169,11 @@ class PreferencesRootViewTest : RobolectricTest() {
|
||||
isMultiAccountEnabled = true,
|
||||
isOtherAccountsSectionExpanded = true,
|
||||
otherSessions = listOf(
|
||||
aMatrixUser(
|
||||
id = A_USER_ID_2.value,
|
||||
displayName = "Bob",
|
||||
anOtherAccount(
|
||||
user = aMatrixUser(
|
||||
id = A_USER_ID_2.value,
|
||||
displayName = "Bob",
|
||||
),
|
||||
)
|
||||
),
|
||||
eventSink = eventsRecorder,
|
||||
|
||||
Reference in New Issue
Block a user