Remove the green badge on a pending invite after a first preview (#4532)
* Remove condition on displayType as I believe, that it has no effect. * Remove the green badge on a pending invite after a first preview * Update screenshots * Fix test * Improve DefaultSeenInvitesStore, clear it on logout, and on clear cache. Also create a store per session. * Remember the returned flow. --------- Co-authored-by: ElementBot <android@element.io>
This commit is contained in:
parent
77a7c0b2e5
commit
ef8eeb804e
26 changed files with 326 additions and 29 deletions
|
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2025 New Vector 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.invite.api
|
||||||
|
|
||||||
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
interface SeenInvitesStore {
|
||||||
|
/**
|
||||||
|
* Returns a flow of seen room IDs of invitation.
|
||||||
|
*/
|
||||||
|
fun seenRoomIds(): Flow<Set<RoomId>>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark the invitation as seen.
|
||||||
|
* Call this when the invitation details are shown to the user.
|
||||||
|
* @param roomId the room ID of the invitation to mark as seen.
|
||||||
|
*/
|
||||||
|
suspend fun markAsSeen(roomId: RoomId)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark the invitation as unseen.
|
||||||
|
* Call this when the invitation has been accepted or declined.
|
||||||
|
* @param roomId the room ID of the invitation to mark as unseen.
|
||||||
|
*/
|
||||||
|
suspend fun markAsUnSeen(roomId: RoomId)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete the store.
|
||||||
|
*/
|
||||||
|
suspend fun clear()
|
||||||
|
}
|
||||||
|
|
@ -21,6 +21,7 @@ setupAnvil()
|
||||||
dependencies {
|
dependencies {
|
||||||
api(projects.features.invite.api)
|
api(projects.features.invite.api)
|
||||||
implementation(libs.androidx.datastore.preferences)
|
implementation(libs.androidx.datastore.preferences)
|
||||||
|
implementation(projects.libraries.androidutils)
|
||||||
implementation(projects.libraries.core)
|
implementation(projects.libraries.core)
|
||||||
implementation(projects.libraries.architecture)
|
implementation(projects.libraries.architecture)
|
||||||
implementation(projects.libraries.matrix.api)
|
implementation(projects.libraries.matrix.api)
|
||||||
|
|
@ -35,6 +36,7 @@ dependencies {
|
||||||
testImplementation(libs.molecule.runtime)
|
testImplementation(libs.molecule.runtime)
|
||||||
testImplementation(libs.test.truth)
|
testImplementation(libs.test.truth)
|
||||||
testImplementation(libs.test.turbine)
|
testImplementation(libs.test.turbine)
|
||||||
|
testImplementation(projects.features.invite.test)
|
||||||
testImplementation(projects.libraries.matrix.test)
|
testImplementation(projects.libraries.matrix.test)
|
||||||
testImplementation(projects.libraries.push.test)
|
testImplementation(projects.libraries.push.test)
|
||||||
testImplementation(projects.services.analytics.test)
|
testImplementation(projects.services.analytics.test)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,90 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2025 New Vector 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.invite.impl
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.datastore.preferences.core.PreferenceDataStoreFactory
|
||||||
|
import androidx.datastore.preferences.core.edit
|
||||||
|
import androidx.datastore.preferences.core.stringSetPreferencesKey
|
||||||
|
import androidx.datastore.preferences.preferencesDataStoreFile
|
||||||
|
import com.squareup.anvil.annotations.ContributesBinding
|
||||||
|
import io.element.android.features.invite.api.SeenInvitesStore
|
||||||
|
import io.element.android.libraries.androidutils.file.safeDelete
|
||||||
|
import io.element.android.libraries.androidutils.hash.hash
|
||||||
|
import io.element.android.libraries.di.ApplicationContext
|
||||||
|
import io.element.android.libraries.di.SessionScope
|
||||||
|
import io.element.android.libraries.di.SingleIn
|
||||||
|
import io.element.android.libraries.di.annotations.SessionCoroutineScope
|
||||||
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
|
import io.element.android.libraries.matrix.api.core.SessionId
|
||||||
|
import io.element.android.libraries.matrix.api.user.CurrentSessionIdHolder
|
||||||
|
import io.element.android.libraries.sessionstorage.api.observer.SessionListener
|
||||||
|
import io.element.android.libraries.sessionstorage.api.observer.SessionObserver
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
private val seenInvitesKey = stringSetPreferencesKey("seenInvites")
|
||||||
|
|
||||||
|
@SingleIn(SessionScope::class)
|
||||||
|
@ContributesBinding(SessionScope::class)
|
||||||
|
class DefaultSeenInvitesStore @Inject constructor(
|
||||||
|
@ApplicationContext context: Context,
|
||||||
|
currentSessionIdHolder: CurrentSessionIdHolder,
|
||||||
|
@SessionCoroutineScope sessionCoroutineScope: CoroutineScope,
|
||||||
|
sessionObserver: SessionObserver,
|
||||||
|
) : SeenInvitesStore {
|
||||||
|
private val sessionId: SessionId = currentSessionIdHolder.current
|
||||||
|
|
||||||
|
init {
|
||||||
|
sessionObserver.addListener(object : SessionListener {
|
||||||
|
override suspend fun onSessionCreated(userId: String) = Unit
|
||||||
|
override suspend fun onSessionDeleted(userId: String) {
|
||||||
|
if (sessionId.value == userId) {
|
||||||
|
clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private val dataStoreFile = sessionId.value.hash().take(16).let { hashedUserId ->
|
||||||
|
context.preferencesDataStoreFile("session_${hashedUserId}_seen-invites")
|
||||||
|
}
|
||||||
|
|
||||||
|
private val store = PreferenceDataStoreFactory.create(
|
||||||
|
scope = sessionCoroutineScope,
|
||||||
|
migrations = emptyList(),
|
||||||
|
) {
|
||||||
|
dataStoreFile
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun seenRoomIds(): Flow<Set<RoomId>> =
|
||||||
|
store.data.map { prefs ->
|
||||||
|
prefs[seenInvitesKey]
|
||||||
|
.orEmpty()
|
||||||
|
.map { RoomId(it) }
|
||||||
|
.toSet()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun markAsSeen(roomId: RoomId) {
|
||||||
|
store.edit { prefs ->
|
||||||
|
prefs[seenInvitesKey] = prefs[seenInvitesKey].orEmpty() + roomId.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun markAsUnSeen(roomId: RoomId) {
|
||||||
|
store.edit { prefs ->
|
||||||
|
prefs[seenInvitesKey] = prefs[seenInvitesKey].orEmpty() - roomId.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun clear() {
|
||||||
|
dataStoreFile.safeDelete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -13,6 +13,7 @@ import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import im.vector.app.features.analytics.plan.JoinedRoom
|
import im.vector.app.features.analytics.plan.JoinedRoom
|
||||||
|
import io.element.android.features.invite.api.SeenInvitesStore
|
||||||
import io.element.android.features.invite.api.response.AcceptDeclineInviteEvents
|
import io.element.android.features.invite.api.response.AcceptDeclineInviteEvents
|
||||||
import io.element.android.features.invite.api.response.AcceptDeclineInviteState
|
import io.element.android.features.invite.api.response.AcceptDeclineInviteState
|
||||||
import io.element.android.features.invite.api.response.ConfirmingDeclineInvite
|
import io.element.android.features.invite.api.response.ConfirmingDeclineInvite
|
||||||
|
|
@ -34,6 +35,7 @@ class AcceptDeclineInvitePresenter @Inject constructor(
|
||||||
private val client: MatrixClient,
|
private val client: MatrixClient,
|
||||||
private val joinRoom: JoinRoom,
|
private val joinRoom: JoinRoom,
|
||||||
private val notificationCleaner: NotificationCleaner,
|
private val notificationCleaner: NotificationCleaner,
|
||||||
|
private val seenInvitesStore: SeenInvitesStore,
|
||||||
) : Presenter<AcceptDeclineInviteState> {
|
) : Presenter<AcceptDeclineInviteState> {
|
||||||
@Composable
|
@Composable
|
||||||
override fun present(): AcceptDeclineInviteState {
|
override fun present(): AcceptDeclineInviteState {
|
||||||
|
|
@ -107,6 +109,7 @@ class AcceptDeclineInvitePresenter @Inject constructor(
|
||||||
)
|
)
|
||||||
.onSuccess {
|
.onSuccess {
|
||||||
notificationCleaner.clearMembershipNotificationForRoom(client.sessionId, roomId)
|
notificationCleaner.clearMembershipNotificationForRoom(client.sessionId, roomId)
|
||||||
|
seenInvitesStore.markAsUnSeen(roomId)
|
||||||
}
|
}
|
||||||
.map { roomId }
|
.map { roomId }
|
||||||
}
|
}
|
||||||
|
|
@ -125,6 +128,7 @@ class AcceptDeclineInvitePresenter @Inject constructor(
|
||||||
client.ignoreUser(inviteData.senderId).getOrThrow()
|
client.ignoreUser(inviteData.senderId).getOrThrow()
|
||||||
}
|
}
|
||||||
notificationCleaner.clearMembershipNotificationForRoom(client.sessionId, inviteData.roomId)
|
notificationCleaner.clearMembershipNotificationForRoom(client.sessionId, inviteData.roomId)
|
||||||
|
seenInvitesStore.markAsUnSeen(inviteData.roomId)
|
||||||
inviteData.roomId
|
inviteData.roomId
|
||||||
}.runCatchingUpdatingState(declinedAction)
|
}.runCatchingUpdatingState(declinedAction)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,11 @@ package io.element.android.features.invite.impl.response
|
||||||
|
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import im.vector.app.features.analytics.plan.JoinedRoom
|
import im.vector.app.features.analytics.plan.JoinedRoom
|
||||||
|
import io.element.android.features.invite.api.SeenInvitesStore
|
||||||
import io.element.android.features.invite.api.response.AcceptDeclineInviteEvents
|
import io.element.android.features.invite.api.response.AcceptDeclineInviteEvents
|
||||||
import io.element.android.features.invite.api.response.ConfirmingDeclineInvite
|
import io.element.android.features.invite.api.response.ConfirmingDeclineInvite
|
||||||
import io.element.android.features.invite.api.response.InviteData
|
import io.element.android.features.invite.api.response.InviteData
|
||||||
|
import io.element.android.features.invite.test.InMemorySeenInvitesStore
|
||||||
import io.element.android.libraries.architecture.AsyncAction
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.matrix.api.MatrixClient
|
import io.element.android.libraries.matrix.api.MatrixClient
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
|
|
@ -20,6 +22,8 @@ import io.element.android.libraries.matrix.api.core.SessionId
|
||||||
import io.element.android.libraries.matrix.api.core.UserId
|
import io.element.android.libraries.matrix.api.core.UserId
|
||||||
import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias
|
import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias
|
||||||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
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_ID_3
|
||||||
import io.element.android.libraries.matrix.test.A_ROOM_NAME
|
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_SESSION_ID
|
||||||
import io.element.android.libraries.matrix.test.A_USER_ID
|
import io.element.android.libraries.matrix.test.A_USER_ID
|
||||||
|
|
@ -33,6 +37,7 @@ import io.element.android.tests.testutils.lambda.assert
|
||||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||||
import io.element.android.tests.testutils.lambda.value
|
import io.element.android.tests.testutils.lambda.value
|
||||||
import io.element.android.tests.testutils.test
|
import io.element.android.tests.testutils.test
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
@ -54,7 +59,10 @@ class AcceptDeclineInvitePresenterTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - declining invite cancel flow`() = runTest {
|
fun `present - declining invite cancel flow`() = runTest {
|
||||||
val presenter = createAcceptDeclineInvitePresenter()
|
val seenInvitesStore = InMemorySeenInvitesStore(setOf(A_ROOM_ID, A_ROOM_ID_2, A_ROOM_ID_3))
|
||||||
|
val presenter = createAcceptDeclineInvitePresenter(
|
||||||
|
seenInvitesStore = seenInvitesStore,
|
||||||
|
)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
val inviteData = anInviteData()
|
val inviteData = anInviteData()
|
||||||
awaitItem().also { state ->
|
awaitItem().also { state ->
|
||||||
|
|
@ -72,6 +80,7 @@ class AcceptDeclineInvitePresenterTest {
|
||||||
assertThat(state.declineAction).isInstanceOf(AsyncAction.Uninitialized::class.java)
|
assertThat(state.declineAction).isInstanceOf(AsyncAction.Uninitialized::class.java)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
assertThat(seenInvitesStore.seenRoomIds().first()).containsExactly(A_ROOM_ID, A_ROOM_ID_2, A_ROOM_ID_3)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -84,7 +93,11 @@ class AcceptDeclineInvitePresenterTest {
|
||||||
Result.success(FakeRoomPreview(declineInviteResult = declineInviteFailure))
|
Result.success(FakeRoomPreview(declineInviteResult = declineInviteFailure))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
val presenter = createAcceptDeclineInvitePresenter(client = client)
|
val seenInvitesStore = InMemorySeenInvitesStore(setOf(A_ROOM_ID, A_ROOM_ID_2, A_ROOM_ID_3))
|
||||||
|
val presenter = createAcceptDeclineInvitePresenter(
|
||||||
|
client = client,
|
||||||
|
seenInvitesStore = seenInvitesStore,
|
||||||
|
)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
val inviteData = anInviteData()
|
val inviteData = anInviteData()
|
||||||
awaitItem().also { state ->
|
awaitItem().also { state ->
|
||||||
|
|
@ -111,6 +124,7 @@ class AcceptDeclineInvitePresenterTest {
|
||||||
cancelAndConsumeRemainingEvents()
|
cancelAndConsumeRemainingEvents()
|
||||||
}
|
}
|
||||||
assert(declineInviteFailure).isCalledOnce()
|
assert(declineInviteFailure).isCalledOnce()
|
||||||
|
assertThat(seenInvitesStore.seenRoomIds().first()).containsExactly(A_ROOM_ID, A_ROOM_ID_2, A_ROOM_ID_3)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -129,9 +143,11 @@ class AcceptDeclineInvitePresenterTest {
|
||||||
Result.success(FakeRoomPreview(declineInviteResult = declineInviteSuccess))
|
Result.success(FakeRoomPreview(declineInviteResult = declineInviteSuccess))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
val seenInvitesStore = InMemorySeenInvitesStore(setOf(A_ROOM_ID, A_ROOM_ID_2, A_ROOM_ID_3))
|
||||||
val presenter = createAcceptDeclineInvitePresenter(
|
val presenter = createAcceptDeclineInvitePresenter(
|
||||||
client = client,
|
client = client,
|
||||||
notificationCleaner = fakeNotificationCleaner,
|
notificationCleaner = fakeNotificationCleaner,
|
||||||
|
seenInvitesStore = seenInvitesStore,
|
||||||
)
|
)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
val inviteData = anInviteData()
|
val inviteData = anInviteData()
|
||||||
|
|
@ -156,6 +172,7 @@ class AcceptDeclineInvitePresenterTest {
|
||||||
clearMembershipNotificationForRoomLambda.assertions()
|
clearMembershipNotificationForRoomLambda.assertions()
|
||||||
.isCalledOnce()
|
.isCalledOnce()
|
||||||
.with(value(A_SESSION_ID), value(A_ROOM_ID))
|
.with(value(A_SESSION_ID), value(A_ROOM_ID))
|
||||||
|
assertThat(seenInvitesStore.seenRoomIds().first()).containsExactly(A_ROOM_ID_2, A_ROOM_ID_3)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -174,9 +191,11 @@ class AcceptDeclineInvitePresenterTest {
|
||||||
},
|
},
|
||||||
ignoreUserResult = ignoreUserSuccess
|
ignoreUserResult = ignoreUserSuccess
|
||||||
)
|
)
|
||||||
|
val seenInvitesStore = InMemorySeenInvitesStore(setOf(A_ROOM_ID, A_ROOM_ID_2, A_ROOM_ID_3))
|
||||||
val presenter = createAcceptDeclineInvitePresenter(
|
val presenter = createAcceptDeclineInvitePresenter(
|
||||||
client = client,
|
client = client,
|
||||||
notificationCleaner = fakeNotificationCleaner,
|
notificationCleaner = fakeNotificationCleaner,
|
||||||
|
seenInvitesStore = seenInvitesStore,
|
||||||
)
|
)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
val inviteData = anInviteData()
|
val inviteData = anInviteData()
|
||||||
|
|
@ -202,6 +221,7 @@ class AcceptDeclineInvitePresenterTest {
|
||||||
clearMembershipNotificationForRoomLambda.assertions()
|
clearMembershipNotificationForRoomLambda.assertions()
|
||||||
.isCalledOnce()
|
.isCalledOnce()
|
||||||
.with(value(A_SESSION_ID), value(A_ROOM_ID))
|
.with(value(A_SESSION_ID), value(A_ROOM_ID))
|
||||||
|
assertThat(seenInvitesStore.seenRoomIds().first()).containsExactly(A_ROOM_ID_2, A_ROOM_ID_3)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -214,7 +234,11 @@ class AcceptDeclineInvitePresenterTest {
|
||||||
Result.success(FakeRoomPreview(declineInviteResult = declineInviteFailure))
|
Result.success(FakeRoomPreview(declineInviteResult = declineInviteFailure))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
val presenter = createAcceptDeclineInvitePresenter(client = client)
|
val seenInvitesStore = InMemorySeenInvitesStore(setOf(A_ROOM_ID, A_ROOM_ID_2, A_ROOM_ID_3))
|
||||||
|
val presenter = createAcceptDeclineInvitePresenter(
|
||||||
|
client = client,
|
||||||
|
seenInvitesStore = seenInvitesStore,
|
||||||
|
)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
val inviteData = anInviteData()
|
val inviteData = anInviteData()
|
||||||
awaitItem().also { state ->
|
awaitItem().also { state ->
|
||||||
|
|
@ -230,6 +254,7 @@ class AcceptDeclineInvitePresenterTest {
|
||||||
}
|
}
|
||||||
assertThat(awaitItem().declineAction.isLoading()).isTrue()
|
assertThat(awaitItem().declineAction.isLoading()).isTrue()
|
||||||
}
|
}
|
||||||
|
assertThat(seenInvitesStore.seenRoomIds().first()).containsExactly(A_ROOM_ID, A_ROOM_ID_2, A_ROOM_ID_3)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -237,7 +262,11 @@ class AcceptDeclineInvitePresenterTest {
|
||||||
val joinRoomFailure = lambdaRecorder { roomIdOrAlias: RoomIdOrAlias, _: List<String>, _: JoinedRoom.Trigger ->
|
val joinRoomFailure = lambdaRecorder { roomIdOrAlias: RoomIdOrAlias, _: List<String>, _: JoinedRoom.Trigger ->
|
||||||
Result.failure<Unit>(RuntimeException("Failed to join room $roomIdOrAlias"))
|
Result.failure<Unit>(RuntimeException("Failed to join room $roomIdOrAlias"))
|
||||||
}
|
}
|
||||||
val presenter = createAcceptDeclineInvitePresenter(joinRoomLambda = joinRoomFailure)
|
val seenInvitesStore = InMemorySeenInvitesStore(setOf(A_ROOM_ID, A_ROOM_ID_2, A_ROOM_ID_3))
|
||||||
|
val presenter = createAcceptDeclineInvitePresenter(
|
||||||
|
joinRoomLambda = joinRoomFailure,
|
||||||
|
seenInvitesStore = seenInvitesStore,
|
||||||
|
)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
val inviteData = anInviteData()
|
val inviteData = anInviteData()
|
||||||
awaitItem().also { state ->
|
awaitItem().also { state ->
|
||||||
|
|
@ -266,6 +295,7 @@ class AcceptDeclineInvitePresenterTest {
|
||||||
value(emptyList<String>()),
|
value(emptyList<String>()),
|
||||||
value(JoinedRoom.Trigger.Invite)
|
value(JoinedRoom.Trigger.Invite)
|
||||||
)
|
)
|
||||||
|
assertThat(seenInvitesStore.seenRoomIds().first()).containsExactly(A_ROOM_ID, A_ROOM_ID_2, A_ROOM_ID_3)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -279,9 +309,11 @@ class AcceptDeclineInvitePresenterTest {
|
||||||
val joinRoomSuccess = lambdaRecorder { _: RoomIdOrAlias, _: List<String>, _: JoinedRoom.Trigger ->
|
val joinRoomSuccess = lambdaRecorder { _: RoomIdOrAlias, _: List<String>, _: JoinedRoom.Trigger ->
|
||||||
Result.success(Unit)
|
Result.success(Unit)
|
||||||
}
|
}
|
||||||
|
val seenInvitesStore = InMemorySeenInvitesStore(setOf(A_ROOM_ID, A_ROOM_ID_2, A_ROOM_ID_3))
|
||||||
val presenter = createAcceptDeclineInvitePresenter(
|
val presenter = createAcceptDeclineInvitePresenter(
|
||||||
joinRoomLambda = joinRoomSuccess,
|
joinRoomLambda = joinRoomSuccess,
|
||||||
notificationCleaner = fakeNotificationCleaner,
|
notificationCleaner = fakeNotificationCleaner,
|
||||||
|
seenInvitesStore = seenInvitesStore,
|
||||||
)
|
)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
val inviteData = anInviteData()
|
val inviteData = anInviteData()
|
||||||
|
|
@ -308,6 +340,7 @@ class AcceptDeclineInvitePresenterTest {
|
||||||
clearMembershipNotificationForRoomLambda.assertions()
|
clearMembershipNotificationForRoomLambda.assertions()
|
||||||
.isCalledOnce()
|
.isCalledOnce()
|
||||||
.with(value(A_SESSION_ID), value(A_ROOM_ID))
|
.with(value(A_SESSION_ID), value(A_ROOM_ID))
|
||||||
|
assertThat(seenInvitesStore.seenRoomIds().first()).containsExactly(A_ROOM_ID_2, A_ROOM_ID_3)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun anInviteData(
|
private fun anInviteData(
|
||||||
|
|
@ -330,11 +363,13 @@ class AcceptDeclineInvitePresenterTest {
|
||||||
Result.success(Unit)
|
Result.success(Unit)
|
||||||
},
|
},
|
||||||
notificationCleaner: NotificationCleaner = FakeNotificationCleaner(),
|
notificationCleaner: NotificationCleaner = FakeNotificationCleaner(),
|
||||||
|
seenInvitesStore: SeenInvitesStore = InMemorySeenInvitesStore(),
|
||||||
): AcceptDeclineInvitePresenter {
|
): AcceptDeclineInvitePresenter {
|
||||||
return AcceptDeclineInvitePresenter(
|
return AcceptDeclineInvitePresenter(
|
||||||
client = client,
|
client = client,
|
||||||
joinRoom = FakeJoinRoom(joinRoomLambda),
|
joinRoom = FakeJoinRoom(joinRoomLambda),
|
||||||
notificationCleaner = notificationCleaner,
|
notificationCleaner = notificationCleaner,
|
||||||
|
seenInvitesStore = seenInvitesStore,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
29
features/invite/test/build.gradle.kts
Normal file
29
features/invite/test/build.gradle.kts
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2025 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
id("io.element.android-library")
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = "io.element.android.features.invite.test"
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation(libs.coroutines.core)
|
||||||
|
implementation(projects.libraries.matrix.api)
|
||||||
|
api(projects.features.invite.api)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2025 New Vector 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.invite.test
|
||||||
|
|
||||||
|
import io.element.android.features.invite.api.SeenInvitesStore
|
||||||
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
|
||||||
|
class InMemorySeenInvitesStore(
|
||||||
|
initialRoomIds: Set<RoomId> = emptySet(),
|
||||||
|
) : SeenInvitesStore {
|
||||||
|
private val roomIds = MutableStateFlow(initialRoomIds)
|
||||||
|
|
||||||
|
override fun seenRoomIds(): Flow<Set<RoomId>> = roomIds
|
||||||
|
|
||||||
|
override suspend fun markAsSeen(roomId: RoomId) {
|
||||||
|
roomIds.value += roomId
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun markAsUnSeen(roomId: RoomId) {
|
||||||
|
roomIds.value -= roomId
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun clear() {
|
||||||
|
roomIds.value = emptySet()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -42,6 +42,7 @@ dependencies {
|
||||||
testImplementation(libs.test.robolectric)
|
testImplementation(libs.test.robolectric)
|
||||||
testImplementation(libs.test.truth)
|
testImplementation(libs.test.truth)
|
||||||
testImplementation(libs.test.turbine)
|
testImplementation(libs.test.turbine)
|
||||||
|
testImplementation(projects.features.invite.test)
|
||||||
testImplementation(projects.libraries.matrix.test)
|
testImplementation(projects.libraries.matrix.test)
|
||||||
testImplementation(projects.tests.testutils)
|
testImplementation(projects.tests.testutils)
|
||||||
testImplementation(libs.androidx.compose.ui.test.junit)
|
testImplementation(libs.androidx.compose.ui.test.junit)
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ package io.element.android.features.joinroom.impl
|
||||||
|
|
||||||
import androidx.annotation.VisibleForTesting
|
import androidx.annotation.VisibleForTesting
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.MutableState
|
import androidx.compose.runtime.MutableState
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
|
@ -22,6 +23,7 @@ import androidx.compose.runtime.setValue
|
||||||
import dagger.assisted.Assisted
|
import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedInject
|
import dagger.assisted.AssistedInject
|
||||||
import im.vector.app.features.analytics.plan.JoinedRoom
|
import im.vector.app.features.analytics.plan.JoinedRoom
|
||||||
|
import io.element.android.features.invite.api.SeenInvitesStore
|
||||||
import io.element.android.features.invite.api.response.AcceptDeclineInviteEvents
|
import io.element.android.features.invite.api.response.AcceptDeclineInviteEvents
|
||||||
import io.element.android.features.invite.api.response.AcceptDeclineInviteState
|
import io.element.android.features.invite.api.response.AcceptDeclineInviteState
|
||||||
import io.element.android.features.invite.api.response.InviteData
|
import io.element.android.features.invite.api.response.InviteData
|
||||||
|
|
@ -67,6 +69,7 @@ class JoinRoomPresenter @AssistedInject constructor(
|
||||||
private val forgetRoom: ForgetRoom,
|
private val forgetRoom: ForgetRoom,
|
||||||
private val acceptDeclineInvitePresenter: Presenter<AcceptDeclineInviteState>,
|
private val acceptDeclineInvitePresenter: Presenter<AcceptDeclineInviteState>,
|
||||||
private val buildMeta: BuildMeta,
|
private val buildMeta: BuildMeta,
|
||||||
|
private val seenInvitesStore: SeenInvitesStore,
|
||||||
) : Presenter<JoinRoomState> {
|
) : Presenter<JoinRoomState> {
|
||||||
interface Factory {
|
interface Factory {
|
||||||
fun create(
|
fun create(
|
||||||
|
|
@ -149,6 +152,10 @@ class JoinRoomPresenter @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
val acceptDeclineInviteState = acceptDeclineInvitePresenter.present()
|
val acceptDeclineInviteState = acceptDeclineInvitePresenter.present()
|
||||||
|
|
||||||
|
LaunchedEffect(contentState) {
|
||||||
|
contentState.markRoomInviteAsSeen()
|
||||||
|
}
|
||||||
|
|
||||||
fun handleEvents(event: JoinRoomEvents) {
|
fun handleEvents(event: JoinRoomEvents) {
|
||||||
when (event) {
|
when (event) {
|
||||||
JoinRoomEvents.JoinRoom -> coroutineScope.joinRoom(joinAction)
|
JoinRoomEvents.JoinRoom -> coroutineScope.joinRoom(joinAction)
|
||||||
|
|
@ -236,6 +243,12 @@ class JoinRoomPresenter @AssistedInject constructor(
|
||||||
forgetRoom.invoke(roomId)
|
forgetRoom.invoke(roomId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun ContentState.markRoomInviteAsSeen() {
|
||||||
|
if ((this as? ContentState.Loaded)?.joinAuthorisationStatus as? JoinAuthorisationStatus.IsInvited != null) {
|
||||||
|
seenInvitesStore.markAsSeen(roomId)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun RoomPreviewInfo.toContentState(senderMember: RoomMember?, reason: String?): ContentState {
|
private fun RoomPreviewInfo.toContentState(senderMember: RoomMember?, reason: String?): ContentState {
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import com.squareup.anvil.annotations.ContributesTo
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
import im.vector.app.features.analytics.plan.JoinedRoom
|
import im.vector.app.features.analytics.plan.JoinedRoom
|
||||||
|
import io.element.android.features.invite.api.SeenInvitesStore
|
||||||
import io.element.android.features.invite.api.response.AcceptDeclineInviteState
|
import io.element.android.features.invite.api.response.AcceptDeclineInviteState
|
||||||
import io.element.android.features.joinroom.impl.JoinRoomPresenter
|
import io.element.android.features.joinroom.impl.JoinRoomPresenter
|
||||||
import io.element.android.features.roomdirectory.api.RoomDescription
|
import io.element.android.features.roomdirectory.api.RoomDescription
|
||||||
|
|
@ -35,6 +36,7 @@ object JoinRoomModule {
|
||||||
forgetRoom: ForgetRoom,
|
forgetRoom: ForgetRoom,
|
||||||
acceptDeclineInvitePresenter: Presenter<AcceptDeclineInviteState>,
|
acceptDeclineInvitePresenter: Presenter<AcceptDeclineInviteState>,
|
||||||
buildMeta: BuildMeta,
|
buildMeta: BuildMeta,
|
||||||
|
seenInvitesStore: SeenInvitesStore,
|
||||||
): JoinRoomPresenter.Factory {
|
): JoinRoomPresenter.Factory {
|
||||||
return object : JoinRoomPresenter.Factory {
|
return object : JoinRoomPresenter.Factory {
|
||||||
override fun create(
|
override fun create(
|
||||||
|
|
@ -57,6 +59,7 @@ object JoinRoomModule {
|
||||||
cancelKnockRoom = cancelKnockRoom,
|
cancelKnockRoom = cancelKnockRoom,
|
||||||
acceptDeclineInvitePresenter = acceptDeclineInvitePresenter,
|
acceptDeclineInvitePresenter = acceptDeclineInvitePresenter,
|
||||||
buildMeta = buildMeta,
|
buildMeta = buildMeta,
|
||||||
|
seenInvitesStore = seenInvitesStore,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,11 @@ package io.element.android.features.joinroom.impl
|
||||||
|
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import im.vector.app.features.analytics.plan.JoinedRoom
|
import im.vector.app.features.analytics.plan.JoinedRoom
|
||||||
|
import io.element.android.features.invite.api.SeenInvitesStore
|
||||||
import io.element.android.features.invite.api.response.AcceptDeclineInviteEvents
|
import io.element.android.features.invite.api.response.AcceptDeclineInviteEvents
|
||||||
import io.element.android.features.invite.api.response.AcceptDeclineInviteState
|
import io.element.android.features.invite.api.response.AcceptDeclineInviteState
|
||||||
import io.element.android.features.invite.api.response.anAcceptDeclineInviteState
|
import io.element.android.features.invite.api.response.anAcceptDeclineInviteState
|
||||||
|
import io.element.android.features.invite.test.InMemorySeenInvitesStore
|
||||||
import io.element.android.features.joinroom.impl.di.CancelKnockRoom
|
import io.element.android.features.joinroom.impl.di.CancelKnockRoom
|
||||||
import io.element.android.features.joinroom.impl.di.ForgetRoom
|
import io.element.android.features.joinroom.impl.di.ForgetRoom
|
||||||
import io.element.android.features.joinroom.impl.di.KnockRoom
|
import io.element.android.features.joinroom.impl.di.KnockRoom
|
||||||
|
|
@ -52,6 +54,7 @@ import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||||
import io.element.android.tests.testutils.lambda.value
|
import io.element.android.tests.testutils.lambda.value
|
||||||
import io.element.android.tests.testutils.test
|
import io.element.android.tests.testutils.test
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.flow.flowOf
|
import kotlinx.coroutines.flow.flowOf
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
|
|
@ -111,14 +114,19 @@ class JoinRoomPresenterTest {
|
||||||
flowOf(Optional.of(roomSummary))
|
flowOf(Optional.of(roomSummary))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
val seenInvitesStore = InMemorySeenInvitesStore()
|
||||||
val presenter = createJoinRoomPresenter(
|
val presenter = createJoinRoomPresenter(
|
||||||
matrixClient = matrixClient
|
matrixClient = matrixClient,
|
||||||
|
seenInvitesStore = seenInvitesStore,
|
||||||
)
|
)
|
||||||
|
assertThat(seenInvitesStore.seenRoomIds().first()).isEmpty()
|
||||||
presenter.test {
|
presenter.test {
|
||||||
skipItems(1)
|
skipItems(1)
|
||||||
awaitItem().also { state ->
|
awaitItem().also { state ->
|
||||||
assertThat(state.joinAuthorisationStatus).isEqualTo(JoinAuthorisationStatus.IsInvited(null))
|
assertThat(state.joinAuthorisationStatus).isEqualTo(JoinAuthorisationStatus.IsInvited(null))
|
||||||
}
|
}
|
||||||
|
// Check that the roomId is stored in the seen invites store
|
||||||
|
assertThat(seenInvitesStore.seenRoomIds().first()).containsExactly(roomSummary.roomId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -759,7 +767,8 @@ class JoinRoomPresenterTest {
|
||||||
cancelKnockRoom: CancelKnockRoom = FakeCancelKnockRoom(),
|
cancelKnockRoom: CancelKnockRoom = FakeCancelKnockRoom(),
|
||||||
forgetRoom: ForgetRoom = FakeForgetRoom(),
|
forgetRoom: ForgetRoom = FakeForgetRoom(),
|
||||||
buildMeta: BuildMeta = aBuildMeta(applicationName = "AppName"),
|
buildMeta: BuildMeta = aBuildMeta(applicationName = "AppName"),
|
||||||
acceptDeclineInvitePresenter: Presenter<AcceptDeclineInviteState> = Presenter { anAcceptDeclineInviteState() }
|
acceptDeclineInvitePresenter: Presenter<AcceptDeclineInviteState> = Presenter { anAcceptDeclineInviteState() },
|
||||||
|
seenInvitesStore: SeenInvitesStore = InMemorySeenInvitesStore(),
|
||||||
): JoinRoomPresenter {
|
): JoinRoomPresenter {
|
||||||
return JoinRoomPresenter(
|
return JoinRoomPresenter(
|
||||||
roomId = roomId,
|
roomId = roomId,
|
||||||
|
|
@ -773,7 +782,8 @@ class JoinRoomPresenterTest {
|
||||||
cancelKnockRoom = cancelKnockRoom,
|
cancelKnockRoom = cancelKnockRoom,
|
||||||
forgetRoom = forgetRoom,
|
forgetRoom = forgetRoom,
|
||||||
buildMeta = buildMeta,
|
buildMeta = buildMeta,
|
||||||
acceptDeclineInvitePresenter = acceptDeclineInvitePresenter
|
acceptDeclineInvitePresenter = acceptDeclineInvitePresenter,
|
||||||
|
seenInvitesStore = seenInvitesStore,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -74,6 +74,7 @@ dependencies {
|
||||||
implementation(projects.features.licenses.api)
|
implementation(projects.features.licenses.api)
|
||||||
implementation(projects.features.logout.api)
|
implementation(projects.features.logout.api)
|
||||||
implementation(projects.features.deactivation.api)
|
implementation(projects.features.deactivation.api)
|
||||||
|
implementation(projects.features.invite.api)
|
||||||
implementation(projects.features.roomlist.api)
|
implementation(projects.features.roomlist.api)
|
||||||
implementation(projects.services.analytics.api)
|
implementation(projects.services.analytics.api)
|
||||||
implementation(projects.services.analytics.compose)
|
implementation(projects.services.analytics.compose)
|
||||||
|
|
@ -103,6 +104,7 @@ dependencies {
|
||||||
testImplementation(projects.libraries.push.test)
|
testImplementation(projects.libraries.push.test)
|
||||||
testImplementation(projects.libraries.pushstore.test)
|
testImplementation(projects.libraries.pushstore.test)
|
||||||
testImplementation(projects.features.ftue.test)
|
testImplementation(projects.features.ftue.test)
|
||||||
|
testImplementation(projects.features.invite.test)
|
||||||
testImplementation(projects.features.rageshake.test)
|
testImplementation(projects.features.rageshake.test)
|
||||||
testImplementation(projects.features.rageshake.impl)
|
testImplementation(projects.features.rageshake.impl)
|
||||||
testImplementation(projects.features.logout.test)
|
testImplementation(projects.features.logout.test)
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import android.content.Context
|
||||||
import coil3.SingletonImageLoader
|
import coil3.SingletonImageLoader
|
||||||
import com.squareup.anvil.annotations.ContributesBinding
|
import com.squareup.anvil.annotations.ContributesBinding
|
||||||
import io.element.android.features.ftue.api.state.FtueService
|
import io.element.android.features.ftue.api.state.FtueService
|
||||||
|
import io.element.android.features.invite.api.SeenInvitesStore
|
||||||
import io.element.android.features.preferences.impl.DefaultCacheService
|
import io.element.android.features.preferences.impl.DefaultCacheService
|
||||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||||
import io.element.android.libraries.di.ApplicationContext
|
import io.element.android.libraries.di.ApplicationContext
|
||||||
|
|
@ -35,6 +36,7 @@ class DefaultClearCacheUseCase @Inject constructor(
|
||||||
private val okHttpClient: Provider<OkHttpClient>,
|
private val okHttpClient: Provider<OkHttpClient>,
|
||||||
private val ftueService: FtueService,
|
private val ftueService: FtueService,
|
||||||
private val pushService: PushService,
|
private val pushService: PushService,
|
||||||
|
private val seenInvitesStore: SeenInvitesStore,
|
||||||
) : ClearCacheUseCase {
|
) : ClearCacheUseCase {
|
||||||
override suspend fun invoke() = withContext(coroutineDispatchers.io) {
|
override suspend fun invoke() = withContext(coroutineDispatchers.io) {
|
||||||
// Clear Matrix cache
|
// Clear Matrix cache
|
||||||
|
|
@ -50,6 +52,7 @@ class DefaultClearCacheUseCase @Inject constructor(
|
||||||
context.cacheDir.deleteRecursively()
|
context.cacheDir.deleteRecursively()
|
||||||
// Clear some settings
|
// Clear some settings
|
||||||
ftueService.reset()
|
ftueService.reset()
|
||||||
|
seenInvitesStore.clear()
|
||||||
// Ensure any error will be displayed again
|
// Ensure any error will be displayed again
|
||||||
pushService.setIgnoreRegistrationError(matrixClient.sessionId, false)
|
pushService.setIgnoreRegistrationError(matrixClient.sessionId, false)
|
||||||
// Ensure the app is restarted
|
// Ensure the app is restarted
|
||||||
|
|
|
||||||
|
|
@ -11,13 +11,16 @@ import androidx.test.platform.app.InstrumentationRegistry
|
||||||
import app.cash.turbine.test
|
import app.cash.turbine.test
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.features.ftue.test.FakeFtueService
|
import io.element.android.features.ftue.test.FakeFtueService
|
||||||
|
import io.element.android.features.invite.test.InMemorySeenInvitesStore
|
||||||
import io.element.android.features.preferences.impl.DefaultCacheService
|
import io.element.android.features.preferences.impl.DefaultCacheService
|
||||||
import io.element.android.libraries.matrix.api.core.SessionId
|
import io.element.android.libraries.matrix.api.core.SessionId
|
||||||
|
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||||
import io.element.android.libraries.push.test.FakePushService
|
import io.element.android.libraries.push.test.FakePushService
|
||||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||||
import io.element.android.tests.testutils.lambda.value
|
import io.element.android.tests.testutils.lambda.value
|
||||||
import io.element.android.tests.testutils.testCoroutineDispatchers
|
import io.element.android.tests.testutils.testCoroutineDispatchers
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
@ -41,6 +44,8 @@ class DefaultClearCacheUseCaseTest {
|
||||||
val pushService = FakePushService(
|
val pushService = FakePushService(
|
||||||
setIgnoreRegistrationErrorLambda = setIgnoreRegistrationErrorLambda
|
setIgnoreRegistrationErrorLambda = setIgnoreRegistrationErrorLambda
|
||||||
)
|
)
|
||||||
|
val seenInvitesStore = InMemorySeenInvitesStore(setOf(A_ROOM_ID))
|
||||||
|
assertThat(seenInvitesStore.seenRoomIds().first()).isNotEmpty()
|
||||||
val sut = DefaultClearCacheUseCase(
|
val sut = DefaultClearCacheUseCase(
|
||||||
context = InstrumentationRegistry.getInstrumentation().context,
|
context = InstrumentationRegistry.getInstrumentation().context,
|
||||||
matrixClient = matrixClient,
|
matrixClient = matrixClient,
|
||||||
|
|
@ -49,6 +54,7 @@ class DefaultClearCacheUseCaseTest {
|
||||||
okHttpClient = { OkHttpClient.Builder().build() },
|
okHttpClient = { OkHttpClient.Builder().build() },
|
||||||
ftueService = ftueService,
|
ftueService = ftueService,
|
||||||
pushService = pushService,
|
pushService = pushService,
|
||||||
|
seenInvitesStore = seenInvitesStore,
|
||||||
)
|
)
|
||||||
defaultCacheService.clearedCacheEventFlow.test {
|
defaultCacheService.clearedCacheEventFlow.test {
|
||||||
sut.invoke()
|
sut.invoke()
|
||||||
|
|
@ -57,6 +63,7 @@ class DefaultClearCacheUseCaseTest {
|
||||||
setIgnoreRegistrationErrorLambda.assertions().isCalledOnce()
|
setIgnoreRegistrationErrorLambda.assertions().isCalledOnce()
|
||||||
.with(value(matrixClient.sessionId), value(false))
|
.with(value(matrixClient.sessionId), value(false))
|
||||||
assertThat(awaitItem()).isEqualTo(matrixClient.sessionId)
|
assertThat(awaitItem()).isEqualTo(matrixClient.sessionId)
|
||||||
|
assertThat(seenInvitesStore.seenRoomIds().first()).isEmpty()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,9 @@ dependencies {
|
||||||
testImplementation(libs.test.truth)
|
testImplementation(libs.test.truth)
|
||||||
testImplementation(libs.test.turbine)
|
testImplementation(libs.test.turbine)
|
||||||
testImplementation(libs.test.robolectric)
|
testImplementation(libs.test.robolectric)
|
||||||
|
testImplementation(projects.features.invite.test)
|
||||||
|
testImplementation(projects.features.logout.test)
|
||||||
|
testImplementation(projects.features.networkmonitor.test)
|
||||||
testImplementation(projects.libraries.matrix.test)
|
testImplementation(projects.libraries.matrix.test)
|
||||||
testImplementation(projects.libraries.featureflag.test)
|
testImplementation(projects.libraries.featureflag.test)
|
||||||
testImplementation(projects.libraries.dateformatter.test)
|
testImplementation(projects.libraries.dateformatter.test)
|
||||||
|
|
@ -72,7 +75,5 @@ dependencies {
|
||||||
testImplementation(projects.libraries.push.test)
|
testImplementation(projects.libraries.push.test)
|
||||||
testImplementation(projects.services.analytics.test)
|
testImplementation(projects.services.analytics.test)
|
||||||
testImplementation(projects.services.toolbox.test)
|
testImplementation(projects.services.toolbox.test)
|
||||||
testImplementation(projects.features.networkmonitor.test)
|
|
||||||
testImplementation(projects.features.logout.test)
|
|
||||||
testImplementation(projects.tests.testutils)
|
testImplementation(projects.tests.testutils)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,10 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
import io.element.android.features.roomlist.impl.model.RoomListRoomSummary
|
import io.element.android.features.roomlist.impl.model.RoomListRoomSummary
|
||||||
import io.element.android.libraries.fullscreenintent.api.FullScreenIntentPermissionsState
|
import io.element.android.libraries.fullscreenintent.api.FullScreenIntentPermissionsState
|
||||||
import io.element.android.libraries.fullscreenintent.api.aFullScreenIntentPermissionsState
|
import io.element.android.libraries.fullscreenintent.api.aFullScreenIntentPermissionsState
|
||||||
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
import kotlinx.collections.immutable.toPersistentSet
|
||||||
|
|
||||||
open class RoomListContentStateProvider : PreviewParameterProvider<RoomListContentState> {
|
open class RoomListContentStateProvider : PreviewParameterProvider<RoomListContentState> {
|
||||||
override val values: Sequence<RoomListContentState>
|
override val values: Sequence<RoomListContentState>
|
||||||
|
|
@ -29,10 +31,12 @@ internal fun aRoomsContentState(
|
||||||
securityBannerState: SecurityBannerState = SecurityBannerState.None,
|
securityBannerState: SecurityBannerState = SecurityBannerState.None,
|
||||||
summaries: ImmutableList<RoomListRoomSummary> = aRoomListRoomSummaryList(),
|
summaries: ImmutableList<RoomListRoomSummary> = aRoomListRoomSummaryList(),
|
||||||
fullScreenIntentPermissionsState: FullScreenIntentPermissionsState = aFullScreenIntentPermissionsState(),
|
fullScreenIntentPermissionsState: FullScreenIntentPermissionsState = aFullScreenIntentPermissionsState(),
|
||||||
|
seenRoomInvites: Set<RoomId> = emptySet(),
|
||||||
) = RoomListContentState.Rooms(
|
) = RoomListContentState.Rooms(
|
||||||
securityBannerState = securityBannerState,
|
securityBannerState = securityBannerState,
|
||||||
fullScreenIntentPermissionsState = fullScreenIntentPermissionsState,
|
fullScreenIntentPermissionsState = fullScreenIntentPermissionsState,
|
||||||
summaries = summaries,
|
summaries = summaries,
|
||||||
|
seenRoomInvites = seenRoomInvites.toPersistentSet(),
|
||||||
)
|
)
|
||||||
|
|
||||||
internal fun aSkeletonContentState() = RoomListContentState.Skeleton(16)
|
internal fun aSkeletonContentState() = RoomListContentState.Skeleton(16)
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.runtime.snapshotFlow
|
import androidx.compose.runtime.snapshotFlow
|
||||||
import im.vector.app.features.analytics.plan.Interaction
|
import im.vector.app.features.analytics.plan.Interaction
|
||||||
|
import io.element.android.features.invite.api.SeenInvitesStore
|
||||||
import io.element.android.features.invite.api.response.AcceptDeclineInviteEvents
|
import io.element.android.features.invite.api.response.AcceptDeclineInviteEvents
|
||||||
import io.element.android.features.invite.api.response.AcceptDeclineInviteState
|
import io.element.android.features.invite.api.response.AcceptDeclineInviteState
|
||||||
import io.element.android.features.invite.api.response.InviteData
|
import io.element.android.features.invite.api.response.InviteData
|
||||||
|
|
@ -57,6 +58,7 @@ import io.element.android.libraries.push.api.notifications.NotificationCleaner
|
||||||
import io.element.android.services.analytics.api.AnalyticsService
|
import io.element.android.services.analytics.api.AnalyticsService
|
||||||
import io.element.android.services.analyticsproviders.api.trackers.captureInteraction
|
import io.element.android.services.analyticsproviders.api.trackers.captureInteraction
|
||||||
import kotlinx.collections.immutable.toPersistentList
|
import kotlinx.collections.immutable.toPersistentList
|
||||||
|
import kotlinx.collections.immutable.toPersistentSet
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
|
|
@ -92,6 +94,7 @@ class RoomListPresenter @Inject constructor(
|
||||||
private val logoutPresenter: Presenter<DirectLogoutState>,
|
private val logoutPresenter: Presenter<DirectLogoutState>,
|
||||||
private val appPreferencesStore: AppPreferencesStore,
|
private val appPreferencesStore: AppPreferencesStore,
|
||||||
private val rageshakeFeatureAvailability: RageshakeFeatureAvailability,
|
private val rageshakeFeatureAvailability: RageshakeFeatureAvailability,
|
||||||
|
private val seenInvitesStore: SeenInvitesStore,
|
||||||
) : Presenter<RoomListState> {
|
) : Presenter<RoomListState> {
|
||||||
private val encryptionService: EncryptionService = client.encryptionService()
|
private val encryptionService: EncryptionService = client.encryptionService()
|
||||||
|
|
||||||
|
|
@ -227,6 +230,7 @@ class RoomListPresenter @Inject constructor(
|
||||||
loadingState == RoomList.LoadingState.NotLoaded || roomSummaries is AsyncData.Loading
|
loadingState == RoomList.LoadingState.NotLoaded || roomSummaries is AsyncData.Loading
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
val seenRoomInvites by remember { seenInvitesStore.seenRoomIds() }.collectAsState(emptySet())
|
||||||
val securityBannerState by rememberSecurityBannerState(securityBannerDismissed)
|
val securityBannerState by rememberSecurityBannerState(securityBannerDismissed)
|
||||||
return when {
|
return when {
|
||||||
showEmpty -> RoomListContentState.Empty(securityBannerState = securityBannerState)
|
showEmpty -> RoomListContentState.Empty(securityBannerState = securityBannerState)
|
||||||
|
|
@ -235,7 +239,8 @@ class RoomListPresenter @Inject constructor(
|
||||||
RoomListContentState.Rooms(
|
RoomListContentState.Rooms(
|
||||||
securityBannerState = securityBannerState,
|
securityBannerState = securityBannerState,
|
||||||
fullScreenIntentPermissionsState = fullScreenIntentPermissionsPresenter.present(),
|
fullScreenIntentPermissionsState = fullScreenIntentPermissionsPresenter.present(),
|
||||||
summaries = roomSummaries.dataOrNull().orEmpty().toPersistentList()
|
summaries = roomSummaries.dataOrNull().orEmpty().toPersistentList(),
|
||||||
|
seenRoomInvites = seenRoomInvites.toPersistentSet(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ import io.element.android.libraries.fullscreenintent.api.FullScreenIntentPermiss
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
import kotlinx.collections.immutable.ImmutableSet
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
data class RoomListState(
|
data class RoomListState(
|
||||||
|
|
@ -65,9 +66,11 @@ sealed interface RoomListContentState {
|
||||||
data class Empty(
|
data class Empty(
|
||||||
val securityBannerState: SecurityBannerState,
|
val securityBannerState: SecurityBannerState,
|
||||||
) : RoomListContentState
|
) : RoomListContentState
|
||||||
|
|
||||||
data class Rooms(
|
data class Rooms(
|
||||||
val securityBannerState: SecurityBannerState,
|
val securityBannerState: SecurityBannerState,
|
||||||
val fullScreenIntentPermissionsState: FullScreenIntentPermissionsState,
|
val fullScreenIntentPermissionsState: FullScreenIntentPermissionsState,
|
||||||
val summaries: ImmutableList<RoomListRoomSummary>,
|
val summaries: ImmutableList<RoomListRoomSummary>,
|
||||||
|
val seenRoomInvites: ImmutableSet<RoomId>,
|
||||||
) : RoomListContentState
|
) : RoomListContentState
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,7 @@ import io.element.android.features.roomlist.impl.filters.RoomListFiltersState
|
||||||
import io.element.android.features.roomlist.impl.filters.aRoomListFiltersState
|
import io.element.android.features.roomlist.impl.filters.aRoomListFiltersState
|
||||||
import io.element.android.features.roomlist.impl.filters.selection.FilterSelectionState
|
import io.element.android.features.roomlist.impl.filters.selection.FilterSelectionState
|
||||||
import io.element.android.features.roomlist.impl.model.RoomListRoomSummary
|
import io.element.android.features.roomlist.impl.model.RoomListRoomSummary
|
||||||
|
import io.element.android.features.roomlist.impl.model.RoomSummaryDisplayType
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
import io.element.android.libraries.designsystem.theme.components.Button
|
import io.element.android.libraries.designsystem.theme.components.Button
|
||||||
|
|
@ -239,6 +240,8 @@ private fun RoomsViewList(
|
||||||
) { index, room ->
|
) { index, room ->
|
||||||
RoomSummaryRow(
|
RoomSummaryRow(
|
||||||
room = room,
|
room = room,
|
||||||
|
isInviteSeen = room.displayType == RoomSummaryDisplayType.INVITE &&
|
||||||
|
state.seenRoomInvites.contains(room.roomId),
|
||||||
onClick = onRoomClick,
|
onClick = onRoomClick,
|
||||||
eventSink = eventSink,
|
eventSink = eventSink,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,7 @@ internal val minHeight = 84.dp
|
||||||
@Composable
|
@Composable
|
||||||
internal fun RoomSummaryRow(
|
internal fun RoomSummaryRow(
|
||||||
room: RoomListRoomSummary,
|
room: RoomListRoomSummary,
|
||||||
|
isInviteSeen: Boolean,
|
||||||
onClick: (RoomListRoomSummary) -> Unit,
|
onClick: (RoomListRoomSummary) -> Unit,
|
||||||
eventSink: (RoomListEvents) -> Unit,
|
eventSink: (RoomListEvents) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
|
@ -85,7 +86,7 @@ internal fun RoomSummaryRow(
|
||||||
Timber.d("Long click on invite room")
|
Timber.d("Long click on invite room")
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
InviteNameAndIndicatorRow(name = room.name)
|
InviteNameAndIndicatorRow(name = room.name, isInviteSeen = isInviteSeen)
|
||||||
InviteSubtitle(isDm = room.isDm, inviteSender = room.inviteSender)
|
InviteSubtitle(isDm = room.isDm, inviteSender = room.inviteSender)
|
||||||
if (!room.isDm && room.inviteSender != null) {
|
if (!room.isDm && room.inviteSender != null) {
|
||||||
Spacer(modifier = Modifier.height(4.dp))
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
|
@ -300,6 +301,7 @@ private fun LastMessageAndIndicatorRow(
|
||||||
@Composable
|
@Composable
|
||||||
private fun InviteNameAndIndicatorRow(
|
private fun InviteNameAndIndicatorRow(
|
||||||
name: String?,
|
name: String?,
|
||||||
|
isInviteSeen: Boolean,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
|
|
@ -316,9 +318,11 @@ private fun InviteNameAndIndicatorRow(
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis
|
overflow = TextOverflow.Ellipsis
|
||||||
)
|
)
|
||||||
UnreadIndicatorAtom(
|
if (!isInviteSeen) {
|
||||||
color = ElementTheme.colors.unreadIndicator
|
UnreadIndicatorAtom(
|
||||||
)
|
color = ElementTheme.colors.unreadIndicator
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -384,6 +388,8 @@ private fun MentionIndicatorAtom() {
|
||||||
internal fun RoomSummaryRowPreview(@PreviewParameter(RoomListRoomSummaryProvider::class) data: RoomListRoomSummary) = ElementPreview {
|
internal fun RoomSummaryRowPreview(@PreviewParameter(RoomListRoomSummaryProvider::class) data: RoomListRoomSummary) = ElementPreview {
|
||||||
RoomSummaryRow(
|
RoomSummaryRow(
|
||||||
room = data,
|
room = data,
|
||||||
|
// Set isInviteSeen to true for the preview when the room has name "Bob"
|
||||||
|
isInviteSeen = data.name == "Bob",
|
||||||
onClick = {},
|
onClick = {},
|
||||||
eventSink = {},
|
eventSink = {},
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -39,12 +39,10 @@ data class RoomListRoomSummary(
|
||||||
) {
|
) {
|
||||||
val isHighlighted = userDefinedNotificationMode != RoomNotificationMode.MUTE &&
|
val isHighlighted = userDefinedNotificationMode != RoomNotificationMode.MUTE &&
|
||||||
(numberOfUnreadNotifications > 0 || numberOfUnreadMentions > 0) ||
|
(numberOfUnreadNotifications > 0 || numberOfUnreadMentions > 0) ||
|
||||||
isMarkedUnread ||
|
isMarkedUnread
|
||||||
displayType == RoomSummaryDisplayType.INVITE
|
|
||||||
|
|
||||||
val hasNewContent = numberOfUnreadMessages > 0 ||
|
val hasNewContent = numberOfUnreadMessages > 0 ||
|
||||||
numberOfUnreadMentions > 0 ||
|
numberOfUnreadMentions > 0 ||
|
||||||
numberOfUnreadNotifications > 0 ||
|
numberOfUnreadNotifications > 0 ||
|
||||||
isMarkedUnread ||
|
isMarkedUnread
|
||||||
displayType == RoomSummaryDisplayType.INVITE
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -173,6 +173,8 @@ private fun RoomListSearchContent(
|
||||||
) { room ->
|
) { room ->
|
||||||
RoomSummaryRow(
|
RoomSummaryRow(
|
||||||
room = room,
|
room = room,
|
||||||
|
// TODO
|
||||||
|
isInviteSeen = false,
|
||||||
onClick = ::onRoomClick,
|
onClick = ::onRoomClick,
|
||||||
eventSink = eventSink,
|
eventSink = eventSink,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -12,9 +12,11 @@ import app.cash.molecule.moleculeFlow
|
||||||
import app.cash.turbine.test
|
import app.cash.turbine.test
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import im.vector.app.features.analytics.plan.Interaction
|
import im.vector.app.features.analytics.plan.Interaction
|
||||||
|
import io.element.android.features.invite.api.SeenInvitesStore
|
||||||
import io.element.android.features.invite.api.response.AcceptDeclineInviteEvents
|
import io.element.android.features.invite.api.response.AcceptDeclineInviteEvents
|
||||||
import io.element.android.features.invite.api.response.AcceptDeclineInviteState
|
import io.element.android.features.invite.api.response.AcceptDeclineInviteState
|
||||||
import io.element.android.features.invite.api.response.anAcceptDeclineInviteState
|
import io.element.android.features.invite.api.response.anAcceptDeclineInviteState
|
||||||
|
import io.element.android.features.invite.test.InMemorySeenInvitesStore
|
||||||
import io.element.android.features.leaveroom.api.LeaveRoomEvent
|
import io.element.android.features.leaveroom.api.LeaveRoomEvent
|
||||||
import io.element.android.features.leaveroom.api.LeaveRoomState
|
import io.element.android.features.leaveroom.api.LeaveRoomState
|
||||||
import io.element.android.features.leaveroom.api.aLeaveRoomState
|
import io.element.android.features.leaveroom.api.aLeaveRoomState
|
||||||
|
|
@ -169,10 +171,11 @@ class RoomListPresenterTest {
|
||||||
val matrixClient = FakeMatrixClient(
|
val matrixClient = FakeMatrixClient(
|
||||||
roomListService = roomListService
|
roomListService = roomListService
|
||||||
)
|
)
|
||||||
val presenter = createRoomListPresenter(client = matrixClient)
|
val presenter = createRoomListPresenter(
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
client = matrixClient,
|
||||||
presenter.present()
|
seenInvitesStore = InMemorySeenInvitesStore(setOf(A_ROOM_ID, A_ROOM_ID_2, A_ROOM_ID_3)),
|
||||||
}.test {
|
)
|
||||||
|
presenter.test {
|
||||||
val initialState = consumeItemsUntilPredicate { state -> state.contentState is RoomListContentState.Skeleton }.last()
|
val initialState = consumeItemsUntilPredicate { state -> state.contentState is RoomListContentState.Skeleton }.last()
|
||||||
assertThat(initialState.contentState).isInstanceOf(RoomListContentState.Skeleton::class.java)
|
assertThat(initialState.contentState).isInstanceOf(RoomListContentState.Skeleton::class.java)
|
||||||
roomListService.postAllRoomsLoadingState(RoomList.LoadingState.Loaded(1))
|
roomListService.postAllRoomsLoadingState(RoomList.LoadingState.Loaded(1))
|
||||||
|
|
@ -194,6 +197,7 @@ class RoomListPresenterTest {
|
||||||
timestamp = "0 TimeOrDate true",
|
timestamp = "0 TimeOrDate true",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
assertThat(withRoomsState.contentAsRooms().seenRoomInvites).containsExactly(A_ROOM_ID, A_ROOM_ID_2, A_ROOM_ID_3)
|
||||||
cancelAndIgnoreRemainingEvents()
|
cancelAndIgnoreRemainingEvents()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -680,6 +684,7 @@ class RoomListPresenterTest {
|
||||||
notificationCleaner: NotificationCleaner = FakeNotificationCleaner(),
|
notificationCleaner: NotificationCleaner = FakeNotificationCleaner(),
|
||||||
appPreferencesStore: AppPreferencesStore = InMemoryAppPreferencesStore(),
|
appPreferencesStore: AppPreferencesStore = InMemoryAppPreferencesStore(),
|
||||||
rageshakeFeatureAvailability: RageshakeFeatureAvailability = RageshakeFeatureAvailability { true },
|
rageshakeFeatureAvailability: RageshakeFeatureAvailability = RageshakeFeatureAvailability { true },
|
||||||
|
seenInvitesStore: SeenInvitesStore = InMemorySeenInvitesStore()
|
||||||
) = RoomListPresenter(
|
) = RoomListPresenter(
|
||||||
client = client,
|
client = client,
|
||||||
syncService = syncService,
|
syncService = syncService,
|
||||||
|
|
@ -711,6 +716,7 @@ class RoomListPresenterTest {
|
||||||
logoutPresenter = { aDirectLogoutState() },
|
logoutPresenter = { aDirectLogoutState() },
|
||||||
appPreferencesStore = appPreferencesStore,
|
appPreferencesStore = appPreferencesStore,
|
||||||
rageshakeFeatureAvailability = rageshakeFeatureAvailability,
|
rageshakeFeatureAvailability = rageshakeFeatureAvailability,
|
||||||
|
seenInvitesStore = seenInvitesStore,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -65,12 +65,12 @@ class RoomListRoomSummaryTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when display type is invite then isHighlighted and hasNewContent are true`() {
|
fun `when display type is invite then isHighlighted and hasNewContent are false`() {
|
||||||
val sut = createRoomListRoomSummary(
|
val sut = createRoomListRoomSummary(
|
||||||
displayType = RoomSummaryDisplayType.INVITE,
|
displayType = RoomSummaryDisplayType.INVITE,
|
||||||
)
|
)
|
||||||
assertThat(sut.isHighlighted).isTrue()
|
assertThat(sut.isHighlighted).isFalse()
|
||||||
assertThat(sut.hasNewContent).isTrue()
|
assertThat(sut.hasNewContent).isFalse()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:f64d2899886ab2e20bbd2dd0e0e2bd909f461751e60f4b6d93e506963c41ce03
|
oid sha256:a92aa815f9c74893ea1b9b98912f0eefd86159af900ece588a3eb426264d076a
|
||||||
size 15832
|
size 15473
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:20a0dfc19f05e9727cf70cf7eaf83503c805423cc36edbefaa119c2692eaadd2
|
oid sha256:8287ed2eda7e0d486f6122c5bd61517f7f6ccc5ea21413f2eea085683d15ba68
|
||||||
size 15678
|
size 15370
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue