diff --git a/features/invite/api/src/main/kotlin/io/element/android/features/invite/api/SeenInvitesStore.kt b/features/invite/api/src/main/kotlin/io/element/android/features/invite/api/SeenInvitesStore.kt new file mode 100644 index 0000000000..682970ffe7 --- /dev/null +++ b/features/invite/api/src/main/kotlin/io/element/android/features/invite/api/SeenInvitesStore.kt @@ -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> + + /** + * 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() +} diff --git a/features/invite/impl/build.gradle.kts b/features/invite/impl/build.gradle.kts index 8c00ac3d23..7f052bea09 100644 --- a/features/invite/impl/build.gradle.kts +++ b/features/invite/impl/build.gradle.kts @@ -21,6 +21,7 @@ setupAnvil() dependencies { api(projects.features.invite.api) implementation(libs.androidx.datastore.preferences) + implementation(projects.libraries.androidutils) implementation(projects.libraries.core) implementation(projects.libraries.architecture) implementation(projects.libraries.matrix.api) @@ -35,6 +36,7 @@ dependencies { testImplementation(libs.molecule.runtime) testImplementation(libs.test.truth) testImplementation(libs.test.turbine) + testImplementation(projects.features.invite.test) testImplementation(projects.libraries.matrix.test) testImplementation(projects.libraries.push.test) testImplementation(projects.services.analytics.test) diff --git a/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/DefaultSeenInvitesStore.kt b/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/DefaultSeenInvitesStore.kt new file mode 100644 index 0000000000..3accc163b0 --- /dev/null +++ b/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/DefaultSeenInvitesStore.kt @@ -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> = + 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() + } +} diff --git a/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/response/AcceptDeclineInvitePresenter.kt b/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/response/AcceptDeclineInvitePresenter.kt index 9f4a7ee848..e02642c32e 100644 --- a/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/response/AcceptDeclineInvitePresenter.kt +++ b/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/response/AcceptDeclineInvitePresenter.kt @@ -13,6 +13,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope 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.AcceptDeclineInviteState import io.element.android.features.invite.api.response.ConfirmingDeclineInvite @@ -34,6 +35,7 @@ class AcceptDeclineInvitePresenter @Inject constructor( private val client: MatrixClient, private val joinRoom: JoinRoom, private val notificationCleaner: NotificationCleaner, + private val seenInvitesStore: SeenInvitesStore, ) : Presenter { @Composable override fun present(): AcceptDeclineInviteState { @@ -107,6 +109,7 @@ class AcceptDeclineInvitePresenter @Inject constructor( ) .onSuccess { notificationCleaner.clearMembershipNotificationForRoom(client.sessionId, roomId) + seenInvitesStore.markAsUnSeen(roomId) } .map { roomId } } @@ -125,6 +128,7 @@ class AcceptDeclineInvitePresenter @Inject constructor( client.ignoreUser(inviteData.senderId).getOrThrow() } notificationCleaner.clearMembershipNotificationForRoom(client.sessionId, inviteData.roomId) + seenInvitesStore.markAsUnSeen(inviteData.roomId) inviteData.roomId }.runCatchingUpdatingState(declinedAction) } diff --git a/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/response/AcceptDeclineInvitePresenterTest.kt b/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/response/AcceptDeclineInvitePresenterTest.kt index 870d3bba09..7feaff091e 100644 --- a/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/response/AcceptDeclineInvitePresenterTest.kt +++ b/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/response/AcceptDeclineInvitePresenterTest.kt @@ -9,9 +9,11 @@ package io.element.android.features.invite.impl.response import com.google.common.truth.Truth.assertThat 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.ConfirmingDeclineInvite 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.matrix.api.MatrixClient 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.toRoomIdOrAlias 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_SESSION_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.value import io.element.android.tests.testutils.test +import kotlinx.coroutines.flow.first import kotlinx.coroutines.test.runTest import org.junit.Rule import org.junit.Test @@ -54,7 +59,10 @@ class AcceptDeclineInvitePresenterTest { @Test 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 { val inviteData = anInviteData() awaitItem().also { state -> @@ -72,6 +80,7 @@ class AcceptDeclineInvitePresenterTest { 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 @@ -84,7 +93,11 @@ class AcceptDeclineInvitePresenterTest { 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 { val inviteData = anInviteData() awaitItem().also { state -> @@ -111,6 +124,7 @@ class AcceptDeclineInvitePresenterTest { cancelAndConsumeRemainingEvents() } assert(declineInviteFailure).isCalledOnce() + assertThat(seenInvitesStore.seenRoomIds().first()).containsExactly(A_ROOM_ID, A_ROOM_ID_2, A_ROOM_ID_3) } @Test @@ -129,9 +143,11 @@ class AcceptDeclineInvitePresenterTest { Result.success(FakeRoomPreview(declineInviteResult = declineInviteSuccess)) } ) + val seenInvitesStore = InMemorySeenInvitesStore(setOf(A_ROOM_ID, A_ROOM_ID_2, A_ROOM_ID_3)) val presenter = createAcceptDeclineInvitePresenter( client = client, notificationCleaner = fakeNotificationCleaner, + seenInvitesStore = seenInvitesStore, ) presenter.test { val inviteData = anInviteData() @@ -156,6 +172,7 @@ class AcceptDeclineInvitePresenterTest { clearMembershipNotificationForRoomLambda.assertions() .isCalledOnce() .with(value(A_SESSION_ID), value(A_ROOM_ID)) + assertThat(seenInvitesStore.seenRoomIds().first()).containsExactly(A_ROOM_ID_2, A_ROOM_ID_3) } @Test @@ -174,9 +191,11 @@ class AcceptDeclineInvitePresenterTest { }, ignoreUserResult = ignoreUserSuccess ) + val seenInvitesStore = InMemorySeenInvitesStore(setOf(A_ROOM_ID, A_ROOM_ID_2, A_ROOM_ID_3)) val presenter = createAcceptDeclineInvitePresenter( client = client, notificationCleaner = fakeNotificationCleaner, + seenInvitesStore = seenInvitesStore, ) presenter.test { val inviteData = anInviteData() @@ -202,6 +221,7 @@ class AcceptDeclineInvitePresenterTest { clearMembershipNotificationForRoomLambda.assertions() .isCalledOnce() .with(value(A_SESSION_ID), value(A_ROOM_ID)) + assertThat(seenInvitesStore.seenRoomIds().first()).containsExactly(A_ROOM_ID_2, A_ROOM_ID_3) } @Test @@ -214,7 +234,11 @@ class AcceptDeclineInvitePresenterTest { 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 { val inviteData = anInviteData() awaitItem().also { state -> @@ -230,6 +254,7 @@ class AcceptDeclineInvitePresenterTest { } assertThat(awaitItem().declineAction.isLoading()).isTrue() } + assertThat(seenInvitesStore.seenRoomIds().first()).containsExactly(A_ROOM_ID, A_ROOM_ID_2, A_ROOM_ID_3) } @Test @@ -237,7 +262,11 @@ class AcceptDeclineInvitePresenterTest { val joinRoomFailure = lambdaRecorder { roomIdOrAlias: RoomIdOrAlias, _: List, _: JoinedRoom.Trigger -> Result.failure(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 { val inviteData = anInviteData() awaitItem().also { state -> @@ -266,6 +295,7 @@ class AcceptDeclineInvitePresenterTest { value(emptyList()), value(JoinedRoom.Trigger.Invite) ) + assertThat(seenInvitesStore.seenRoomIds().first()).containsExactly(A_ROOM_ID, A_ROOM_ID_2, A_ROOM_ID_3) } @Test @@ -279,9 +309,11 @@ class AcceptDeclineInvitePresenterTest { val joinRoomSuccess = lambdaRecorder { _: RoomIdOrAlias, _: List, _: JoinedRoom.Trigger -> Result.success(Unit) } + val seenInvitesStore = InMemorySeenInvitesStore(setOf(A_ROOM_ID, A_ROOM_ID_2, A_ROOM_ID_3)) val presenter = createAcceptDeclineInvitePresenter( joinRoomLambda = joinRoomSuccess, notificationCleaner = fakeNotificationCleaner, + seenInvitesStore = seenInvitesStore, ) presenter.test { val inviteData = anInviteData() @@ -308,6 +340,7 @@ class AcceptDeclineInvitePresenterTest { clearMembershipNotificationForRoomLambda.assertions() .isCalledOnce() .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( @@ -330,11 +363,13 @@ class AcceptDeclineInvitePresenterTest { Result.success(Unit) }, notificationCleaner: NotificationCleaner = FakeNotificationCleaner(), + seenInvitesStore: SeenInvitesStore = InMemorySeenInvitesStore(), ): AcceptDeclineInvitePresenter { return AcceptDeclineInvitePresenter( client = client, joinRoom = FakeJoinRoom(joinRoomLambda), notificationCleaner = notificationCleaner, + seenInvitesStore = seenInvitesStore, ) } } diff --git a/features/invite/test/build.gradle.kts b/features/invite/test/build.gradle.kts new file mode 100644 index 0000000000..dc43ba00c3 --- /dev/null +++ b/features/invite/test/build.gradle.kts @@ -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) +} diff --git a/features/invite/test/src/main/kotlin/io/element/android/features/invite/test/InMemorySeenInvitesStore.kt b/features/invite/test/src/main/kotlin/io/element/android/features/invite/test/InMemorySeenInvitesStore.kt new file mode 100644 index 0000000000..25db72532e --- /dev/null +++ b/features/invite/test/src/main/kotlin/io/element/android/features/invite/test/InMemorySeenInvitesStore.kt @@ -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 = emptySet(), +) : SeenInvitesStore { + private val roomIds = MutableStateFlow(initialRoomIds) + + override fun seenRoomIds(): Flow> = 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() + } +} diff --git a/features/joinroom/impl/build.gradle.kts b/features/joinroom/impl/build.gradle.kts index 65635f1907..ecf3843674 100644 --- a/features/joinroom/impl/build.gradle.kts +++ b/features/joinroom/impl/build.gradle.kts @@ -42,6 +42,7 @@ dependencies { testImplementation(libs.test.robolectric) testImplementation(libs.test.truth) testImplementation(libs.test.turbine) + testImplementation(projects.features.invite.test) testImplementation(projects.libraries.matrix.test) testImplementation(projects.tests.testutils) testImplementation(libs.androidx.compose.ui.test.junit) diff --git a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenter.kt b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenter.kt index b1fb345a0c..9914afbded 100644 --- a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenter.kt +++ b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenter.kt @@ -9,6 +9,7 @@ package io.element.android.features.joinroom.impl import androidx.annotation.VisibleForTesting 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 @@ -22,6 +23,7 @@ import androidx.compose.runtime.setValue import dagger.assisted.Assisted import dagger.assisted.AssistedInject 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.AcceptDeclineInviteState import io.element.android.features.invite.api.response.InviteData @@ -67,6 +69,7 @@ class JoinRoomPresenter @AssistedInject constructor( private val forgetRoom: ForgetRoom, private val acceptDeclineInvitePresenter: Presenter, private val buildMeta: BuildMeta, + private val seenInvitesStore: SeenInvitesStore, ) : Presenter { interface Factory { fun create( @@ -149,6 +152,10 @@ class JoinRoomPresenter @AssistedInject constructor( } val acceptDeclineInviteState = acceptDeclineInvitePresenter.present() + LaunchedEffect(contentState) { + contentState.markRoomInviteAsSeen() + } + fun handleEvents(event: JoinRoomEvents) { when (event) { JoinRoomEvents.JoinRoom -> coroutineScope.joinRoom(joinAction) @@ -236,6 +243,12 @@ class JoinRoomPresenter @AssistedInject constructor( 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 { diff --git a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/di/JoinRoomModule.kt b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/di/JoinRoomModule.kt index ff4cbbbc80..6a9bd559af 100644 --- a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/di/JoinRoomModule.kt +++ b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/di/JoinRoomModule.kt @@ -11,6 +11,7 @@ import com.squareup.anvil.annotations.ContributesTo import dagger.Module import dagger.Provides 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.joinroom.impl.JoinRoomPresenter import io.element.android.features.roomdirectory.api.RoomDescription @@ -35,6 +36,7 @@ object JoinRoomModule { forgetRoom: ForgetRoom, acceptDeclineInvitePresenter: Presenter, buildMeta: BuildMeta, + seenInvitesStore: SeenInvitesStore, ): JoinRoomPresenter.Factory { return object : JoinRoomPresenter.Factory { override fun create( @@ -57,6 +59,7 @@ object JoinRoomModule { cancelKnockRoom = cancelKnockRoom, acceptDeclineInvitePresenter = acceptDeclineInvitePresenter, buildMeta = buildMeta, + seenInvitesStore = seenInvitesStore, ) } } diff --git a/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenterTest.kt b/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenterTest.kt index 1c146285c2..b226d53706 100644 --- a/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenterTest.kt +++ b/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenterTest.kt @@ -9,9 +9,11 @@ package io.element.android.features.joinroom.impl import com.google.common.truth.Truth.assertThat 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.AcceptDeclineInviteState 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.ForgetRoom 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.test import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runTest import org.junit.Rule @@ -111,14 +114,19 @@ class JoinRoomPresenterTest { flowOf(Optional.of(roomSummary)) } } + val seenInvitesStore = InMemorySeenInvitesStore() val presenter = createJoinRoomPresenter( - matrixClient = matrixClient + matrixClient = matrixClient, + seenInvitesStore = seenInvitesStore, ) + assertThat(seenInvitesStore.seenRoomIds().first()).isEmpty() presenter.test { skipItems(1) awaitItem().also { state -> 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(), forgetRoom: ForgetRoom = FakeForgetRoom(), buildMeta: BuildMeta = aBuildMeta(applicationName = "AppName"), - acceptDeclineInvitePresenter: Presenter = Presenter { anAcceptDeclineInviteState() } + acceptDeclineInvitePresenter: Presenter = Presenter { anAcceptDeclineInviteState() }, + seenInvitesStore: SeenInvitesStore = InMemorySeenInvitesStore(), ): JoinRoomPresenter { return JoinRoomPresenter( roomId = roomId, @@ -773,7 +782,8 @@ class JoinRoomPresenterTest { cancelKnockRoom = cancelKnockRoom, forgetRoom = forgetRoom, buildMeta = buildMeta, - acceptDeclineInvitePresenter = acceptDeclineInvitePresenter + acceptDeclineInvitePresenter = acceptDeclineInvitePresenter, + seenInvitesStore = seenInvitesStore, ) } diff --git a/features/preferences/impl/build.gradle.kts b/features/preferences/impl/build.gradle.kts index c1f32affa3..8d34d55559 100644 --- a/features/preferences/impl/build.gradle.kts +++ b/features/preferences/impl/build.gradle.kts @@ -74,6 +74,7 @@ dependencies { implementation(projects.features.licenses.api) implementation(projects.features.logout.api) implementation(projects.features.deactivation.api) + implementation(projects.features.invite.api) implementation(projects.features.roomlist.api) implementation(projects.services.analytics.api) implementation(projects.services.analytics.compose) @@ -103,6 +104,7 @@ dependencies { testImplementation(projects.libraries.push.test) testImplementation(projects.libraries.pushstore.test) testImplementation(projects.features.ftue.test) + testImplementation(projects.features.invite.test) testImplementation(projects.features.rageshake.test) testImplementation(projects.features.rageshake.impl) testImplementation(projects.features.logout.test) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/tasks/ClearCacheUseCase.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/tasks/ClearCacheUseCase.kt index 27763db5f5..8a081c0e41 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/tasks/ClearCacheUseCase.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/tasks/ClearCacheUseCase.kt @@ -11,6 +11,7 @@ import android.content.Context import coil3.SingletonImageLoader import com.squareup.anvil.annotations.ContributesBinding 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.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.di.ApplicationContext @@ -35,6 +36,7 @@ class DefaultClearCacheUseCase @Inject constructor( private val okHttpClient: Provider, private val ftueService: FtueService, private val pushService: PushService, + private val seenInvitesStore: SeenInvitesStore, ) : ClearCacheUseCase { override suspend fun invoke() = withContext(coroutineDispatchers.io) { // Clear Matrix cache @@ -50,6 +52,7 @@ class DefaultClearCacheUseCase @Inject constructor( context.cacheDir.deleteRecursively() // Clear some settings ftueService.reset() + seenInvitesStore.clear() // Ensure any error will be displayed again pushService.setIgnoreRegistrationError(matrixClient.sessionId, false) // Ensure the app is restarted diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/tasks/DefaultClearCacheUseCaseTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/tasks/DefaultClearCacheUseCaseTest.kt index 778db4a4a8..401477d5fc 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/tasks/DefaultClearCacheUseCaseTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/tasks/DefaultClearCacheUseCaseTest.kt @@ -11,13 +11,16 @@ import androidx.test.platform.app.InstrumentationRegistry import app.cash.turbine.test import com.google.common.truth.Truth.assertThat 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.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.push.test.FakePushService import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.lambda.value import io.element.android.tests.testutils.testCoroutineDispatchers +import kotlinx.coroutines.flow.first import kotlinx.coroutines.test.runTest import okhttp3.OkHttpClient import org.junit.Test @@ -41,6 +44,8 @@ class DefaultClearCacheUseCaseTest { val pushService = FakePushService( setIgnoreRegistrationErrorLambda = setIgnoreRegistrationErrorLambda ) + val seenInvitesStore = InMemorySeenInvitesStore(setOf(A_ROOM_ID)) + assertThat(seenInvitesStore.seenRoomIds().first()).isNotEmpty() val sut = DefaultClearCacheUseCase( context = InstrumentationRegistry.getInstrumentation().context, matrixClient = matrixClient, @@ -49,6 +54,7 @@ class DefaultClearCacheUseCaseTest { okHttpClient = { OkHttpClient.Builder().build() }, ftueService = ftueService, pushService = pushService, + seenInvitesStore = seenInvitesStore, ) defaultCacheService.clearedCacheEventFlow.test { sut.invoke() @@ -57,6 +63,7 @@ class DefaultClearCacheUseCaseTest { setIgnoreRegistrationErrorLambda.assertions().isCalledOnce() .with(value(matrixClient.sessionId), value(false)) assertThat(awaitItem()).isEqualTo(matrixClient.sessionId) + assertThat(seenInvitesStore.seenRoomIds().first()).isEmpty() } } } diff --git a/features/roomlist/impl/build.gradle.kts b/features/roomlist/impl/build.gradle.kts index 87f683d847..a6f09ff4ed 100644 --- a/features/roomlist/impl/build.gradle.kts +++ b/features/roomlist/impl/build.gradle.kts @@ -61,6 +61,9 @@ dependencies { testImplementation(libs.test.truth) testImplementation(libs.test.turbine) 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.featureflag.test) testImplementation(projects.libraries.dateformatter.test) @@ -72,7 +75,5 @@ dependencies { testImplementation(projects.libraries.push.test) testImplementation(projects.services.analytics.test) testImplementation(projects.services.toolbox.test) - testImplementation(projects.features.networkmonitor.test) - testImplementation(projects.features.logout.test) testImplementation(projects.tests.testutils) } diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListContentStateProvider.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListContentStateProvider.kt index 64348f623c..7435424b15 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListContentStateProvider.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListContentStateProvider.kt @@ -11,8 +11,10 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.features.roomlist.impl.model.RoomListRoomSummary import io.element.android.libraries.fullscreenintent.api.FullScreenIntentPermissionsState 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.persistentListOf +import kotlinx.collections.immutable.toPersistentSet open class RoomListContentStateProvider : PreviewParameterProvider { override val values: Sequence @@ -29,10 +31,12 @@ internal fun aRoomsContentState( securityBannerState: SecurityBannerState = SecurityBannerState.None, summaries: ImmutableList = aRoomListRoomSummaryList(), fullScreenIntentPermissionsState: FullScreenIntentPermissionsState = aFullScreenIntentPermissionsState(), + seenRoomInvites: Set = emptySet(), ) = RoomListContentState.Rooms( securityBannerState = securityBannerState, fullScreenIntentPermissionsState = fullScreenIntentPermissionsState, summaries = summaries, + seenRoomInvites = seenRoomInvites.toPersistentSet(), ) internal fun aSkeletonContentState() = RoomListContentState.Skeleton(16) diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt index 660f0f516f..461204b2a2 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt @@ -24,6 +24,7 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshotFlow 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.AcceptDeclineInviteState 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.analyticsproviders.api.trackers.captureInteraction import kotlinx.collections.immutable.toPersistentList +import kotlinx.collections.immutable.toPersistentSet import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.Job @@ -92,6 +94,7 @@ class RoomListPresenter @Inject constructor( private val logoutPresenter: Presenter, private val appPreferencesStore: AppPreferencesStore, private val rageshakeFeatureAvailability: RageshakeFeatureAvailability, + private val seenInvitesStore: SeenInvitesStore, ) : Presenter { private val encryptionService: EncryptionService = client.encryptionService() @@ -227,6 +230,7 @@ class RoomListPresenter @Inject constructor( loadingState == RoomList.LoadingState.NotLoaded || roomSummaries is AsyncData.Loading } } + val seenRoomInvites by remember { seenInvitesStore.seenRoomIds() }.collectAsState(emptySet()) val securityBannerState by rememberSecurityBannerState(securityBannerDismissed) return when { showEmpty -> RoomListContentState.Empty(securityBannerState = securityBannerState) @@ -235,7 +239,8 @@ class RoomListPresenter @Inject constructor( RoomListContentState.Rooms( securityBannerState = securityBannerState, fullScreenIntentPermissionsState = fullScreenIntentPermissionsPresenter.present(), - summaries = roomSummaries.dataOrNull().orEmpty().toPersistentList() + summaries = roomSummaries.dataOrNull().orEmpty().toPersistentList(), + seenRoomInvites = seenRoomInvites.toPersistentSet(), ) } } diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListState.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListState.kt index 4ee15b7fab..ae62b88deb 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListState.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListState.kt @@ -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.user.MatrixUser import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.ImmutableSet @Immutable data class RoomListState( @@ -65,9 +66,11 @@ sealed interface RoomListContentState { data class Empty( val securityBannerState: SecurityBannerState, ) : RoomListContentState + data class Rooms( val securityBannerState: SecurityBannerState, val fullScreenIntentPermissionsState: FullScreenIntentPermissionsState, val summaries: ImmutableList, + val seenRoomInvites: ImmutableSet, ) : RoomListContentState } diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomListContentView.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomListContentView.kt index ebd618185c..acdb762fa7 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomListContentView.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomListContentView.kt @@ -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.selection.FilterSelectionState 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.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.Button @@ -239,6 +240,8 @@ private fun RoomsViewList( ) { index, room -> RoomSummaryRow( room = room, + isInviteSeen = room.displayType == RoomSummaryDisplayType.INVITE && + state.seenRoomInvites.contains(room.roomId), onClick = onRoomClick, eventSink = eventSink, ) diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomSummaryRow.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomSummaryRow.kt index 9ce03ab4cc..bfa255af13 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomSummaryRow.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomSummaryRow.kt @@ -68,6 +68,7 @@ internal val minHeight = 84.dp @Composable internal fun RoomSummaryRow( room: RoomListRoomSummary, + isInviteSeen: Boolean, onClick: (RoomListRoomSummary) -> Unit, eventSink: (RoomListEvents) -> Unit, modifier: Modifier = Modifier, @@ -85,7 +86,7 @@ internal fun RoomSummaryRow( Timber.d("Long click on invite room") }, ) { - InviteNameAndIndicatorRow(name = room.name) + InviteNameAndIndicatorRow(name = room.name, isInviteSeen = isInviteSeen) InviteSubtitle(isDm = room.isDm, inviteSender = room.inviteSender) if (!room.isDm && room.inviteSender != null) { Spacer(modifier = Modifier.height(4.dp)) @@ -300,6 +301,7 @@ private fun LastMessageAndIndicatorRow( @Composable private fun InviteNameAndIndicatorRow( name: String?, + isInviteSeen: Boolean, modifier: Modifier = Modifier, ) { Row( @@ -316,9 +318,11 @@ private fun InviteNameAndIndicatorRow( maxLines = 1, overflow = TextOverflow.Ellipsis ) - UnreadIndicatorAtom( - color = ElementTheme.colors.unreadIndicator - ) + if (!isInviteSeen) { + UnreadIndicatorAtom( + color = ElementTheme.colors.unreadIndicator + ) + } } } @@ -384,6 +388,8 @@ private fun MentionIndicatorAtom() { internal fun RoomSummaryRowPreview(@PreviewParameter(RoomListRoomSummaryProvider::class) data: RoomListRoomSummary) = ElementPreview { RoomSummaryRow( room = data, + // Set isInviteSeen to true for the preview when the room has name "Bob" + isInviteSeen = data.name == "Bob", onClick = {}, eventSink = {}, ) diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/model/RoomListRoomSummary.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/model/RoomListRoomSummary.kt index 498ad762a3..cb4c48d3f7 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/model/RoomListRoomSummary.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/model/RoomListRoomSummary.kt @@ -39,12 +39,10 @@ data class RoomListRoomSummary( ) { val isHighlighted = userDefinedNotificationMode != RoomNotificationMode.MUTE && (numberOfUnreadNotifications > 0 || numberOfUnreadMentions > 0) || - isMarkedUnread || - displayType == RoomSummaryDisplayType.INVITE + isMarkedUnread val hasNewContent = numberOfUnreadMessages > 0 || numberOfUnreadMentions > 0 || numberOfUnreadNotifications > 0 || - isMarkedUnread || - displayType == RoomSummaryDisplayType.INVITE + isMarkedUnread } diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/search/RoomListSearchView.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/search/RoomListSearchView.kt index 90b4fba44c..4f6e783704 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/search/RoomListSearchView.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/search/RoomListSearchView.kt @@ -173,6 +173,8 @@ private fun RoomListSearchContent( ) { room -> RoomSummaryRow( room = room, + // TODO + isInviteSeen = false, onClick = ::onRoomClick, eventSink = eventSink, ) diff --git a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTest.kt b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTest.kt index 5043b78b10..50e7628d09 100644 --- a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTest.kt +++ b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTest.kt @@ -12,9 +12,11 @@ import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat 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.AcceptDeclineInviteState 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.LeaveRoomState import io.element.android.features.leaveroom.api.aLeaveRoomState @@ -169,10 +171,11 @@ class RoomListPresenterTest { val matrixClient = FakeMatrixClient( roomListService = roomListService ) - val presenter = createRoomListPresenter(client = matrixClient) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + val presenter = createRoomListPresenter( + client = matrixClient, + seenInvitesStore = InMemorySeenInvitesStore(setOf(A_ROOM_ID, A_ROOM_ID_2, A_ROOM_ID_3)), + ) + presenter.test { val initialState = consumeItemsUntilPredicate { state -> state.contentState is RoomListContentState.Skeleton }.last() assertThat(initialState.contentState).isInstanceOf(RoomListContentState.Skeleton::class.java) roomListService.postAllRoomsLoadingState(RoomList.LoadingState.Loaded(1)) @@ -194,6 +197,7 @@ class RoomListPresenterTest { timestamp = "0 TimeOrDate true", ) ) + assertThat(withRoomsState.contentAsRooms().seenRoomInvites).containsExactly(A_ROOM_ID, A_ROOM_ID_2, A_ROOM_ID_3) cancelAndIgnoreRemainingEvents() } } @@ -680,6 +684,7 @@ class RoomListPresenterTest { notificationCleaner: NotificationCleaner = FakeNotificationCleaner(), appPreferencesStore: AppPreferencesStore = InMemoryAppPreferencesStore(), rageshakeFeatureAvailability: RageshakeFeatureAvailability = RageshakeFeatureAvailability { true }, + seenInvitesStore: SeenInvitesStore = InMemorySeenInvitesStore() ) = RoomListPresenter( client = client, syncService = syncService, @@ -711,6 +716,7 @@ class RoomListPresenterTest { logoutPresenter = { aDirectLogoutState() }, appPreferencesStore = appPreferencesStore, rageshakeFeatureAvailability = rageshakeFeatureAvailability, + seenInvitesStore = seenInvitesStore, ) } diff --git a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/model/RoomListRoomSummaryTest.kt b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/model/RoomListRoomSummaryTest.kt index caa204328b..a7940a203b 100644 --- a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/model/RoomListRoomSummaryTest.kt +++ b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/model/RoomListRoomSummaryTest.kt @@ -65,12 +65,12 @@ class RoomListRoomSummaryTest { } @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( displayType = RoomSummaryDisplayType.INVITE, ) - assertThat(sut.isHighlighted).isTrue() - assertThat(sut.hasNewContent).isTrue() + assertThat(sut.isHighlighted).isFalse() + assertThat(sut.hasNewContent).isFalse() } } diff --git a/tests/uitests/src/test/snapshots/images/features.roomlist.impl.components_RoomSummaryRow_Day_30_en.png b/tests/uitests/src/test/snapshots/images/features.roomlist.impl.components_RoomSummaryRow_Day_30_en.png index 5ee881449b..c5f164e6cd 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomlist.impl.components_RoomSummaryRow_Day_30_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomlist.impl.components_RoomSummaryRow_Day_30_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f64d2899886ab2e20bbd2dd0e0e2bd909f461751e60f4b6d93e506963c41ce03 -size 15832 +oid sha256:a92aa815f9c74893ea1b9b98912f0eefd86159af900ece588a3eb426264d076a +size 15473 diff --git a/tests/uitests/src/test/snapshots/images/features.roomlist.impl.components_RoomSummaryRow_Night_30_en.png b/tests/uitests/src/test/snapshots/images/features.roomlist.impl.components_RoomSummaryRow_Night_30_en.png index 4ec7b36e3b..c4379b0b60 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomlist.impl.components_RoomSummaryRow_Night_30_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomlist.impl.components_RoomSummaryRow_Night_30_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:20a0dfc19f05e9727cf70cf7eaf83503c805423cc36edbefaa119c2692eaadd2 -size 15678 +oid sha256:8287ed2eda7e0d486f6122c5bd61517f7f6ccc5ea21413f2eea085683d15ba68 +size 15370