Ignore/Unignore: makes more sense to be at the client level than room

This commit is contained in:
ganfra 2023-04-27 22:13:25 +02:00
parent ccfe0bb10c
commit 884cdb9575
10 changed files with 60 additions and 35 deletions

View file

@ -49,7 +49,7 @@ object RoomMemberProvidesModule {
): RoomMemberDetailsPresenter.Factory { ): RoomMemberDetailsPresenter.Factory {
return object : RoomMemberDetailsPresenter.Factory { return object : RoomMemberDetailsPresenter.Factory {
override fun create(roomMember: RoomMember): RoomMemberDetailsPresenter { override fun create(roomMember: RoomMember): RoomMemberDetailsPresenter {
return RoomMemberDetailsPresenter(matrixClient.sessionId, room, roomMember) return RoomMemberDetailsPresenter(matrixClient, room, roomMember)
} }
} }
} }

View file

@ -28,6 +28,7 @@ import dagger.assisted.Assisted
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsState.ConfirmationDialog import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsState.ConfirmationDialog
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.SessionId 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.room.MatrixRoom import io.element.android.libraries.matrix.api.room.MatrixRoom
@ -36,7 +37,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class RoomMemberDetailsPresenter @AssistedInject constructor( class RoomMemberDetailsPresenter @AssistedInject constructor(
private val currentUserSessionId: SessionId, private val client: MatrixClient,
private val room: MatrixRoom, private val room: MatrixRoom,
@Assisted private val roomMember: RoomMember, @Assisted private val roomMember: RoomMember,
) : Presenter<RoomMemberDetailsState> { ) : Presenter<RoomMemberDetailsState> {
@ -91,16 +92,16 @@ class RoomMemberDetailsPresenter @AssistedInject constructor(
avatarUrl = userAvatar, avatarUrl = userAvatar,
isBlocked = isBlocked.value, isBlocked = isBlocked.value,
displayConfirmationDialog = confirmationDialog, displayConfirmationDialog = confirmationDialog,
isCurrentUser = roomMember.userId == currentUserSessionId, isCurrentUser = roomMember.userId == client.sessionId,
eventSink = ::handleEvents eventSink = ::handleEvents
) )
} }
private fun CoroutineScope.blockUser(userId: UserId, isBlockedState: MutableState<Boolean>) = launch { private fun CoroutineScope.blockUser(userId: UserId, isBlockedState: MutableState<Boolean>) = launch {
room.ignoreUser(userId).onSuccess { isBlockedState.value = true } client.ignoreUser(userId).onSuccess { isBlockedState.value = true }
} }
private fun CoroutineScope.unblockUser(userId: UserId, isBlockedState: MutableState<Boolean>) = launch { private fun CoroutineScope.unblockUser(userId: UserId, isBlockedState: MutableState<Boolean>) = launch {
room.unignoreUser(userId).onSuccess { isBlockedState.value = false } client.unignoreUser(userId).onSuccess { isBlockedState.value = false }
} }
} }

View file

@ -27,6 +27,7 @@ import io.element.android.features.roomdetails.impl.RoomDetailsType
import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsPresenter import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsPresenter
import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Async
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.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.room.MatrixRoom import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState
@ -39,6 +40,7 @@ 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
import io.element.android.libraries.matrix.test.A_USER_ID_2 import io.element.android.libraries.matrix.test.A_USER_ID_2
import io.element.android.libraries.matrix.test.FakeMatrixClient
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
import io.element.android.tests.testutils.testCoroutineDispatchers import io.element.android.tests.testutils.testCoroutineDispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
@ -57,7 +59,7 @@ class RoomDetailsPresenterTests {
private fun aRoomDetailsPresenter(room: MatrixRoom): RoomDetailsPresenter { private fun aRoomDetailsPresenter(room: MatrixRoom): RoomDetailsPresenter {
val roomMemberDetailsPresenterFactory = object : RoomMemberDetailsPresenter.Factory { val roomMemberDetailsPresenterFactory = object : RoomMemberDetailsPresenter.Factory {
override fun create(roomMember: RoomMember): RoomMemberDetailsPresenter { override fun create(roomMember: RoomMember): RoomMemberDetailsPresenter {
return RoomMemberDetailsPresenter(A_SESSION_ID, room, roomMember) return RoomMemberDetailsPresenter(aMatrixClient(), room, roomMember)
} }
} }
return RoomDetailsPresenter(room, roomMembershipObserver, testCoroutineDispatchers, roomMemberDetailsPresenterFactory) return RoomDetailsPresenter(room, roomMembershipObserver, testCoroutineDispatchers, roomMemberDetailsPresenterFactory)
@ -240,6 +242,10 @@ class RoomDetailsPresenterTests {
} }
} }
fun aMatrixClient(
sessionId: SessionId = A_SESSION_ID,
) = FakeMatrixClient()
fun aMatrixRoom( fun aMatrixRoom(
roomId: RoomId = A_ROOM_ID, roomId: RoomId = A_ROOM_ID,
name: String? = A_ROOM_NAME, name: String? = A_ROOM_NAME,

View file

@ -20,12 +20,12 @@ import app.cash.molecule.RecompositionClock
import app.cash.molecule.moleculeFlow import app.cash.molecule.moleculeFlow
import app.cash.turbine.test import app.cash.turbine.test
import com.google.common.truth.Truth import com.google.common.truth.Truth
import io.element.android.features.roomdetails.aMatrixClient
import io.element.android.features.roomdetails.aMatrixRoom import io.element.android.features.roomdetails.aMatrixRoom
import io.element.android.features.roomdetails.aRoomMember import io.element.android.features.roomdetails.aRoomMember
import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsEvents import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsEvents
import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsPresenter import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsPresenter
import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsState import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsState
import io.element.android.libraries.matrix.test.A_SESSION_ID
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import org.junit.Test import org.junit.Test
@ -33,6 +33,8 @@ import org.junit.Test
@ExperimentalCoroutinesApi @ExperimentalCoroutinesApi
class RoomMemberDetailsPresenterTests { class RoomMemberDetailsPresenterTests {
private val matrixClient = aMatrixClient()
@Test @Test
fun `present - returns the room member's data, then updates it if needed`() = runTest { fun `present - returns the room member's data, then updates it if needed`() = runTest {
val room = aMatrixRoom().apply { val room = aMatrixRoom().apply {
@ -40,7 +42,7 @@ class RoomMemberDetailsPresenterTests {
givenUserAvatarUrlResult(Result.success("A custom avatar")) givenUserAvatarUrlResult(Result.success("A custom avatar"))
} }
val roomMember = aRoomMember(displayName = "Alice") val roomMember = aRoomMember(displayName = "Alice")
val presenter = RoomMemberDetailsPresenter(A_SESSION_ID, room, roomMember) val presenter = RoomMemberDetailsPresenter(matrixClient, room, roomMember)
moleculeFlow(RecompositionClock.Immediate) { moleculeFlow(RecompositionClock.Immediate) {
presenter.present() presenter.present()
}.test { }.test {
@ -63,7 +65,7 @@ class RoomMemberDetailsPresenterTests {
givenUserAvatarUrlResult(Result.failure(Throwable())) givenUserAvatarUrlResult(Result.failure(Throwable()))
} }
val roomMember = aRoomMember(displayName = "Alice") val roomMember = aRoomMember(displayName = "Alice")
val presenter = RoomMemberDetailsPresenter(A_SESSION_ID, room, roomMember) val presenter = RoomMemberDetailsPresenter(matrixClient, room, roomMember)
moleculeFlow(RecompositionClock.Immediate) { moleculeFlow(RecompositionClock.Immediate) {
presenter.present() presenter.present()
}.test { }.test {
@ -82,7 +84,7 @@ class RoomMemberDetailsPresenterTests {
givenUserAvatarUrlResult(Result.success(null)) givenUserAvatarUrlResult(Result.success(null))
} }
val roomMember = aRoomMember(displayName = "Alice") val roomMember = aRoomMember(displayName = "Alice")
val presenter = RoomMemberDetailsPresenter(A_SESSION_ID, room, roomMember) val presenter = RoomMemberDetailsPresenter(matrixClient, room, roomMember)
moleculeFlow(RecompositionClock.Immediate) { moleculeFlow(RecompositionClock.Immediate) {
presenter.present() presenter.present()
}.test { }.test {
@ -98,7 +100,7 @@ class RoomMemberDetailsPresenterTests {
fun `present - BlockUser needing confirmation displays confirmation dialog`() = runTest { fun `present - BlockUser needing confirmation displays confirmation dialog`() = runTest {
val room = aMatrixRoom() val room = aMatrixRoom()
val roomMember = aRoomMember() val roomMember = aRoomMember()
val presenter = RoomMemberDetailsPresenter(A_SESSION_ID, room, roomMember) val presenter = RoomMemberDetailsPresenter(matrixClient, room, roomMember)
moleculeFlow(RecompositionClock.Immediate) { moleculeFlow(RecompositionClock.Immediate) {
presenter.present() presenter.present()
}.test { }.test {
@ -119,7 +121,7 @@ class RoomMemberDetailsPresenterTests {
fun `present - BlockUser and UnblockUser without confirmation change the 'blocked' state`() = runTest { fun `present - BlockUser and UnblockUser without confirmation change the 'blocked' state`() = runTest {
val room = aMatrixRoom() val room = aMatrixRoom()
val roomMember = aRoomMember() val roomMember = aRoomMember()
val presenter = RoomMemberDetailsPresenter(A_SESSION_ID, room, roomMember) val presenter = RoomMemberDetailsPresenter(matrixClient, room, roomMember)
moleculeFlow(RecompositionClock.Immediate) { moleculeFlow(RecompositionClock.Immediate) {
presenter.present() presenter.present()
}.test { }.test {
@ -136,7 +138,7 @@ class RoomMemberDetailsPresenterTests {
fun `present - UnblockUser needing confirmation displays confirmation dialog`() = runTest { fun `present - UnblockUser needing confirmation displays confirmation dialog`() = runTest {
val room = aMatrixRoom() val room = aMatrixRoom()
val roomMember = aRoomMember() val roomMember = aRoomMember()
val presenter = RoomMemberDetailsPresenter(A_SESSION_ID, room, roomMember) val presenter = RoomMemberDetailsPresenter(matrixClient, room, roomMember)
moleculeFlow(RecompositionClock.Immediate) { moleculeFlow(RecompositionClock.Immediate) {
presenter.present() presenter.present()
}.test { }.test {

View file

@ -35,6 +35,8 @@ interface MatrixClient : Closeable {
val invitesDataSource: RoomSummaryDataSource val invitesDataSource: RoomSummaryDataSource
fun getRoom(roomId: RoomId): MatrixRoom? fun getRoom(roomId: RoomId): MatrixRoom?
fun findDM(userId: UserId): MatrixRoom? fun findDM(userId: UserId): MatrixRoom?
suspend fun ignoreUser(userId: UserId): Result<Unit>
suspend fun unignoreUser(userId: UserId): Result<Unit>
suspend fun createRoom(createRoomParams: CreateRoomParameters): Result<RoomId> suspend fun createRoom(createRoomParams: CreateRoomParameters): Result<RoomId>
suspend fun createDM(userId: UserId): Result<RoomId> suspend fun createDM(userId: UserId): Result<RoomId>
fun startSync() fun startSync()

View file

@ -68,10 +68,6 @@ interface MatrixRoom : Closeable {
suspend fun redactEvent(eventId: EventId, reason: String? = null): Result<Unit> suspend fun redactEvent(eventId: EventId, reason: String? = null): Result<Unit>
suspend fun ignoreUser(userId: UserId): Result<Unit>
suspend fun unignoreUser(userId: UserId): Result<Unit>
suspend fun leave(): Result<Unit> suspend fun leave(): Result<Unit>
suspend fun acceptInvitation(): Result<Unit> suspend fun acceptInvitation(): Result<Unit>

View file

@ -212,6 +212,18 @@ class RustMatrixClient constructor(
return roomId?.let { getRoom(it) } return roomId?.let { getRoom(it) }
} }
override suspend fun ignoreUser(userId: UserId): Result<Unit> = withContext(dispatchers.io) {
runCatching {
client.ignoreUser(userId.value)
}
}
override suspend fun unignoreUser(userId: UserId): Result<Unit> = withContext(dispatchers.io) {
runCatching {
client.unignoreUser(userId.value)
}
}
override suspend fun createRoom(createRoomParams: CreateRoomParameters): Result<RoomId> = withContext(dispatchers.io) { override suspend fun createRoom(createRoomParams: CreateRoomParameters): Result<RoomId> = withContext(dispatchers.io) {
runCatching { runCatching {
val rustParams = RustCreateRoomParameters( val rustParams = RustCreateRoomParameters(

View file

@ -38,6 +38,7 @@ import org.matrix.rustcomponents.sdk.SlidingSyncRoom
import org.matrix.rustcomponents.sdk.UpdateSummary import org.matrix.rustcomponents.sdk.UpdateSummary
import org.matrix.rustcomponents.sdk.genTransactionId import org.matrix.rustcomponents.sdk.genTransactionId
import org.matrix.rustcomponents.sdk.messageEventContentFromMarkdown import org.matrix.rustcomponents.sdk.messageEventContentFromMarkdown
import org.matrix.rustcomponents.sdk.use
import org.matrix.rustcomponents.sdk.RoomMember as RustRoomMember import org.matrix.rustcomponents.sdk.RoomMember as RustRoomMember
class RustMatrixRoom( class RustMatrixRoom(
@ -200,19 +201,10 @@ class RustMatrixRoom(
} }
} }
override suspend fun ignoreUser(userId: UserId): Result<Unit> { private fun findRoomMember(userId: UserId, action: (RustRoomMember).() -> Unit) {
return runCatching { return innerRoom.members()
getRustMember(userId)?.ignore() ?: error("No member with userId $userId exists in room $roomId") .find { it.userId() == userId.value }
} ?.use(action)
} ?: error("No member with userId $userId exists in room $roomId")
override suspend fun unignoreUser(userId: UserId): Result<Unit> {
return runCatching {
getRustMember(userId)?.unignore() ?: error("No member with userId $userId exists in room $roomId")
}
}
private fun getRustMember(userId: UserId): RustRoomMember? {
return innerRoom.members().find { it.userId() == userId.value }
} }
} }

View file

@ -47,6 +47,8 @@ class FakeMatrixClient(
private val notificationService: FakeNotificationService = FakeNotificationService(), private val notificationService: FakeNotificationService = FakeNotificationService(),
) : MatrixClient { ) : MatrixClient {
private var ignoreUserResult: Result<Unit> = Result.success(Unit)
private var unignoreUserResult: Result<Unit> = Result.success(Unit)
private var createRoomResult: Result<RoomId> = Result.success(A_ROOM_ID) private var createRoomResult: Result<RoomId> = Result.success(A_ROOM_ID)
private var createDmResult: Result<RoomId> = Result.success(A_ROOM_ID) private var createDmResult: Result<RoomId> = Result.success(A_ROOM_ID)
private var createDmFailure: Throwable? = null private var createDmFailure: Throwable? = null
@ -62,6 +64,14 @@ class FakeMatrixClient(
return findDmResult return findDmResult
} }
override suspend fun ignoreUser(userId: UserId): Result<Unit> {
return ignoreUserResult
}
override suspend fun unignoreUser(userId: UserId): Result<Unit> {
return unignoreUserResult
}
override suspend fun createRoom(createRoomParams: CreateRoomParameters): Result<RoomId> { override suspend fun createRoom(createRoomParams: CreateRoomParameters): Result<RoomId> {
delay(100) delay(100)
return createRoomResult return createRoomResult
@ -130,6 +140,14 @@ class FakeMatrixClient(
createDmResult = result createDmResult = result
} }
fun givenIgnoreUserResult(result: Result<Unit>) {
ignoreUserResult = result
}
fun givenUnignoreUserResult(result: Result<Unit>) {
unignoreUserResult = result
}
fun givenCreateDmError(failure: Throwable?) { fun givenCreateDmError(failure: Throwable?) {
createDmFailure = failure createDmFailure = failure
} }

View file

@ -117,10 +117,6 @@ class FakeMatrixRoom(
return Result.success(Unit) return Result.success(Unit)
} }
override suspend fun ignoreUser(userId: UserId): Result<Unit> = ignoreResult
override suspend fun unignoreUser(userId: UserId): Result<Unit> = unignoreResult
override suspend fun leave(): Result<Unit> = leaveRoomError?.let { Result.failure(it) } ?: Result.success(Unit) override suspend fun leave(): Result<Unit> = leaveRoomError?.let { Result.failure(it) } ?: Result.success(Unit)
override suspend fun acceptInvitation(): Result<Unit> { override suspend fun acceptInvitation(): Result<Unit> {
isInviteAccepted = true isInviteAccepted = true