Show badges for new invites (#355)
Show badges for new invites Closes #238
This commit is contained in:
parent
c103ed549b
commit
ee909fcbd8
49 changed files with 744 additions and 153 deletions
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Copyright (c) 2023 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
|
||||
*
|
||||
* http://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.invitelist.impl
|
||||
|
||||
import android.content.Context
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.core.edit
|
||||
import androidx.datastore.preferences.core.stringSetPreferencesKey
|
||||
import androidx.datastore.preferences.preferencesDataStore
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.features.invitelist.api.SeenInvitesStore
|
||||
import io.element.android.libraries.di.ApplicationContext
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import javax.inject.Inject
|
||||
|
||||
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "elementx_seeninvites")
|
||||
private val seenInvitesKey = stringSetPreferencesKey("seenInvites")
|
||||
|
||||
|
||||
@ContributesBinding(SessionScope::class)
|
||||
class DefaultSeenInvitesStore @Inject constructor(
|
||||
@ApplicationContext context: Context
|
||||
) : SeenInvitesStore {
|
||||
|
||||
private val store = context.dataStore
|
||||
|
||||
override fun seenRoomIds(): Flow<Set<RoomId>> =
|
||||
store.data.map { prefs ->
|
||||
prefs[seenInvitesKey]
|
||||
.orEmpty()
|
||||
.map { RoomId(it) }
|
||||
.toSet()
|
||||
}
|
||||
|
||||
override suspend fun markAsSeen(roomIds: Set<RoomId>) {
|
||||
store.edit { prefs ->
|
||||
prefs[seenInvitesKey] = roomIds.map { it.value }.toSet()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -17,12 +17,15 @@
|
|||
package io.element.android.features.invitelist.impl
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import io.element.android.features.invitelist.api.SeenInvitesStore
|
||||
import io.element.android.features.invitelist.impl.model.InviteListInviteSummary
|
||||
import io.element.android.features.invitelist.impl.model.InviteSender
|
||||
import io.element.android.libraries.architecture.Async
|
||||
|
|
@ -34,11 +37,13 @@ import io.element.android.libraries.matrix.api.core.RoomId
|
|||
import io.element.android.libraries.matrix.api.room.RoomSummary
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
class InviteListPresenter @Inject constructor(
|
||||
private val client: MatrixClient,
|
||||
private val store: SeenInvitesStore,
|
||||
) : Presenter<InviteListState> {
|
||||
|
||||
@Composable
|
||||
|
|
@ -48,6 +53,21 @@ class InviteListPresenter @Inject constructor(
|
|||
.roomSummaries()
|
||||
.collectAsState()
|
||||
|
||||
var seenInvites by remember { mutableStateOf<Set<RoomId>>(emptySet()) }
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
seenInvites = store.seenRoomIds().first()
|
||||
}
|
||||
|
||||
LaunchedEffect(invites) {
|
||||
store.markAsSeen(
|
||||
invites
|
||||
.filterIsInstance<RoomSummary.Filled>()
|
||||
.map { it.details.roomId }
|
||||
.toSet()
|
||||
)
|
||||
}
|
||||
|
||||
val localCoroutineScope = rememberCoroutineScope()
|
||||
val acceptedAction: MutableState<Async<RoomId>> = remember { mutableStateOf(Async.Uninitialized) }
|
||||
val declinedAction: MutableState<Async<Unit>> = remember { mutableStateOf(Async.Uninitialized) }
|
||||
|
|
@ -86,8 +106,17 @@ class InviteListPresenter @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
val inviteList = remember(seenInvites, invites) {
|
||||
invites
|
||||
.filterIsInstance<RoomSummary.Filled>()
|
||||
.map {
|
||||
it.toInviteSummary(seenInvites.contains(it.details.roomId))
|
||||
}
|
||||
.toPersistentList()
|
||||
}
|
||||
|
||||
return InviteListState(
|
||||
inviteList = invites.mapNotNull(::toInviteSummary).toPersistentList(),
|
||||
inviteList = inviteList,
|
||||
declineConfirmationDialog = decliningInvite.value?.let {
|
||||
InviteDeclineConfirmationDialog.Visible(
|
||||
isDirect = it.isDirect,
|
||||
|
|
@ -113,49 +142,44 @@ class InviteListPresenter @Inject constructor(
|
|||
}.execute(declinedAction)
|
||||
}
|
||||
|
||||
private fun toInviteSummary(roomSummary: RoomSummary): InviteListInviteSummary? {
|
||||
return when (roomSummary) {
|
||||
is RoomSummary.Filled -> roomSummary.details.run {
|
||||
val i = inviter
|
||||
val avatarData = if (isDirect && i != null)
|
||||
AvatarData(
|
||||
id = i.userId.value,
|
||||
name = i.displayName,
|
||||
url = i.avatarUrl,
|
||||
)
|
||||
else
|
||||
AvatarData(
|
||||
id = roomId.value,
|
||||
name = name,
|
||||
url = avatarURLString
|
||||
)
|
||||
private fun RoomSummary.Filled.toInviteSummary(seen: Boolean) = details.run {
|
||||
val i = inviter
|
||||
val avatarData = if (isDirect && i != null)
|
||||
AvatarData(
|
||||
id = i.userId.value,
|
||||
name = i.displayName,
|
||||
url = i.avatarUrl,
|
||||
)
|
||||
else
|
||||
AvatarData(
|
||||
id = roomId.value,
|
||||
name = name,
|
||||
url = avatarURLString
|
||||
)
|
||||
|
||||
val alias = if (isDirect)
|
||||
inviter?.userId?.value
|
||||
else
|
||||
canonicalAlias
|
||||
val alias = if (isDirect)
|
||||
inviter?.userId?.value
|
||||
else
|
||||
canonicalAlias
|
||||
|
||||
InviteListInviteSummary(
|
||||
roomId = roomId,
|
||||
roomName = name,
|
||||
roomAlias = alias,
|
||||
roomAvatarData = avatarData,
|
||||
isDirect = isDirect,
|
||||
sender = if (isDirect) null else inviter?.let {
|
||||
InviteSender(
|
||||
userId = it.userId,
|
||||
displayName = it.displayName ?: "",
|
||||
avatarData = AvatarData(
|
||||
id = it.userId.value,
|
||||
name = it.displayName,
|
||||
url = it.avatarUrl,
|
||||
),
|
||||
)
|
||||
},
|
||||
InviteListInviteSummary(
|
||||
roomId = roomId,
|
||||
roomName = name,
|
||||
roomAlias = alias,
|
||||
roomAvatarData = avatarData,
|
||||
isDirect = isDirect,
|
||||
isNew = !seen,
|
||||
sender = if (isDirect) null else inviter?.run {
|
||||
InviteSender(
|
||||
userId = userId,
|
||||
displayName = displayName ?: "",
|
||||
avatarData = AvatarData(
|
||||
id = userId.value,
|
||||
name = displayName,
|
||||
url = avatarUrl,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
else -> null
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
package io.element.android.features.invitelist.impl.components
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.IntrinsicSize
|
||||
|
|
@ -27,13 +28,17 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
|||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.text.InlineTextContent
|
||||
import androidx.compose.foundation.text.appendInlineContent
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.Placeholder
|
||||
|
|
@ -59,6 +64,7 @@ import io.element.android.libraries.designsystem.preview.ElementPreviewLight
|
|||
import io.element.android.libraries.designsystem.theme.components.Button
|
||||
import io.element.android.libraries.designsystem.theme.components.OutlinedButton
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.designsystem.theme.roomListUnreadIndicator
|
||||
import kotlinx.collections.immutable.persistentMapOf
|
||||
import io.element.android.libraries.ui.strings.R as StringR
|
||||
|
||||
|
|
@ -73,8 +79,8 @@ internal fun InviteSummaryRow(
|
|||
) {
|
||||
Box(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(min = minHeight)
|
||||
.fillMaxWidth()
|
||||
.heightIn(min = minHeight)
|
||||
) {
|
||||
DefaultInviteSummaryRow(
|
||||
invite = invite,
|
||||
|
|
@ -92,19 +98,20 @@ internal fun DefaultInviteSummaryRow(
|
|||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp, vertical = 12.dp)
|
||||
.height(IntrinsicSize.Min),
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp, vertical = 12.dp)
|
||||
.height(IntrinsicSize.Min),
|
||||
verticalAlignment = Alignment.Top
|
||||
) {
|
||||
Avatar(
|
||||
invite.roomAvatarData,
|
||||
)
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(start = 12.dp, end = 4.dp)
|
||||
.alignByBaseline()
|
||||
.weight(1f)
|
||||
.padding(start = 12.dp, end = 4.dp)
|
||||
.alignByBaseline()
|
||||
.weight(1f)
|
||||
) {
|
||||
// Name
|
||||
Text(
|
||||
|
|
@ -152,6 +159,15 @@ internal fun DefaultInviteSummaryRow(
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
val unreadIndicatorColor = if (invite.isNew) MaterialTheme.roomListUnreadIndicator() else Color.Transparent
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(12.dp)
|
||||
.clip(CircleShape)
|
||||
.background(unreadIndicatorColor)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -183,7 +199,11 @@ private fun SenderRow(sender: InviteSender) {
|
|||
Placeholder(20.dp.toSp(), 20.dp.toSp(), PlaceholderVerticalAlign.Center)
|
||||
}
|
||||
) {
|
||||
Box(Modifier.fillMaxHeight().padding(end = 4.dp)) {
|
||||
Box(
|
||||
Modifier
|
||||
.fillMaxHeight()
|
||||
.padding(end = 4.dp)
|
||||
) {
|
||||
Avatar(
|
||||
avatarData = sender.avatarData.copy(size = AvatarSize.Custom(16.dp)),
|
||||
modifier = Modifier.align(Alignment.Center)
|
||||
|
|
|
|||
|
|
@ -28,7 +28,8 @@ data class InviteListInviteSummary(
|
|||
val roomAlias: String? = null,
|
||||
val roomAvatarData: AvatarData = AvatarData(roomId.value, roomName),
|
||||
val sender: InviteSender? = null,
|
||||
val isDirect: Boolean = false
|
||||
val isDirect: Boolean = false,
|
||||
val isNew: Boolean = false,
|
||||
)
|
||||
|
||||
data class InviteSender(
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ open class InviteListInviteSummaryProvider : PreviewParameterProvider<InviteList
|
|||
aInviteListInviteSummary(),
|
||||
aInviteListInviteSummary().copy(roomAlias = "#someroom:example.com"),
|
||||
aInviteListInviteSummary().copy(roomName = "Alice", sender = null),
|
||||
aInviteListInviteSummary().copy(isNew = true)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import app.cash.molecule.RecompositionClock
|
|||
import app.cash.molecule.moleculeFlow
|
||||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth
|
||||
import io.element.android.features.invitelist.test.FakeSeenInvitesStore
|
||||
import io.element.android.libraries.architecture.Async
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
|
|
@ -29,6 +30,7 @@ import io.element.android.libraries.matrix.api.room.RoomSummary
|
|||
import io.element.android.libraries.matrix.api.room.RoomSummaryDetails
|
||||
import io.element.android.libraries.matrix.test.AN_AVATAR_URL
|
||||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||
import io.element.android.libraries.matrix.test.A_ROOM_ID_2
|
||||
import io.element.android.libraries.matrix.test.A_ROOM_NAME
|
||||
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
||||
import io.element.android.libraries.matrix.test.A_USER_ID
|
||||
|
|
@ -50,7 +52,8 @@ class InviteListPresenterTests {
|
|||
FakeMatrixClient(
|
||||
sessionId = A_SESSION_ID,
|
||||
invitesDataSource = invitesDataSource,
|
||||
)
|
||||
),
|
||||
FakeSeenInvitesStore(),
|
||||
)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
presenter.present()
|
||||
|
|
@ -58,21 +61,7 @@ class InviteListPresenterTests {
|
|||
val initialState = awaitItem()
|
||||
Truth.assertThat(initialState.inviteList).isEmpty()
|
||||
|
||||
invitesDataSource.postRoomSummary(
|
||||
listOf(
|
||||
RoomSummary.Filled(
|
||||
RoomSummaryDetails(
|
||||
roomId = A_ROOM_ID,
|
||||
name = A_ROOM_NAME,
|
||||
avatarURLString = null,
|
||||
isDirect = false,
|
||||
lastMessage = null,
|
||||
lastMessageTimestamp = null,
|
||||
unreadNotificationCount = 0,
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
invitesDataSource.postRoomSummary(listOf(aRoomSummary()))
|
||||
|
||||
val withInviteState = awaitItem()
|
||||
Truth.assertThat(withInviteState.inviteList.size).isEqualTo(1)
|
||||
|
|
@ -88,7 +77,8 @@ class InviteListPresenterTests {
|
|||
FakeMatrixClient(
|
||||
sessionId = A_SESSION_ID,
|
||||
invitesDataSource = invitesDataSource,
|
||||
)
|
||||
),
|
||||
FakeSeenInvitesStore(),
|
||||
)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
presenter.present()
|
||||
|
|
@ -117,7 +107,8 @@ class InviteListPresenterTests {
|
|||
FakeMatrixClient(
|
||||
sessionId = A_SESSION_ID,
|
||||
invitesDataSource = invitesDataSource,
|
||||
)
|
||||
),
|
||||
FakeSeenInvitesStore(),
|
||||
)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
presenter.present()
|
||||
|
|
@ -144,7 +135,8 @@ class InviteListPresenterTests {
|
|||
FakeMatrixClient(
|
||||
sessionId = A_SESSION_ID,
|
||||
invitesDataSource = invitesDataSource,
|
||||
)
|
||||
),
|
||||
FakeSeenInvitesStore(),
|
||||
)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
presenter.present()
|
||||
|
|
@ -169,7 +161,8 @@ class InviteListPresenterTests {
|
|||
FakeMatrixClient(
|
||||
sessionId = A_SESSION_ID,
|
||||
invitesDataSource = invitesDataSource,
|
||||
)
|
||||
),
|
||||
FakeSeenInvitesStore(),
|
||||
)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
presenter.present()
|
||||
|
|
@ -194,7 +187,8 @@ class InviteListPresenterTests {
|
|||
FakeMatrixClient(
|
||||
sessionId = A_SESSION_ID,
|
||||
invitesDataSource = invitesDataSource,
|
||||
)
|
||||
),
|
||||
FakeSeenInvitesStore(),
|
||||
)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
presenter.present()
|
||||
|
|
@ -219,7 +213,7 @@ class InviteListPresenterTests {
|
|||
invitesDataSource = invitesDataSource,
|
||||
)
|
||||
val room = FakeMatrixRoom()
|
||||
val presenter = InviteListPresenter(client)
|
||||
val presenter = InviteListPresenter(client, FakeSeenInvitesStore())
|
||||
client.givenGetRoomResult(A_ROOM_ID, room)
|
||||
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
|
|
@ -246,7 +240,7 @@ class InviteListPresenterTests {
|
|||
invitesDataSource = invitesDataSource,
|
||||
)
|
||||
val room = FakeMatrixRoom()
|
||||
val presenter = InviteListPresenter(client)
|
||||
val presenter = InviteListPresenter(client, FakeSeenInvitesStore())
|
||||
val ex = Throwable("Ruh roh!")
|
||||
room.givenRejectInviteResult(Result.failure(ex))
|
||||
client.givenGetRoomResult(A_ROOM_ID, room)
|
||||
|
|
@ -278,7 +272,7 @@ class InviteListPresenterTests {
|
|||
invitesDataSource = invitesDataSource,
|
||||
)
|
||||
val room = FakeMatrixRoom()
|
||||
val presenter = InviteListPresenter(client)
|
||||
val presenter = InviteListPresenter(client, FakeSeenInvitesStore())
|
||||
val ex = Throwable("Ruh roh!")
|
||||
room.givenRejectInviteResult(Result.failure(ex))
|
||||
client.givenGetRoomResult(A_ROOM_ID, room)
|
||||
|
|
@ -311,7 +305,7 @@ class InviteListPresenterTests {
|
|||
invitesDataSource = invitesDataSource,
|
||||
)
|
||||
val room = FakeMatrixRoom()
|
||||
val presenter = InviteListPresenter(client)
|
||||
val presenter = InviteListPresenter(client, FakeSeenInvitesStore())
|
||||
client.givenGetRoomResult(A_ROOM_ID, room)
|
||||
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
|
|
@ -335,7 +329,7 @@ class InviteListPresenterTests {
|
|||
invitesDataSource = invitesDataSource,
|
||||
)
|
||||
val room = FakeMatrixRoom()
|
||||
val presenter = InviteListPresenter(client)
|
||||
val presenter = InviteListPresenter(client, FakeSeenInvitesStore())
|
||||
val ex = Throwable("Ruh roh!")
|
||||
room.givenAcceptInviteResult(Result.failure(ex))
|
||||
client.givenGetRoomResult(A_ROOM_ID, room)
|
||||
|
|
@ -361,7 +355,7 @@ class InviteListPresenterTests {
|
|||
invitesDataSource = invitesDataSource,
|
||||
)
|
||||
val room = FakeMatrixRoom()
|
||||
val presenter = InviteListPresenter(client)
|
||||
val presenter = InviteListPresenter(client, FakeSeenInvitesStore())
|
||||
val ex = Throwable("Ruh roh!")
|
||||
room.givenAcceptInviteResult(Result.failure(ex))
|
||||
client.givenGetRoomResult(A_ROOM_ID, room)
|
||||
|
|
@ -381,6 +375,71 @@ class InviteListPresenterTests {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - stores seen invites when received`() = runTest {
|
||||
val invitesDataSource = FakeRoomSummaryDataSource()
|
||||
val store = FakeSeenInvitesStore()
|
||||
val presenter = InviteListPresenter(
|
||||
FakeMatrixClient(
|
||||
sessionId = A_SESSION_ID,
|
||||
invitesDataSource = invitesDataSource,
|
||||
),
|
||||
store,
|
||||
)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
awaitItem()
|
||||
|
||||
// When one invite is received, that ID is saved
|
||||
invitesDataSource.postRoomSummary(listOf(aRoomSummary()))
|
||||
|
||||
awaitItem()
|
||||
Truth.assertThat(store.getProvidedRoomIds()).isEqualTo(setOf(A_ROOM_ID))
|
||||
|
||||
// When a second is added, both are saved
|
||||
invitesDataSource.postRoomSummary(listOf(aRoomSummary(), aRoomSummary(A_ROOM_ID_2)))
|
||||
|
||||
awaitItem()
|
||||
Truth.assertThat(store.getProvidedRoomIds()).isEqualTo(setOf(A_ROOM_ID, A_ROOM_ID_2))
|
||||
|
||||
// When they're both dismissed, an empty set is saved
|
||||
invitesDataSource.postRoomSummary(listOf())
|
||||
|
||||
awaitItem()
|
||||
Truth.assertThat(store.getProvidedRoomIds()).isEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - marks invite as new if they're unseen`() = runTest {
|
||||
val invitesDataSource = FakeRoomSummaryDataSource()
|
||||
val store = FakeSeenInvitesStore()
|
||||
store.publishRoomIds(setOf(A_ROOM_ID))
|
||||
val presenter = InviteListPresenter(
|
||||
FakeMatrixClient(
|
||||
sessionId = A_SESSION_ID,
|
||||
invitesDataSource = invitesDataSource,
|
||||
),
|
||||
store,
|
||||
)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
awaitItem()
|
||||
|
||||
invitesDataSource.postRoomSummary(listOf(aRoomSummary(), aRoomSummary(A_ROOM_ID_2)))
|
||||
skipItems(1)
|
||||
|
||||
val withInviteState = awaitItem()
|
||||
Truth.assertThat(withInviteState.inviteList.size).isEqualTo(2)
|
||||
Truth.assertThat(withInviteState.inviteList[0].roomId).isEqualTo(A_ROOM_ID)
|
||||
Truth.assertThat(withInviteState.inviteList[0].isNew).isFalse()
|
||||
Truth.assertThat(withInviteState.inviteList[1].roomId).isEqualTo(A_ROOM_ID_2)
|
||||
Truth.assertThat(withInviteState.inviteList[1].isNew).isTrue()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun FakeRoomSummaryDataSource.withRoomInvitation(): FakeRoomSummaryDataSource {
|
||||
postRoomSummary(
|
||||
listOf(
|
||||
|
|
@ -438,4 +497,17 @@ class InviteListPresenterTests {
|
|||
)
|
||||
return this
|
||||
}
|
||||
|
||||
private fun aRoomSummary(id: RoomId = A_ROOM_ID) = RoomSummary.Filled(
|
||||
RoomSummaryDetails(
|
||||
roomId = id,
|
||||
name = A_ROOM_NAME,
|
||||
avatarURLString = null,
|
||||
isDirect = false,
|
||||
lastMessage = null,
|
||||
lastMessageTimestamp = null,
|
||||
unreadNotificationCount = 0,
|
||||
)
|
||||
)
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue