From 69d0a5c7b15f3f8cfa328e2c2e1bcd37db776c72 Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 29 Nov 2023 16:16:09 +0100 Subject: [PATCH 1/6] Creates a startDM method so we can reuse it for the new flow --- .../impl/root/CreateRoomRootPresenter.kt | 26 +++++------ .../libraries/matrix/api/room/StartDM.kt | 43 +++++++++++++++++++ .../src/main/res/values/localazy.xml | 4 +- 3 files changed, 57 insertions(+), 16 deletions(-) create mode 100644 libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/StartDM.kt diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootPresenter.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootPresenter.kt index 839e4cd69e..bdccf3d625 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootPresenter.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootPresenter.kt @@ -28,10 +28,11 @@ import io.element.android.features.createroom.impl.userlist.UserListPresenter import io.element.android.features.createroom.impl.userlist.UserListPresenterArgs import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Presenter -import io.element.android.libraries.architecture.runCatchingUpdatingState import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.room.StartDMResult +import io.element.android.libraries.matrix.api.room.startDM import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.usersearch.api.UserRepository import io.element.android.services.analytics.api.AnalyticsService @@ -79,19 +80,18 @@ class CreateRoomRootPresenter @Inject constructor( } private fun CoroutineScope.startDm(matrixUser: MatrixUser, startDmAction: MutableState>) = launch { - suspend { - matrixClient.findDM(matrixUser.userId).use { existingDM -> - existingDM?.roomId ?: createDM(matrixUser) + startDmAction.value = Async.Loading() + when (val result = matrixClient.startDM(matrixUser)) { + is StartDMResult.Success -> { + if (result.isNew) { + analyticsService.capture(CreatedRoom(isDM = true)) + } + startDmAction.value = Async.Success(result.roomId) } - }.runCatchingUpdatingState(startDmAction) - } + is StartDMResult.Failure -> { + startDmAction.value = Async.Failure(result) + } + } - private suspend fun createDM(user: MatrixUser): RoomId { - return matrixClient - .createDM(user.userId) - .onSuccess { - analyticsService.capture(CreatedRoom(isDM = true)) - } - .getOrThrow() } } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/StartDM.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/StartDM.kt new file mode 100644 index 0000000000..4aa280513b --- /dev/null +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/StartDM.kt @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.matrix.api.room + +import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.user.MatrixUser + +/** + * Try to find an existing DM with the given user, or create one if none exists. + */ +suspend fun MatrixClient.startDM(matrixUser: MatrixUser): StartDMResult { + val existingRoomId = findDM(matrixUser.userId)?.use { existingDM -> + existingDM.roomId + } + return if (existingRoomId != null) { + StartDMResult.Success(existingRoomId, isNew = false) + } else { + createDM(matrixUser.userId).fold( + { StartDMResult.Success(it, isNew = true) }, + { StartDMResult.Failure(it.localizedMessage) } + ) + } +} + +sealed interface StartDMResult { + data class Success(val roomId: RoomId, val isNew: Boolean) : StartDMResult + data class Failure(override val message: String?) : StartDMResult, Exception(message) +} diff --git a/libraries/ui-strings/src/main/res/values/localazy.xml b/libraries/ui-strings/src/main/res/values/localazy.xml index c816850f98..638f4634e3 100644 --- a/libraries/ui-strings/src/main/res/values/localazy.xml +++ b/libraries/ui-strings/src/main/res/values/localazy.xml @@ -109,6 +109,7 @@ "Dark" "Decryption error" "Developer options" + "Direct chat" "(edited)" "Editing" "* %1$s %2$s" @@ -227,9 +228,6 @@ "%d votes" "Rageshake to report bug" - "Are you sure you want to delete this poll?" - "Delete Poll" - "Edit poll" "Failed selecting media, please try again." "Failed processing media to upload, please try again." "Failed uploading media, please try again." From 118f1c14594d49f779439432e4f3164765c8b445 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 30 Nov 2023 13:07:24 +0100 Subject: [PATCH 2/6] Add StartDMAction to further share code --- .../features/createroom/api/StartDMAction.kt | 31 +++++++++++ .../createroom/impl/DefaultStartDMAction.kt | 54 +++++++++++++++++++ .../impl/root/CreateRoomRootPresenter.kt | 37 +++---------- .../libraries/matrix/api/room/StartDM.kt | 8 +-- 4 files changed, 97 insertions(+), 33 deletions(-) create mode 100644 features/createroom/api/src/main/kotlin/io/element/android/features/createroom/api/StartDMAction.kt create mode 100644 features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/DefaultStartDMAction.kt diff --git a/features/createroom/api/src/main/kotlin/io/element/android/features/createroom/api/StartDMAction.kt b/features/createroom/api/src/main/kotlin/io/element/android/features/createroom/api/StartDMAction.kt new file mode 100644 index 0000000000..ef95f15dce --- /dev/null +++ b/features/createroom/api/src/main/kotlin/io/element/android/features/createroom/api/StartDMAction.kt @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.createroom.api + +import androidx.compose.runtime.MutableState +import io.element.android.libraries.architecture.Async +import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.core.UserId + +interface StartDMAction { + /** + * Try to find an existing DM with the given user, or create one if none exists. + * @param userId The user to start a DM with. + * @param actionState The state to update with the result of the action. + */ + suspend fun execute(userId: UserId, actionState: MutableState>) +} diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/DefaultStartDMAction.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/DefaultStartDMAction.kt new file mode 100644 index 0000000000..0aa2e93dcb --- /dev/null +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/DefaultStartDMAction.kt @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.createroom.impl + +import androidx.compose.runtime.MutableState +import com.squareup.anvil.annotations.ContributesBinding +import im.vector.app.features.analytics.plan.CreatedRoom +import io.element.android.features.createroom.api.StartDMAction +import io.element.android.libraries.architecture.Async +import io.element.android.libraries.di.AppScope +import io.element.android.libraries.di.SessionScope +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.UserId +import io.element.android.libraries.matrix.api.room.StartDMResult +import io.element.android.libraries.matrix.api.room.startDM +import io.element.android.services.analytics.api.AnalyticsService +import javax.inject.Inject + +@ContributesBinding(SessionScope::class) +class DefaultStartDMAction @Inject constructor( + private val matrixClient: MatrixClient, + private val analyticsService: AnalyticsService, +) : StartDMAction { + + override suspend fun execute(userId: UserId, actionState: MutableState>) { + actionState.value = Async.Loading() + when (val result = matrixClient.startDM(userId)) { + is StartDMResult.Success -> { + if (result.isNew) { + analyticsService.capture(CreatedRoom(isDM = true)) + } + actionState.value = Async.Success(result.roomId) + } + is StartDMResult.Failure -> { + actionState.value = Async.Failure(result) + } + } + } +} diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootPresenter.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootPresenter.kt index bdccf3d625..9b60924ca4 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootPresenter.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootPresenter.kt @@ -21,7 +21,7 @@ import androidx.compose.runtime.MutableState import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope -import im.vector.app.features.analytics.plan.CreatedRoom +import io.element.android.features.createroom.api.StartDMAction import io.element.android.features.createroom.impl.userlist.SelectionMode import io.element.android.features.createroom.impl.userlist.UserListDataStore import io.element.android.features.createroom.impl.userlist.UserListPresenter @@ -29,14 +29,8 @@ import io.element.android.features.createroom.impl.userlist.UserListPresenterArg import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.core.meta.BuildMeta -import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.RoomId -import io.element.android.libraries.matrix.api.room.StartDMResult -import io.element.android.libraries.matrix.api.room.startDM -import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.usersearch.api.UserRepository -import io.element.android.services.analytics.api.AnalyticsService -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import javax.inject.Inject @@ -44,8 +38,7 @@ class CreateRoomRootPresenter @Inject constructor( presenterFactory: UserListPresenter.Factory, userRepository: UserRepository, userListDataStore: UserListDataStore, - private val matrixClient: MatrixClient, - private val analyticsService: AnalyticsService, + private val startDMAction: StartDMAction, private val buildMeta: BuildMeta, ) : Presenter { @@ -62,36 +55,22 @@ class CreateRoomRootPresenter @Inject constructor( val userListState = presenter.present() val localCoroutineScope = rememberCoroutineScope() - val startDmAction: MutableState> = remember { mutableStateOf(Async.Uninitialized) } + val startDmActionState: MutableState> = remember { mutableStateOf(Async.Uninitialized) } fun handleEvents(event: CreateRoomRootEvents) { when (event) { - is CreateRoomRootEvents.StartDM -> localCoroutineScope.startDm(event.matrixUser, startDmAction) - CreateRoomRootEvents.CancelStartDM -> startDmAction.value = Async.Uninitialized + is CreateRoomRootEvents.StartDM -> localCoroutineScope.launch { + startDMAction.execute(event.matrixUser.userId, startDmActionState) + } + CreateRoomRootEvents.CancelStartDM -> startDmActionState.value = Async.Uninitialized } } return CreateRoomRootState( applicationName = buildMeta.applicationName, userListState = userListState, - startDmAction = startDmAction.value, + startDmAction = startDmActionState.value, eventSink = ::handleEvents, ) } - - private fun CoroutineScope.startDm(matrixUser: MatrixUser, startDmAction: MutableState>) = launch { - startDmAction.value = Async.Loading() - when (val result = matrixClient.startDM(matrixUser)) { - is StartDMResult.Success -> { - if (result.isNew) { - analyticsService.capture(CreatedRoom(isDM = true)) - } - startDmAction.value = Async.Success(result.roomId) - } - is StartDMResult.Failure -> { - startDmAction.value = Async.Failure(result) - } - } - - } } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/StartDM.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/StartDM.kt index 4aa280513b..722f598d51 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/StartDM.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/StartDM.kt @@ -18,19 +18,19 @@ package io.element.android.libraries.matrix.api.room import io.element.android.libraries.matrix.api.MatrixClient 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.core.UserId /** * Try to find an existing DM with the given user, or create one if none exists. */ -suspend fun MatrixClient.startDM(matrixUser: MatrixUser): StartDMResult { - val existingRoomId = findDM(matrixUser.userId)?.use { existingDM -> +suspend fun MatrixClient.startDM(userId: UserId): StartDMResult { + val existingRoomId = findDM(userId)?.use { existingDM -> existingDM.roomId } return if (existingRoomId != null) { StartDMResult.Success(existingRoomId, isNew = false) } else { - createDM(matrixUser.userId).fold( + createDM(userId).fold( { StartDMResult.Success(it, isNew = true) }, { StartDMResult.Failure(it.localizedMessage) } ) From b57a94943f4ffbcf2e00381418b1e1b3da8cbd2b Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 30 Nov 2023 13:07:50 +0100 Subject: [PATCH 3/6] Branch StartDM in the RoomMemberDetails screen --- .../android/appnav/LoggedInFlowNode.kt | 4 ++ .../android/appnav/room/RoomLoadedFlowNode.kt | 5 ++ .../roomdetails/api/RoomDetailsEntryPoint.kt | 2 + features/roomdetails/impl/build.gradle.kts | 1 + .../roomdetails/impl/RoomDetailsFlowNode.kt | 5 ++ .../impl/blockuser/BlockUserSection.kt | 1 + .../roomdetails/impl/di/RoomMemberModule.kt | 4 +- .../details/RoomMemberDetailsEvents.kt | 2 + .../members/details/RoomMemberDetailsNode.kt | 17 ++++++- .../details/RoomMemberDetailsPresenter.kt | 18 ++++++- .../members/details/RoomMemberDetailsState.kt | 2 + .../details/RoomMemberDetailsStateProvider.kt | 1 + .../members/details/RoomMemberDetailsView.kt | 47 +++++++++++++------ 13 files changed, 91 insertions(+), 18 deletions(-) diff --git a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt index 23c35b119a..80c1a06737 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt @@ -256,6 +256,10 @@ class LoggedInFlowNode @AssistedInject constructor( } is NavTarget.Room -> { val callback = object : RoomLoadedFlowNode.Callback { + override fun onOpenRoom(roomId: RoomId) { + backstack.push(NavTarget.Room(roomId)) + } + override fun onForwardedToSingleRoom(roomId: RoomId) { coroutineScope.launch { attachRoom(roomId) } } diff --git a/appnav/src/main/kotlin/io/element/android/appnav/room/RoomLoadedFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/room/RoomLoadedFlowNode.kt index 613ed650c8..3cc405d7fd 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/room/RoomLoadedFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/room/RoomLoadedFlowNode.kt @@ -74,6 +74,7 @@ class RoomLoadedFlowNode @AssistedInject constructor( ), DaggerComponentOwner { interface Callback : Plugin { + fun onOpenRoom(roomId: RoomId) fun onForwardedToSingleRoom(roomId: RoomId) fun onOpenGlobalNotificationSettings() } @@ -134,6 +135,10 @@ class RoomLoadedFlowNode @AssistedInject constructor( override fun onOpenGlobalNotificationSettings() { callbacks.forEach { it.onOpenGlobalNotificationSettings() } } + + override fun onOpenRoom(roomId: RoomId) { + callbacks.forEach { it.onOpenRoom(roomId) } + } } return roomDetailsEntryPoint.nodeBuilder(this, buildContext) .params(RoomDetailsEntryPoint.Params(initialTarget)) diff --git a/features/roomdetails/api/src/main/kotlin/io/element/android/features/roomdetails/api/RoomDetailsEntryPoint.kt b/features/roomdetails/api/src/main/kotlin/io/element/android/features/roomdetails/api/RoomDetailsEntryPoint.kt index 0aaac324da..bd1a414294 100644 --- a/features/roomdetails/api/src/main/kotlin/io/element/android/features/roomdetails/api/RoomDetailsEntryPoint.kt +++ b/features/roomdetails/api/src/main/kotlin/io/element/android/features/roomdetails/api/RoomDetailsEntryPoint.kt @@ -22,6 +22,7 @@ import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin import io.element.android.libraries.architecture.FeatureEntryPoint import io.element.android.libraries.architecture.NodeInputs +import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.UserId import kotlinx.parcelize.Parcelize @@ -42,6 +43,7 @@ interface RoomDetailsEntryPoint : FeatureEntryPoint { interface Callback : Plugin { fun onOpenGlobalNotificationSettings() + fun onOpenRoom(roomId: RoomId) } interface NodeBuilder { diff --git a/features/roomdetails/impl/build.gradle.kts b/features/roomdetails/impl/build.gradle.kts index e35526ec37..44cbbb5cbb 100644 --- a/features/roomdetails/impl/build.gradle.kts +++ b/features/roomdetails/impl/build.gradle.kts @@ -51,6 +51,7 @@ dependencies { api(projects.services.apperror.api) implementation(libs.coil.compose) implementation(projects.features.leaveroom.api) + implementation(projects.features.createroom.api) implementation(projects.services.analytics.api) testImplementation(libs.test.junit) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt index 60e302494e..27ddf47f92 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt @@ -41,6 +41,7 @@ import io.element.android.libraries.architecture.animation.rememberDefaultTransi import io.element.android.libraries.architecture.createNode import io.element.android.libraries.core.mimetype.MimeTypes import io.element.android.libraries.di.RoomScope +import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.media.MediaSource import io.element.android.libraries.mediaviewer.api.local.MediaInfo @@ -152,6 +153,10 @@ class RoomDetailsFlowNode @AssistedInject constructor( override fun openAvatarPreview(username: String, avatarUrl: String) { backstack.push(NavTarget.MemberAvatarPreview(username, avatarUrl)) } + + override fun onStartDM(roomId: RoomId) { + plugins().forEach { it.onOpenRoom(roomId) } + } } val plugins = listOf(RoomMemberDetailsNode.RoomMemberDetailsInput(navTarget.roomMemberId), callback) createNode(buildContext, plugins) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/blockuser/BlockUserSection.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/blockuser/BlockUserSection.kt index 532e1a8a25..b99319812b 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/blockuser/BlockUserSection.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/blockuser/BlockUserSection.kt @@ -85,6 +85,7 @@ private fun PreferenceBlockUser( leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Block)), onClick = { if (!isLoading) eventSink(RoomMemberDetailsEvents.UnblockUser(needsConfirmation = true)) }, trailingContent = if (isLoading) ListItemContent.Custom(loadingCurrentValue) else null, + style = ListItemStyle.Primary, modifier = modifier, ) } else { diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/di/RoomMemberModule.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/di/RoomMemberModule.kt index ca462c6507..c65b63432e 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/di/RoomMemberModule.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/di/RoomMemberModule.kt @@ -19,6 +19,7 @@ package io.element.android.features.roomdetails.impl.di import com.squareup.anvil.annotations.ContributesTo import dagger.Module import dagger.Provides +import io.element.android.features.createroom.api.StartDMAction import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsPresenter import io.element.android.libraries.di.RoomScope import io.element.android.libraries.matrix.api.MatrixClient @@ -33,10 +34,11 @@ object RoomMemberModule { fun provideRoomMemberDetailsPresenterFactory( matrixClient: MatrixClient, room: MatrixRoom, + startDMAction: StartDMAction, ): RoomMemberDetailsPresenter.Factory { return object : RoomMemberDetailsPresenter.Factory { override fun create(roomMemberId: UserId): RoomMemberDetailsPresenter { - return RoomMemberDetailsPresenter(matrixClient, room, roomMemberId) + return RoomMemberDetailsPresenter(roomMemberId, matrixClient, room, startDMAction) } } } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsEvents.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsEvents.kt index 05688c6cf7..75c66f26e2 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsEvents.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsEvents.kt @@ -17,6 +17,8 @@ package io.element.android.features.roomdetails.impl.members.details sealed interface RoomMemberDetailsEvents { + data object StartDM : RoomMemberDetailsEvents + data object ClearStartDMState : RoomMemberDetailsEvents data class BlockUser(val needsConfirmation: Boolean = false) : RoomMemberDetailsEvents data class UnblockUser(val needsConfirmation: Boolean = false) : RoomMemberDetailsEvents data object ClearBlockUserError : RoomMemberDetailsEvents diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsNode.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsNode.kt index 8d20ddaf77..3037136f63 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsNode.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsNode.kt @@ -17,6 +17,7 @@ package io.element.android.features.roomdetails.impl.members.details import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import com.bumble.appyx.core.lifecycle.subscribe @@ -29,9 +30,11 @@ import im.vector.app.features.analytics.plan.MobileScreen import io.element.android.anvilannotations.ContributesNode import io.element.android.features.roomdetails.impl.R import io.element.android.libraries.androidutils.system.startSharePlainTextIntent +import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.NodeInputs import io.element.android.libraries.architecture.inputs import io.element.android.libraries.di.RoomScope +import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.permalink.PermalinkBuilder import io.element.android.services.analytics.api.AnalyticsService @@ -46,8 +49,9 @@ class RoomMemberDetailsNode @AssistedInject constructor( presenterFactory: RoomMemberDetailsPresenter.Factory, ) : Node(buildContext, plugins = plugins) { - interface Callback: NodeInputs { + interface Callback : NodeInputs { fun openAvatarPreview(username: String, avatarUrl: String) + fun onStartDM(roomId: RoomId) } data class RoomMemberDetailsInput( @@ -84,12 +88,23 @@ class RoomMemberDetailsNode @AssistedInject constructor( } } + fun onStartDM(roomId: RoomId) { + callback.onStartDM(roomId) + } + val state = presenter.present() + + LaunchedEffect(state.startDmActionState) { + if (state.startDmActionState is Async.Success) { + onStartDM(state.startDmActionState.data) + } + } RoomMemberDetailsView( state = state, modifier = modifier, goBack = this::navigateUp, onShareUser = ::onShareUser, + onDMStarted = ::onStartDM, openAvatarPreview = callback::openAvatarPreview, ) } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsPresenter.kt index 3be83a2fef..463fcf186a 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsPresenter.kt @@ -25,13 +25,18 @@ import androidx.compose.runtime.produceState import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue +import com.squareup.anvil.annotations.ContributesBinding import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject +import io.element.android.features.createroom.api.StartDMAction import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsState.ConfirmationDialog import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.core.bool.orFalse +import io.element.android.libraries.di.RoomScope 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.UserId import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.ui.room.getRoomMemberAsState @@ -39,9 +44,10 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch class RoomMemberDetailsPresenter @AssistedInject constructor( + @Assisted private val roomMemberId: UserId, private val client: MatrixClient, private val room: MatrixRoom, - @Assisted private val roomMemberId: UserId, + private val startDMAction: StartDMAction, ) : Presenter { interface Factory { @@ -53,6 +59,7 @@ class RoomMemberDetailsPresenter @AssistedInject constructor( val coroutineScope = rememberCoroutineScope() var confirmationDialog by remember { mutableStateOf(null) } val roomMember by room.getRoomMemberAsState(roomMemberId) + val startDmActionState: MutableState> = remember { mutableStateOf(Async.Uninitialized) } // the room member is not really live... val isBlocked: MutableState> = remember(roomMember) { val isIgnored = roomMember?.isIgnored @@ -88,6 +95,14 @@ class RoomMemberDetailsPresenter @AssistedInject constructor( RoomMemberDetailsEvents.ClearBlockUserError -> { isBlocked.value = Async.Success(isBlocked.value.dataOrNull().orFalse()) } + RoomMemberDetailsEvents.StartDM -> { + coroutineScope.launch { + startDMAction.execute(roomMemberId, startDmActionState) + } + } + RoomMemberDetailsEvents.ClearStartDMState -> { + startDmActionState.value = Async.Uninitialized + } } } @@ -108,6 +123,7 @@ class RoomMemberDetailsPresenter @AssistedInject constructor( userName = userName, avatarUrl = userAvatar, isBlocked = isBlocked.value, + startDmActionState = startDmActionState.value, displayConfirmationDialog = confirmationDialog, isCurrentUser = client.isMe(roomMember?.userId), eventSink = ::handleEvents diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsState.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsState.kt index 957db2233e..ee9cfe388f 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsState.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsState.kt @@ -17,12 +17,14 @@ package io.element.android.features.roomdetails.impl.members.details import io.element.android.libraries.architecture.Async +import io.element.android.libraries.matrix.api.core.RoomId data class RoomMemberDetailsState( val userId: String, val userName: String?, val avatarUrl: String?, val isBlocked: Async, + val startDmActionState: Async, val displayConfirmationDialog: ConfirmationDialog?, val isCurrentUser: Boolean, val eventSink: (RoomMemberDetailsEvents) -> Unit diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsStateProvider.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsStateProvider.kt index b14b0e3634..bf629e01d8 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsStateProvider.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsStateProvider.kt @@ -37,6 +37,7 @@ fun aRoomMemberDetailsState() = RoomMemberDetailsState( userName = "Daniel", avatarUrl = null, isBlocked = Async.Success(false), + startDmActionState = Async.Uninitialized, displayConfirmationDialog = null, isCurrentUser = false, eventSink = {}, diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsView.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsView.kt index 5d9871ace1..279df0c3ea 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsView.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsView.kt @@ -26,22 +26,33 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp +import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.features.roomdetails.impl.blockuser.BlockUserDialogs import io.element.android.features.roomdetails.impl.blockuser.BlockUserSection +import io.element.android.libraries.designsystem.components.async.AsyncView import io.element.android.libraries.designsystem.components.button.BackButton +import io.element.android.libraries.designsystem.components.list.ListItemContent import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreviewLight import io.element.android.libraries.designsystem.preview.PreviewWithLargeHeight +import io.element.android.libraries.designsystem.theme.components.IconSource +import io.element.android.libraries.designsystem.theme.components.ListItem +import io.element.android.libraries.designsystem.theme.components.ListItemStyle import io.element.android.libraries.designsystem.theme.components.Scaffold +import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.TopAppBar +import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.ui.strings.CommonStrings @OptIn(ExperimentalMaterial3Api::class) @Composable fun RoomMemberDetailsView( state: RoomMemberDetailsState, onShareUser: () -> Unit, + onDMStarted: (RoomId) -> Unit, goBack: () -> Unit, openAvatarPreview: (username: String, url: String) -> Unit, modifier: Modifier = Modifier, @@ -71,31 +82,36 @@ fun RoomMemberDetailsView( Spacer(modifier = Modifier.height(26.dp)) - // TODO implement send DM - // SendMessageSection(onSendMessage = { - // ... - // }) - if (!state.isCurrentUser) { + StartDMSection(onStartDMClicked = { state.eventSink(RoomMemberDetailsEvents.StartDM) }) BlockUserSection(state) BlockUserDialogs(state) } + AsyncView( + async = state.startDmActionState, + progressText = stringResource(CommonStrings.common_starting_chat), + onSuccess = onDMStarted, + errorMessage = { stringResource(CommonStrings.common_error) }, + onRetry = { state.eventSink(RoomMemberDetailsEvents.StartDM) }, + onErrorDismiss = { state.eventSink(RoomMemberDetailsEvents.ClearStartDMState) }, + ) } } } -/* @Composable -private fun SendMessageSection(onSendMessage: () -> Unit, modifier: Modifier = Modifier) { - PreferenceCategory(modifier = modifier) { - PreferenceText( - title = stringResource(CommonStrings.action_send_message), - icon = Icons.Outlined.ChatBubbleOutline, - onClick = onSendMessage, - ) - } +private fun StartDMSection( + onStartDMClicked: () -> Unit, + modifier: Modifier = Modifier +) { + ListItem( + headlineContent = { Text(stringResource(CommonStrings.common_direct_chat)) }, + leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Chat)), + style = ListItemStyle.Primary, + onClick = onStartDMClicked, + modifier = modifier, + ) } - */ @PreviewWithLargeHeight @Composable @@ -113,6 +129,7 @@ private fun ContentToPreview(state: RoomMemberDetailsState) { state = state, onShareUser = {}, goBack = {}, + onDMStarted = {}, openAvatarPreview = { _, _ -> } ) } From cd28687eb48e243646a0f18b17c982e8a57ae75f Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 30 Nov 2023 18:05:26 +0100 Subject: [PATCH 4/6] StartDM : add tests --- features/createroom/impl/build.gradle.kts | 1 + .../createroom/impl/DefaultStartDMAction.kt | 1 - .../impl/DefaultStartDMActionTests.kt | 84 +++++++++ .../impl/root/CreateRoomRootPresenterTests.kt | 162 ++++-------------- features/createroom/test/build.gradle.kts | 32 ++++ .../createroom/test/FakeStartDMAction.kt | 39 +++++ features/roomdetails/impl/build.gradle.kts | 1 + .../details/RoomMemberDetailsPresenter.kt | 3 - .../details/RoomMemberDetailsStateProvider.kt | 1 + .../members/details/RoomMemberDetailsView.kt | 3 +- .../impl/src/main/res/values/localazy.xml | 1 + .../roomdetails/RoomDetailsPresenterTests.kt | 59 ++++--- .../RoomMemberDetailsPresenterTests.kt | 88 ++++++++-- .../libraries/matrix/api/MatrixClient.kt | 2 +- .../libraries/matrix/api/room/StartDM.kt | 8 +- .../libraries/matrix/impl/RustMatrixClient.kt | 5 +- .../libraries/matrix/test/FakeMatrixClient.kt | 19 +- settings.gradle.kts | 4 +- tools/localazy/config.json | 3 +- 19 files changed, 320 insertions(+), 196 deletions(-) create mode 100644 features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/DefaultStartDMActionTests.kt create mode 100644 features/createroom/test/build.gradle.kts create mode 100644 features/createroom/test/src/main/kotlin/io/element/android/features/createroom/test/FakeStartDMAction.kt diff --git a/features/createroom/impl/build.gradle.kts b/features/createroom/impl/build.gradle.kts index ae9818b727..6cd9b30741 100644 --- a/features/createroom/impl/build.gradle.kts +++ b/features/createroom/impl/build.gradle.kts @@ -67,6 +67,7 @@ dependencies { testImplementation(projects.libraries.mediaupload.test) testImplementation(projects.libraries.permissions.test) testImplementation(projects.libraries.usersearch.test) + testImplementation(projects.features.createroom.test) testImplementation(projects.tests.testutils) ksp(libs.showkase.processor) diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/DefaultStartDMAction.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/DefaultStartDMAction.kt index 0aa2e93dcb..b7f1dc6c9c 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/DefaultStartDMAction.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/DefaultStartDMAction.kt @@ -21,7 +21,6 @@ import com.squareup.anvil.annotations.ContributesBinding import im.vector.app.features.analytics.plan.CreatedRoom import io.element.android.features.createroom.api.StartDMAction import io.element.android.libraries.architecture.Async -import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.SessionScope import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.RoomId diff --git a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/DefaultStartDMActionTests.kt b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/DefaultStartDMActionTests.kt new file mode 100644 index 0000000000..2bd7ebb687 --- /dev/null +++ b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/DefaultStartDMActionTests.kt @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.createroom.impl + +import androidx.compose.runtime.mutableStateOf +import com.google.common.truth.Truth.assertThat +import im.vector.app.features.analytics.plan.CreatedRoom +import io.element.android.libraries.architecture.Async +import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.room.StartDMResult +import io.element.android.libraries.matrix.test.A_FAILURE_REASON +import io.element.android.libraries.matrix.test.A_ROOM_ID +import io.element.android.libraries.matrix.test.A_THROWABLE +import io.element.android.libraries.matrix.test.A_USER_ID +import io.element.android.libraries.matrix.test.FakeMatrixClient +import io.element.android.services.analytics.api.AnalyticsService +import io.element.android.services.analytics.test.FakeAnalyticsService +import kotlinx.coroutines.test.runTest +import org.junit.Test + +class DefaultStartDMActionTests { + + @Test + fun `when dm is found, assert state is updated with given room id`() = runTest { + val matrixClient = FakeMatrixClient().apply { + givenFindDmResult(A_ROOM_ID) + } + val action = createStartDMAction(matrixClient) + val state = mutableStateOf>(Async.Uninitialized) + action.execute(A_USER_ID, state) + assertThat(state.value).isEqualTo(Async.Success(A_ROOM_ID)) + } + + @Test + fun `when dm is not found, assert dm is created, state is updated with given room id and analytics get called`() = runTest { + val matrixClient = FakeMatrixClient().apply { + givenFindDmResult(null) + givenCreateDmResult(Result.success(A_ROOM_ID)) + } + val analyticsService = FakeAnalyticsService() + val action = createStartDMAction(matrixClient, analyticsService) + val state = mutableStateOf>(Async.Uninitialized) + action.execute(A_USER_ID, state) + assertThat(state.value).isEqualTo(Async.Success(A_ROOM_ID)) + assertThat(analyticsService.capturedEvents).containsExactly(CreatedRoom(isDM = true)) + } + + @Test + fun `when dm creation fails, assert state is updated with given error`() = runTest { + val matrixClient = FakeMatrixClient().apply { + givenFindDmResult(null) + givenCreateDmResult(Result.failure(A_THROWABLE)) + } + val action = createStartDMAction(matrixClient) + val state = mutableStateOf>(Async.Uninitialized) + action.execute(A_USER_ID, state) + assertThat(state.value).isEqualTo(Async.Failure(StartDMResult.Failure(A_FAILURE_REASON))) + } + + private fun createStartDMAction( + matrixClient: MatrixClient = FakeMatrixClient(), + analyticsService: AnalyticsService = FakeAnalyticsService(), + ): DefaultStartDMAction { + return DefaultStartDMAction( + matrixClient = matrixClient, + analyticsService = analyticsService, + ) + } +} diff --git a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootPresenterTests.kt b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootPresenterTests.kt index bf4593bdbb..5322407c35 100644 --- a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootPresenterTests.kt +++ b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootPresenterTests.kt @@ -20,25 +20,22 @@ import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat -import im.vector.app.features.analytics.plan.CreatedRoom +import io.element.android.features.createroom.api.StartDMAction import io.element.android.features.createroom.impl.userlist.FakeUserListPresenter import io.element.android.features.createroom.impl.userlist.FakeUserListPresenterFactory import io.element.android.features.createroom.impl.userlist.UserListDataStore -import io.element.android.features.createroom.impl.userlist.aUserListState +import io.element.android.features.createroom.test.FakeStartDMAction import io.element.android.libraries.architecture.Async import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.UserId +import io.element.android.libraries.matrix.api.room.StartDMResult import io.element.android.libraries.matrix.api.user.MatrixUser -import io.element.android.libraries.matrix.test.A_THROWABLE -import io.element.android.libraries.matrix.test.FakeMatrixClient +import io.element.android.libraries.matrix.test.A_FAILURE_REASON +import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.matrix.test.core.aBuildMeta -import io.element.android.libraries.matrix.test.room.FakeMatrixRoom import io.element.android.libraries.usersearch.test.FakeUserRepository -import io.element.android.services.analytics.test.FakeAnalyticsService import io.element.android.tests.testutils.WarmUpRule -import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.test.runTest -import org.junit.Before import org.junit.Rule import org.junit.Test @@ -47,142 +44,57 @@ class CreateRoomRootPresenterTests { @get:Rule val warmUpRule = WarmUpRule() - private lateinit var userRepository: FakeUserRepository - private lateinit var presenter: CreateRoomRootPresenter - private lateinit var fakeUserListPresenter: FakeUserListPresenter - private lateinit var fakeMatrixClient: FakeMatrixClient - private lateinit var fakeAnalyticsService: FakeAnalyticsService - - @Before - fun setup() { - fakeUserListPresenter = FakeUserListPresenter() - fakeMatrixClient = FakeMatrixClient() - fakeAnalyticsService = FakeAnalyticsService() - userRepository = FakeUserRepository() - presenter = CreateRoomRootPresenter( - presenterFactory = FakeUserListPresenterFactory(fakeUserListPresenter), - userRepository = userRepository, - userListDataStore = UserListDataStore(), - matrixClient = fakeMatrixClient, - analyticsService = fakeAnalyticsService, - buildMeta = aBuildMeta(), - ) - } - @Test - fun `present - initial state`() = runTest { + fun `present - start DM action complete scenario`() = runTest { + val startDMAction = FakeStartDMAction() + val presenter = createCreateRoomRootPresenter(startDMAction) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() + assertThat(initialState.startDmAction).isInstanceOf(Async.Uninitialized::class.java) assertThat(initialState.applicationName).isEqualTo(aBuildMeta().applicationName) assertThat(initialState.userListState.selectedUsers).isEmpty() assertThat(initialState.userListState.isSearchActive).isFalse() assertThat(initialState.userListState.isMultiSelectionEnabled).isFalse() - } - } - @Test - fun `present - trigger create DM action`() = runTest { - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - val initialState = awaitItem() val matrixUser = MatrixUser(UserId("@name:domain")) - val createDmResult = Result.success(RoomId("!createDmResult:domain")) - - fakeMatrixClient.givenFindDmResult(null) - fakeMatrixClient.givenCreateDmResult(createDmResult) - - initialState.eventSink(CreateRoomRootEvents.StartDM(matrixUser)) - assertThat(awaitItem().startDmAction).isInstanceOf(Async.Loading::class.java) - val stateAfterStartDM = awaitItem() - assertThat(stateAfterStartDM.startDmAction).isInstanceOf(Async.Success::class.java) - assertThat(stateAfterStartDM.startDmAction.dataOrNull()).isEqualTo(createDmResult.getOrNull()) - } - } - - @Test - fun `present - creating a DM records analytics event`() = runTest { - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - val initialState = awaitItem() - val matrixUser = MatrixUser(UserId("@name:domain")) - val createDmResult = Result.success(RoomId("!createDmResult:domain")) - - fakeMatrixClient.givenFindDmResult(null) - fakeMatrixClient.givenCreateDmResult(createDmResult) - - initialState.eventSink(CreateRoomRootEvents.StartDM(matrixUser)) - skipItems(2) - - val analyticsEvent = fakeAnalyticsService.capturedEvents.filterIsInstance().firstOrNull() - assertThat(analyticsEvent).isNotNull() - assertThat(analyticsEvent?.isDM).isTrue() - } - } - - @Test - fun `present - trigger retrieve DM action`() = runTest { - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - val initialState = awaitItem() - val matrixUser = MatrixUser(UserId("@name:domain")) - val fakeDmResult = FakeMatrixRoom(roomId = RoomId("!fakeDmResult:domain")) - - fakeMatrixClient.givenFindDmResult(fakeDmResult) - - initialState.eventSink(CreateRoomRootEvents.StartDM(matrixUser)) - val stateAfterStartDM = awaitItem() - assertThat(stateAfterStartDM.startDmAction).isInstanceOf(Async.Success::class.java) - assertThat(stateAfterStartDM.startDmAction.dataOrNull()).isEqualTo(fakeDmResult.roomId) - assertThat(fakeAnalyticsService.capturedEvents.filterIsInstance()).isEmpty() - } - } - - @Test - fun `present - trigger retry create DM action`() = runTest { - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - val initialState = awaitItem() - val matrixUser = MatrixUser(UserId("@name:domain")) - val createDmResult = Result.success(RoomId("!createDmResult:domain")) - fakeUserListPresenter.givenState(aUserListState().copy(selectedUsers = persistentListOf(matrixUser))) - - fakeMatrixClient.givenFindDmResult(null) - fakeMatrixClient.givenCreateDmError(A_THROWABLE) - fakeMatrixClient.givenCreateDmResult(createDmResult) + val startDMSuccessResult = Async.Success(A_ROOM_ID) + val startDMFailureResult = Async.Failure(StartDMResult.Failure(A_FAILURE_REASON)) // Failure + startDMAction.givenExecuteResult(startDMFailureResult) initialState.eventSink(CreateRoomRootEvents.StartDM(matrixUser)) assertThat(awaitItem().startDmAction).isInstanceOf(Async.Loading::class.java) - val stateAfterStartDM = awaitItem() - assertThat(stateAfterStartDM.startDmAction).isInstanceOf(Async.Failure::class.java) - assertThat(fakeAnalyticsService.capturedEvents.filterIsInstance()).isEmpty() + awaitItem().also { state -> + assertThat(state.startDmAction).isEqualTo(startDMFailureResult) + state.eventSink(CreateRoomRootEvents.CancelStartDM) + } - // Cancel - stateAfterStartDM.eventSink(CreateRoomRootEvents.CancelStartDM) - val stateAfterCancel = awaitItem() - assertThat(stateAfterCancel.startDmAction).isInstanceOf(Async.Uninitialized::class.java) - - // Failure - stateAfterCancel.eventSink(CreateRoomRootEvents.StartDM(matrixUser)) + // Success + startDMAction.givenExecuteResult(startDMSuccessResult) + awaitItem().also { state -> + assertThat(state.startDmAction).isEqualTo(Async.Uninitialized) + state.eventSink(CreateRoomRootEvents.StartDM(matrixUser)) + } assertThat(awaitItem().startDmAction).isInstanceOf(Async.Loading::class.java) - val stateAfterSecondAttempt = awaitItem() - assertThat(stateAfterSecondAttempt.startDmAction).isInstanceOf(Async.Failure::class.java) - assertThat(fakeAnalyticsService.capturedEvents.filterIsInstance()).isEmpty() + awaitItem().also { state -> + assertThat(state.startDmAction).isEqualTo(startDMSuccessResult) + } - // Retry with success - fakeMatrixClient.givenCreateDmError(null) - stateAfterSecondAttempt.eventSink(CreateRoomRootEvents.StartDM(matrixUser)) - assertThat(awaitItem().startDmAction).isInstanceOf(Async.Loading::class.java) - val stateAfterRetryStartDM = awaitItem() - assertThat(stateAfterRetryStartDM.startDmAction).isInstanceOf(Async.Success::class.java) - assertThat(stateAfterRetryStartDM.startDmAction.dataOrNull()).isEqualTo(createDmResult.getOrNull()) } } + + private fun createCreateRoomRootPresenter( + startDMAction: StartDMAction = FakeStartDMAction(), + ): CreateRoomRootPresenter { + return CreateRoomRootPresenter( + presenterFactory = FakeUserListPresenterFactory(FakeUserListPresenter()), + userRepository = FakeUserRepository(), + userListDataStore = UserListDataStore(), + startDMAction = startDMAction, + buildMeta = aBuildMeta(), + ) + } } diff --git a/features/createroom/test/build.gradle.kts b/features/createroom/test/build.gradle.kts new file mode 100644 index 0000000000..7c83725971 --- /dev/null +++ b/features/createroom/test/build.gradle.kts @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2022 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-compose-library") +} + +android { + namespace = "io.element.android.features.createroom.test" +} + +dependencies { + implementation(libs.coroutines.core) + implementation(projects.libraries.matrix.api) + implementation(projects.libraries.matrix.test) + implementation(projects.tests.testutils) + implementation(projects.libraries.architecture) + api(projects.features.createroom.api) +} diff --git a/features/createroom/test/src/main/kotlin/io/element/android/features/createroom/test/FakeStartDMAction.kt b/features/createroom/test/src/main/kotlin/io/element/android/features/createroom/test/FakeStartDMAction.kt new file mode 100644 index 0000000000..09d59b7f6c --- /dev/null +++ b/features/createroom/test/src/main/kotlin/io/element/android/features/createroom/test/FakeStartDMAction.kt @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.createroom.test + +import androidx.compose.runtime.MutableState +import io.element.android.features.createroom.api.StartDMAction +import io.element.android.libraries.architecture.Async +import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.core.UserId +import io.element.android.libraries.matrix.test.A_ROOM_ID +import io.element.android.tests.testutils.simulateLongTask + +class FakeStartDMAction : StartDMAction { + + private var executeResult: Async = Async.Success(A_ROOM_ID) + + fun givenExecuteResult(result: Async) { + executeResult = result + } + + override suspend fun execute(userId: UserId, actionState: MutableState>) = simulateLongTask { + actionState.value = Async.Loading() + actionState.value = executeResult + } +} diff --git a/features/roomdetails/impl/build.gradle.kts b/features/roomdetails/impl/build.gradle.kts index 44cbbb5cbb..826621b2b5 100644 --- a/features/roomdetails/impl/build.gradle.kts +++ b/features/roomdetails/impl/build.gradle.kts @@ -68,6 +68,7 @@ dependencies { testImplementation(projects.libraries.featureflag.test) testImplementation(projects.tests.testutils) testImplementation(projects.features.leaveroom.test) + testImplementation(projects.features.createroom.test) ksp(libs.showkase.processor) } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsPresenter.kt index 463fcf186a..e82b5f0cb5 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsPresenter.kt @@ -25,16 +25,13 @@ import androidx.compose.runtime.produceState import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue -import com.squareup.anvil.annotations.ContributesBinding import dagger.assisted.Assisted -import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import io.element.android.features.createroom.api.StartDMAction import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsState.ConfirmationDialog import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.core.bool.orFalse -import io.element.android.libraries.di.RoomScope 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.UserId diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsStateProvider.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsStateProvider.kt index bf629e01d8..c5710986a6 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsStateProvider.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsStateProvider.kt @@ -28,6 +28,7 @@ open class RoomMemberDetailsStateProvider : PreviewParameterProvider"Your homeserver does not support this option in encrypted rooms, you won\'t get notified in this room." "All messages" "In this room, notify me for" + "An error occurred when trying to start a chat" "Block" "Blocked users won\'t be able to send you messages and all their messages will be hidden. You can unblock them anytime." "Block user" diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTests.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTests.kt index 5ab79d2cee..577b01a974 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTests.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTests.kt @@ -20,6 +20,7 @@ import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat +import io.element.android.features.createroom.test.FakeStartDMAction import io.element.android.features.leaveroom.api.LeaveRoomEvent import io.element.android.features.leaveroom.api.LeaveRoomPresenter import io.element.android.features.leaveroom.fake.FakeLeaveRoomPresenter @@ -45,10 +46,11 @@ 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.notificationsettings.FakeNotificationSettingsService import io.element.android.libraries.matrix.test.room.FakeMatrixRoom +import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.consumeItemsUntilPredicate import io.element.android.tests.testutils.testCoroutineDispatchers -import io.element.android.tests.testutils.WarmUpRule import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest import org.junit.Rule import org.junit.Test @@ -60,16 +62,16 @@ class RoomDetailsPresenterTests { @get:Rule val warmUpRule = WarmUpRule() - private fun createRoomDetailsPresenter( + private fun TestScope.createRoomDetailsPresenter( room: MatrixRoom, leaveRoomPresenter: LeaveRoomPresenter = FakeLeaveRoomPresenter(), - dispatchers: CoroutineDispatchers, + dispatchers: CoroutineDispatchers = testCoroutineDispatchers(), notificationSettingsService: FakeNotificationSettingsService = FakeNotificationSettingsService() ): RoomDetailsPresenter { val matrixClient = FakeMatrixClient(notificationSettingsService = notificationSettingsService) val roomMemberDetailsPresenterFactory = object : RoomMemberDetailsPresenter.Factory { override fun create(roomMemberId: UserId): RoomMemberDetailsPresenter { - return RoomMemberDetailsPresenter(matrixClient, room, roomMemberId) + return RoomMemberDetailsPresenter(roomMemberId, matrixClient, room, FakeStartDMAction()) } } val featureFlagService = FakeFeatureFlagService( @@ -89,7 +91,7 @@ class RoomDetailsPresenterTests { @Test fun `present - initial state is created from room info`() = runTest { val room = aMatrixRoom() - val presenter = createRoomDetailsPresenter(room, dispatchers = testCoroutineDispatchers()) + val presenter = createRoomDetailsPresenter(room) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -108,7 +110,7 @@ class RoomDetailsPresenterTests { @Test fun `present - initial state with no room name`() = runTest { val room = aMatrixRoom(name = null) - val presenter = createRoomDetailsPresenter(room, dispatchers = testCoroutineDispatchers()) + val presenter = createRoomDetailsPresenter(room) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -130,7 +132,7 @@ class RoomDetailsPresenterTests { val roomMembers = listOf(myRoomMember, otherRoomMember) givenRoomMembersState(MatrixRoomMembersState.Ready(roomMembers)) } - val presenter = createRoomDetailsPresenter(room, dispatchers = testCoroutineDispatchers()) + val presenter = createRoomDetailsPresenter(room) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -164,7 +166,7 @@ class RoomDetailsPresenterTests { val room = aMatrixRoom().apply { givenCanInviteResult(Result.success(false)) } - val presenter = createRoomDetailsPresenter(room, dispatchers = testCoroutineDispatchers()) + val presenter = createRoomDetailsPresenter(room) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -179,7 +181,7 @@ class RoomDetailsPresenterTests { val room = aMatrixRoom().apply { givenCanInviteResult(Result.failure(Throwable("Whoops"))) } - val presenter = createRoomDetailsPresenter(room, dispatchers = testCoroutineDispatchers()) + val presenter = createRoomDetailsPresenter(room) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -197,7 +199,7 @@ class RoomDetailsPresenterTests { givenCanSendStateResult(StateEventType.ROOM_AVATAR, Result.failure(Throwable("Whelp"))) givenCanInviteResult(Result.success(false)) } - val presenter = createRoomDetailsPresenter(room, dispatchers = testCoroutineDispatchers()) + val presenter = createRoomDetailsPresenter(room) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -226,7 +228,7 @@ class RoomDetailsPresenterTests { givenCanSendStateResult(StateEventType.ROOM_AVATAR, Result.success(true)) givenCanInviteResult(Result.success(false)) } - val presenter = createRoomDetailsPresenter(room, dispatchers = testCoroutineDispatchers()) + val presenter = createRoomDetailsPresenter(room) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -241,6 +243,7 @@ class RoomDetailsPresenterTests { cancelAndIgnoreRemainingEvents() } } + @Test fun `present - initial state when in a DM with no topic`() = runTest { val myRoomMember = aRoomMember(A_SESSION_ID) @@ -255,7 +258,7 @@ class RoomDetailsPresenterTests { givenCanSendStateResult(StateEventType.ROOM_TOPIC, Result.success(true)) } - val presenter = createRoomDetailsPresenter(room, dispatchers = testCoroutineDispatchers()) + val presenter = createRoomDetailsPresenter(room) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -276,7 +279,7 @@ class RoomDetailsPresenterTests { givenCanSendStateResult(StateEventType.ROOM_AVATAR, Result.success(true)) givenCanInviteResult(Result.success(false)) } - val presenter = createRoomDetailsPresenter(room, dispatchers = testCoroutineDispatchers()) + val presenter = createRoomDetailsPresenter(room) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -297,7 +300,7 @@ class RoomDetailsPresenterTests { givenCanSendStateResult(StateEventType.ROOM_AVATAR, Result.success(false)) givenCanInviteResult(Result.success(false)) } - val presenter = createRoomDetailsPresenter(room, dispatchers = testCoroutineDispatchers()) + val presenter = createRoomDetailsPresenter(room) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -315,7 +318,7 @@ class RoomDetailsPresenterTests { givenCanInviteResult(Result.success(false)) } - val presenter = createRoomDetailsPresenter(room, dispatchers = testCoroutineDispatchers()) + val presenter = createRoomDetailsPresenter(room) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -333,7 +336,7 @@ class RoomDetailsPresenterTests { givenCanInviteResult(Result.success(false)) } - val presenter = createRoomDetailsPresenter(room, dispatchers = testCoroutineDispatchers()) + val presenter = createRoomDetailsPresenter(room) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -351,7 +354,11 @@ class RoomDetailsPresenterTests { fun `present - leave room event is passed on to leave room presenter`() = runTest { val leaveRoomPresenter = FakeLeaveRoomPresenter() val room = aMatrixRoom() - val presenter = createRoomDetailsPresenter(room, leaveRoomPresenter, testCoroutineDispatchers()) + val presenter = createRoomDetailsPresenter( + room = room, + leaveRoomPresenter = leaveRoomPresenter, + dispatchers = testCoroutineDispatchers() + ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -368,14 +375,18 @@ class RoomDetailsPresenterTests { val leaveRoomPresenter = FakeLeaveRoomPresenter() val notificationSettingsService = FakeNotificationSettingsService() val room = aMatrixRoom(notificationSettingsService = notificationSettingsService) - val presenter = createRoomDetailsPresenter(room, leaveRoomPresenter, testCoroutineDispatchers(), notificationSettingsService) + val presenter = createRoomDetailsPresenter( + room = room, + leaveRoomPresenter = leaveRoomPresenter, + notificationSettingsService = notificationSettingsService, + ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { notificationSettingsService.setRoomNotificationMode(room.roomId, RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY) val updatedState = consumeItemsUntilPredicate { - it.roomNotificationSettings?.mode == RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY + it.roomNotificationSettings?.mode == RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY }.last() assertThat(updatedState.roomNotificationSettings?.mode).isEqualTo(RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY) cancelAndIgnoreRemainingEvents() @@ -384,16 +395,15 @@ class RoomDetailsPresenterTests { @Test fun `present - mute room notifications`() = runTest { - val leaveRoomPresenter = FakeLeaveRoomPresenter() val notificationSettingsService = FakeNotificationSettingsService(initialRoomMode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY) val room = aMatrixRoom(notificationSettingsService = notificationSettingsService) - val presenter = createRoomDetailsPresenter(room, leaveRoomPresenter, testCoroutineDispatchers(), notificationSettingsService) + val presenter = createRoomDetailsPresenter(room = room, notificationSettingsService = notificationSettingsService) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { awaitItem().eventSink(RoomDetailsEvent.MuteNotification) val updatedState = consumeItemsUntilPredicate(timeout = 250.milliseconds) { - it.roomNotificationSettings?.mode == RoomNotificationMode.MUTE + it.roomNotificationSettings?.mode == RoomNotificationMode.MUTE }.last() assertThat(updatedState.roomNotificationSettings?.mode).isEqualTo(RoomNotificationMode.MUTE) cancelAndIgnoreRemainingEvents() @@ -402,19 +412,18 @@ class RoomDetailsPresenterTests { @Test fun `present - unmute room notifications`() = runTest { - val leaveRoomPresenter = FakeLeaveRoomPresenter() val notificationSettingsService = FakeNotificationSettingsService( initialRoomMode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY, initialEncryptedGroupDefaultMode = RoomNotificationMode.ALL_MESSAGES ) val room = aMatrixRoom(notificationSettingsService = notificationSettingsService) - val presenter = createRoomDetailsPresenter(room, leaveRoomPresenter, testCoroutineDispatchers(), notificationSettingsService) + val presenter = createRoomDetailsPresenter(room = room, notificationSettingsService = notificationSettingsService) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { awaitItem().eventSink(RoomDetailsEvent.UnmuteNotification) val updatedState = consumeItemsUntilPredicate { - it.roomNotificationSettings?.mode == RoomNotificationMode.ALL_MESSAGES + it.roomNotificationSettings?.mode == RoomNotificationMode.ALL_MESSAGES }.last() assertThat(updatedState.roomNotificationSettings?.mode).isEqualTo(RoomNotificationMode.ALL_MESSAGES) cancelAndIgnoreRemainingEvents() diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/details/RoomMemberDetailsPresenterTests.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/details/RoomMemberDetailsPresenterTests.kt index a249e38823..1fb5cc15db 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/details/RoomMemberDetailsPresenterTests.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/details/RoomMemberDetailsPresenterTests.kt @@ -20,13 +20,22 @@ import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth +import io.element.android.features.createroom.api.StartDMAction +import io.element.android.features.createroom.test.FakeStartDMAction import io.element.android.features.roomdetails.aMatrixRoom import io.element.android.features.roomdetails.impl.members.aRoomMember 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.RoomMemberDetailsState import io.element.android.libraries.architecture.Async +import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.matrix.api.core.RoomId +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.RoomMember +import io.element.android.libraries.matrix.api.room.StartDMResult +import io.element.android.libraries.matrix.test.A_FAILURE_REASON +import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.matrix.test.A_THROWABLE import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.tests.testutils.WarmUpRule @@ -49,7 +58,10 @@ class RoomMemberDetailsPresenterTests { givenUserAvatarUrlResult(Result.success("A custom avatar")) givenRoomMembersState(MatrixRoomMembersState.Ready(listOf(roomMember))) } - val presenter = RoomMemberDetailsPresenter(FakeMatrixClient(), room, roomMember.userId) + val presenter = createRoomMemberDetailsPresenter( + room = room, + roomMember = roomMember + ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -73,7 +85,10 @@ class RoomMemberDetailsPresenterTests { givenUserAvatarUrlResult(Result.failure(Throwable())) givenRoomMembersState(MatrixRoomMembersState.Ready(listOf(roomMember))) } - val presenter = RoomMemberDetailsPresenter(FakeMatrixClient(), room, roomMember.userId) + val presenter = createRoomMemberDetailsPresenter( + room = room, + roomMember = roomMember + ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -93,7 +108,10 @@ class RoomMemberDetailsPresenterTests { givenUserAvatarUrlResult(Result.success(null)) givenRoomMembersState(MatrixRoomMembersState.Ready(listOf(roomMember))) } - val presenter = RoomMemberDetailsPresenter(FakeMatrixClient(), room, roomMember.userId) + val presenter = createRoomMemberDetailsPresenter( + room = room, + roomMember = roomMember + ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -107,9 +125,7 @@ class RoomMemberDetailsPresenterTests { @Test fun `present - BlockUser needing confirmation displays confirmation dialog`() = runTest { - val room = aMatrixRoom() - val roomMember = aRoomMember() - val presenter = RoomMemberDetailsPresenter(FakeMatrixClient(), room, roomMember.userId) + val presenter = createRoomMemberDetailsPresenter() moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -128,9 +144,7 @@ class RoomMemberDetailsPresenterTests { @Test fun `present - BlockUser and UnblockUser without confirmation change the 'blocked' state`() = runTest { - val room = aMatrixRoom() - val roomMember = aRoomMember() - val presenter = RoomMemberDetailsPresenter(FakeMatrixClient(), room, roomMember.userId) + val presenter = createRoomMemberDetailsPresenter() moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -147,11 +161,9 @@ class RoomMemberDetailsPresenterTests { @Test fun `present - BlockUser with error`() = runTest { - val room = aMatrixRoom() - val roomMember = aRoomMember() val matrixClient = FakeMatrixClient() matrixClient.givenIgnoreUserResult(Result.failure(A_THROWABLE)) - val presenter = RoomMemberDetailsPresenter(matrixClient, room, roomMember.userId) + val presenter = createRoomMemberDetailsPresenter(client = matrixClient) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -168,9 +180,7 @@ class RoomMemberDetailsPresenterTests { @Test fun `present - UnblockUser needing confirmation displays confirmation dialog`() = runTest { - val room = aMatrixRoom() - val roomMember = aRoomMember() - val presenter = RoomMemberDetailsPresenter(FakeMatrixClient(), room, roomMember.userId) + val presenter = createRoomMemberDetailsPresenter() moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -186,4 +196,52 @@ class RoomMemberDetailsPresenterTests { ensureAllEventsConsumed() } } + + @Test + fun `present - start DM action complete scenario`() = runTest { + val startDMAction = FakeStartDMAction() + val presenter = createRoomMemberDetailsPresenter(startDMAction = startDMAction) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + Truth.assertThat(initialState.startDmActionState).isInstanceOf(Async.Uninitialized::class.java) + val startDMSuccessResult = Async.Success(A_ROOM_ID) + val startDMFailureResult = Async.Failure(StartDMResult.Failure(A_FAILURE_REASON)) + + // Failure + startDMAction.givenExecuteResult(startDMFailureResult) + initialState.eventSink(RoomMemberDetailsEvents.StartDM) + Truth.assertThat(awaitItem().startDmActionState).isInstanceOf(Async.Loading::class.java) + awaitItem().also { state -> + Truth.assertThat(state.startDmActionState).isEqualTo(startDMFailureResult) + state.eventSink(RoomMemberDetailsEvents.ClearStartDMState) + } + + // Success + startDMAction.givenExecuteResult(startDMSuccessResult) + awaitItem().also { state -> + Truth.assertThat(state.startDmActionState).isEqualTo(Async.Uninitialized) + state.eventSink(RoomMemberDetailsEvents.StartDM) + } + Truth.assertThat(awaitItem().startDmActionState).isInstanceOf(Async.Loading::class.java) + awaitItem().also { state -> + Truth.assertThat(state.startDmActionState).isEqualTo(startDMSuccessResult) + } + } + } + + private fun createRoomMemberDetailsPresenter( + client: MatrixClient = FakeMatrixClient(), + room: MatrixRoom = aMatrixRoom(), + roomMember: RoomMember = aRoomMember(), + startDMAction: StartDMAction = FakeStartDMAction() + ): RoomMemberDetailsPresenter { + return RoomMemberDetailsPresenter( + roomMemberId = roomMember.userId, + client = client, + room = room, + startDMAction = startDMAction + ) + } } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt index f9ac0d84a5..dc452f1514 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt @@ -41,7 +41,7 @@ interface MatrixClient : Closeable { val roomListService: RoomListService val mediaLoader: MatrixMediaLoader suspend fun getRoom(roomId: RoomId): MatrixRoom? - suspend fun findDM(userId: UserId): MatrixRoom? + suspend fun findDM(userId: UserId): RoomId? suspend fun ignoreUser(userId: UserId): Result suspend fun unignoreUser(userId: UserId): Result suspend fun createRoom(createRoomParams: CreateRoomParameters): Result diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/StartDM.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/StartDM.kt index 722f598d51..b30248170f 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/StartDM.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/StartDM.kt @@ -24,11 +24,9 @@ import io.element.android.libraries.matrix.api.core.UserId * Try to find an existing DM with the given user, or create one if none exists. */ suspend fun MatrixClient.startDM(userId: UserId): StartDMResult { - val existingRoomId = findDM(userId)?.use { existingDM -> - existingDM.roomId - } - return if (existingRoomId != null) { - StartDMResult.Success(existingRoomId, isNew = false) + val existingDM = findDM(userId) + return if (existingDM != null) { + StartDMResult.Success(existingDM, isNew = false) } else { createDM(userId).fold( { StartDMResult.Success(it, isNew = true) }, diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt index ef08f32edf..3a7adefe41 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt @@ -240,9 +240,8 @@ class RustMatrixClient constructor( } } - override suspend fun findDM(userId: UserId): MatrixRoom? { - val roomId = client.getDmRoom(userId.value)?.use { RoomId(it.id()) } - return roomId?.let { getRoom(it) } + override suspend fun findDM(userId: UserId): RoomId? { + return client.getDmRoom(userId.value)?.use { RoomId(it.id()) } } override suspend fun ignoreUser(userId: UserId): Result = withContext(sessionDispatcher) { diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt index e8228e806e..3977fbb133 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt @@ -39,7 +39,6 @@ import io.element.android.libraries.matrix.test.media.FakeMediaLoader import io.element.android.libraries.matrix.test.notification.FakeNotificationService import io.element.android.libraries.matrix.test.notificationsettings.FakeNotificationSettingsService import io.element.android.libraries.matrix.test.pushers.FakePushersService -import io.element.android.libraries.matrix.test.room.FakeMatrixRoom import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService import io.element.android.libraries.matrix.test.sync.FakeSyncService import io.element.android.libraries.matrix.test.verification.FakeSessionVerificationService @@ -72,8 +71,7 @@ class FakeMatrixClient( private var unignoreUserResult: Result = Result.success(Unit) private var createRoomResult: Result = Result.success(A_ROOM_ID) private var createDmResult: Result = Result.success(A_ROOM_ID) - private var createDmFailure: Throwable? = null - private var findDmResult: MatrixRoom? = FakeMatrixRoom() + private var findDmResult: RoomId? = A_ROOM_ID private var logoutFailure: Throwable? = null private val getRoomResults = mutableMapOf() private val searchUserResults = mutableMapOf>() @@ -87,7 +85,7 @@ class FakeMatrixClient( return getRoomResults[roomId] } - override suspend fun findDM(userId: UserId): MatrixRoom? { + override suspend fun findDM(userId: UserId): RoomId? { return findDmResult } @@ -99,14 +97,11 @@ class FakeMatrixClient( return unignoreUserResult } - override suspend fun createRoom(createRoomParams: CreateRoomParameters): Result { - delay(100) + override suspend fun createRoom(createRoomParams: CreateRoomParameters): Result = simulateLongTask { return createRoomResult } - override suspend fun createDM(userId: UserId): Result { - delay(100) - createDmFailure?.let { throw it } + override suspend fun createDM(userId: UserId): Result = simulateLongTask { return createDmResult } @@ -206,11 +201,7 @@ class FakeMatrixClient( unignoreUserResult = result } - fun givenCreateDmError(failure: Throwable?) { - createDmFailure = failure - } - - fun givenFindDmResult(result: MatrixRoom?) { + fun givenFindDmResult(result: RoomId?) { findDmResult = result } diff --git a/settings.gradle.kts b/settings.gradle.kts index 0fe8ca31ee..cd3689abe3 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -82,9 +82,9 @@ includeProjects(File(rootDir, "services"), ":services") // Uncomment to include the compound-android module as a local dependency so you can work on it locally. // You will also need to clone it in the specified folder. -//includeBuild("checkouts/compound-android") { +// includeBuild("checkouts/compound-android") { // dependencySubstitution { // // substitute remote dependency with local module // substitute(module("io.element.android:compound-android")).using(project(":compound")) // } -//} +// } diff --git a/tools/localazy/config.json b/tools/localazy/config.json index 06805d49cc..88eae496a4 100644 --- a/tools/localazy/config.json +++ b/tools/localazy/config.json @@ -115,7 +115,8 @@ "screen_room_member_list_.*", "screen_dm_details_.*", "screen_room_notification_settings_.*", - "screen_notification_settings_edit_failed_updating_default_mode" + "screen_notification_settings_edit_failed_updating_default_mode", + "screen_start_chat_error_starting_chat" ] }, { From ee77131c6106213a6579ea14e5158458281adb59 Mon Sep 17 00:00:00 2001 From: ElementBot Date: Fri, 1 Dec 2023 06:58:55 +0000 Subject: [PATCH 5/6] Update screenshots --- ..._RoomMemberDetailsViewDark--3_5_null_0,NEXUS_5,1.0,en].png | 4 ++-- ..._RoomMemberDetailsViewDark--3_5_null_1,NEXUS_5,1.0,en].png | 4 ++-- ..._RoomMemberDetailsViewDark--3_5_null_2,NEXUS_5,1.0,en].png | 4 ++-- ..._RoomMemberDetailsViewDark--3_5_null_3,NEXUS_5,1.0,en].png | 4 ++-- ..._RoomMemberDetailsViewDark--3_5_null_4,NEXUS_5,1.0,en].png | 4 ++-- ..._RoomMemberDetailsViewDark--3_5_null_5,NEXUS_5,1.0,en].png | 4 ++-- ..._RoomMemberDetailsViewDark--3_5_null_6,NEXUS_5,1.0,en].png | 3 +++ ...RoomMemberDetailsViewLight--2_4_null_0,NEXUS_5,1.0,en].png | 4 ++-- ...RoomMemberDetailsViewLight--2_4_null_1,NEXUS_5,1.0,en].png | 4 ++-- ...RoomMemberDetailsViewLight--2_4_null_2,NEXUS_5,1.0,en].png | 4 ++-- ...RoomMemberDetailsViewLight--2_4_null_3,NEXUS_5,1.0,en].png | 4 ++-- ...RoomMemberDetailsViewLight--2_4_null_4,NEXUS_5,1.0,en].png | 4 ++-- ...RoomMemberDetailsViewLight--2_4_null_5,NEXUS_5,1.0,en].png | 4 ++-- ...RoomMemberDetailsViewLight--2_4_null_6,NEXUS_5,1.0,en].png | 3 +++ 14 files changed, 30 insertions(+), 24 deletions(-) create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_5_null_6,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_4_null_6,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_5_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_5_null_0,NEXUS_5,1.0,en].png index 6cbb96f072..966a6e3d0d 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_5_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_5_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3f209aba9d799dfd6b3b947beef7f5e9ec8b6e45209579c406b41f545300ed9b -size 19562 +oid sha256:635af24c8306297ac954aa7ddb4286c36904b763e8f32acbbad841334dda8253 +size 22352 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_5_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_5_null_1,NEXUS_5,1.0,en].png index bf4bf45d77..3d6ff401a2 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_5_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_5_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9e23c9f4c52976030b3c397ad2e68f94bed265c147d3a40f65b6fa4d1459405d -size 17392 +oid sha256:c8b5d1c152ed4f3334fc5d6fa74546a1fa7f50dd4975f6f108028a34be0db63c +size 20248 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_5_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_5_null_2,NEXUS_5,1.0,en].png index cae6a921c3..201c88ab4a 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_5_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_5_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8109e9c96d3f89ed6747b21fe326f6cdc61157b376497560ba6415d6ee592b79 -size 19932 +oid sha256:a7caaac71c2c280fc18036e725d9a64aae77e351b494440bbac6f6776658c856 +size 22544 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_5_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_5_null_3,NEXUS_5,1.0,en].png index 9c0a35fc1e..cf98fbcce8 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_5_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_5_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:64c553c7647dcf8e6e65e0e5f6958a1e77b67490f7f9b97670ff5ca9a5695cef -size 36807 +oid sha256:44e2a0e5c2adb1d80c2bf2abfc3ab481ef28a7dd44de8fcdca73d87933219f65 +size 38735 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_5_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_5_null_4,NEXUS_5,1.0,en].png index b6b596bd7c..ab06f857b9 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_5_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_5_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:40b07513dadf6339d9d46bcedc3b5e69f798262d10c9072854a9e3c07a184113 -size 28163 +oid sha256:29a24d3f9c01d866f2b3cfdff1460a47f956c2b30865f1c0c74a505a7ee40581 +size 30824 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_5_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_5_null_5,NEXUS_5,1.0,en].png index 9d9369cec3..baca4fe9e0 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_5_null_5,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_5_null_5,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:57428cc1459dab91ffae11e4c719e08b147d3692e24ceea3cec0997659890487 -size 20526 +oid sha256:52009b1f8c129638c82568c0d23822cbed4de45f6ce49062aa38a79dfedecbe8 +size 23041 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_5_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_5_null_6,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..03ff1f7b4b --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewDark_null_RoomMemberDetailsViewDark--3_5_null_6,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:68ceadfa7696c8350c45bc4c47553e8fe8c10e283ae2a3420a4192160bd97ab7 +size 22925 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_4_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_4_null_0,NEXUS_5,1.0,en].png index 52363a0f76..8da2d0200a 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_4_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_4_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fd6633077a2c6e45374beb32e377c813c121bac8d7312439a85808520d061676 -size 20015 +oid sha256:52dcc40a72b799510a4400662b462bba34bbb6be98a35eaa5c3edc3a671aa786 +size 23016 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_4_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_4_null_1,NEXUS_5,1.0,en].png index 831427f42b..7d7ea4300c 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_4_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_4_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e0ef4293fb45a3703c992616709bdd7252d939f419dfd7f3254900cdfdd4b890 -size 17771 +oid sha256:6099039f33757df49245866f6dbcca9576b216b1fe9e1f87c6c4f4e92add153c +size 20945 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_4_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_4_null_2,NEXUS_5,1.0,en].png index d31ae5a8bc..fbdffe0bb4 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_4_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_4_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9019d3716d171d24a02832ec8db6885f811304a76ac6f55d0b4cba5561e51a0a -size 20419 +oid sha256:6f5586f7c60ea5959713a66cb97397c5d834b27100e1603bcbb98c430dc0873e +size 23539 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_4_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_4_null_3,NEXUS_5,1.0,en].png index 10038e1407..f76a02a956 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_4_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_4_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:30dcf6770898700a1fbbd85f80cc957b90af606badcbd22ef5b9ee114142e166 -size 41500 +oid sha256:10d2ea969c9cd2bcd809e72cc56d8ae086b1b4d848636c55e9db5ef23783620d +size 43647 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_4_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_4_null_4,NEXUS_5,1.0,en].png index f81750567c..350f21eeb0 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_4_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_4_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:414cfa386a1c3d97317eaa6ce4272c071420f2b3191ab56e1f4b46904febf552 -size 32491 +oid sha256:e220d275226ae8820e0119a767b2baf964f2173c430531a379e338bdf9e08adc +size 35530 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_4_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_4_null_5,NEXUS_5,1.0,en].png index 752c962509..86f5985e6f 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_4_null_5,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_4_null_5,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:209f2821d99c15981b457682dc8e99036f592858404773ca3165baa66807053c -size 21020 +oid sha256:fd3964a31217ccef0ba53ab1f5341311b52746b20660461220375dcad3befcec +size 24194 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_4_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_4_null_6,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..b99279289b --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members.details_RoomMemberDetailsViewLight_null_RoomMemberDetailsViewLight--2_4_null_6,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1745fbfae1996203b192b07bbbf424eb129944b37b83c0203fcd05c7f332096d +size 25874 From 8ff2454ed08f5f7ca163bb1acb35095399126d3a Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 1 Dec 2023 11:28:29 +0100 Subject: [PATCH 6/6] StartDMAction : small improvements after review --- .../android/features/createroom/impl/DefaultStartDMAction.kt | 2 +- .../features/createroom/impl/DefaultStartDMActionTests.kt | 4 +--- .../createroom/impl/root/CreateRoomRootPresenterTests.kt | 5 ++--- features/createroom/test/build.gradle.kts | 1 - .../android/features/createroom/test/FakeStartDMAction.kt | 5 +++-- .../members/details/RoomMemberDetailsPresenterTests.kt | 4 +--- .../io/element/android/libraries/matrix/api/room/StartDM.kt | 4 ++-- 7 files changed, 10 insertions(+), 15 deletions(-) diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/DefaultStartDMAction.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/DefaultStartDMAction.kt index b7f1dc6c9c..7145ac671e 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/DefaultStartDMAction.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/DefaultStartDMAction.kt @@ -46,7 +46,7 @@ class DefaultStartDMAction @Inject constructor( actionState.value = Async.Success(result.roomId) } is StartDMResult.Failure -> { - actionState.value = Async.Failure(result) + actionState.value = Async.Failure(result.throwable) } } } diff --git a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/DefaultStartDMActionTests.kt b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/DefaultStartDMActionTests.kt index 2bd7ebb687..f2ed30c58d 100644 --- a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/DefaultStartDMActionTests.kt +++ b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/DefaultStartDMActionTests.kt @@ -22,8 +22,6 @@ import im.vector.app.features.analytics.plan.CreatedRoom import io.element.android.libraries.architecture.Async import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.RoomId -import io.element.android.libraries.matrix.api.room.StartDMResult -import io.element.android.libraries.matrix.test.A_FAILURE_REASON import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.matrix.test.A_THROWABLE import io.element.android.libraries.matrix.test.A_USER_ID @@ -69,7 +67,7 @@ class DefaultStartDMActionTests { val action = createStartDMAction(matrixClient) val state = mutableStateOf>(Async.Uninitialized) action.execute(A_USER_ID, state) - assertThat(state.value).isEqualTo(Async.Failure(StartDMResult.Failure(A_FAILURE_REASON))) + assertThat(state.value).isEqualTo(Async.Failure(A_THROWABLE)) } private fun createStartDMAction( diff --git a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootPresenterTests.kt b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootPresenterTests.kt index 5322407c35..a0fb71dd31 100644 --- a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootPresenterTests.kt +++ b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootPresenterTests.kt @@ -28,10 +28,9 @@ import io.element.android.features.createroom.test.FakeStartDMAction import io.element.android.libraries.architecture.Async import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.UserId -import io.element.android.libraries.matrix.api.room.StartDMResult import io.element.android.libraries.matrix.api.user.MatrixUser -import io.element.android.libraries.matrix.test.A_FAILURE_REASON import io.element.android.libraries.matrix.test.A_ROOM_ID +import io.element.android.libraries.matrix.test.A_THROWABLE import io.element.android.libraries.matrix.test.core.aBuildMeta import io.element.android.libraries.usersearch.test.FakeUserRepository import io.element.android.tests.testutils.WarmUpRule @@ -61,7 +60,7 @@ class CreateRoomRootPresenterTests { val matrixUser = MatrixUser(UserId("@name:domain")) val startDMSuccessResult = Async.Success(A_ROOM_ID) - val startDMFailureResult = Async.Failure(StartDMResult.Failure(A_FAILURE_REASON)) + val startDMFailureResult = Async.Failure(A_THROWABLE) // Failure startDMAction.givenExecuteResult(startDMFailureResult) diff --git a/features/createroom/test/build.gradle.kts b/features/createroom/test/build.gradle.kts index 7c83725971..53c3e6461a 100644 --- a/features/createroom/test/build.gradle.kts +++ b/features/createroom/test/build.gradle.kts @@ -26,7 +26,6 @@ dependencies { implementation(libs.coroutines.core) implementation(projects.libraries.matrix.api) implementation(projects.libraries.matrix.test) - implementation(projects.tests.testutils) implementation(projects.libraries.architecture) api(projects.features.createroom.api) } diff --git a/features/createroom/test/src/main/kotlin/io/element/android/features/createroom/test/FakeStartDMAction.kt b/features/createroom/test/src/main/kotlin/io/element/android/features/createroom/test/FakeStartDMAction.kt index 09d59b7f6c..c1888dc53f 100644 --- a/features/createroom/test/src/main/kotlin/io/element/android/features/createroom/test/FakeStartDMAction.kt +++ b/features/createroom/test/src/main/kotlin/io/element/android/features/createroom/test/FakeStartDMAction.kt @@ -22,7 +22,7 @@ import io.element.android.libraries.architecture.Async import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.test.A_ROOM_ID -import io.element.android.tests.testutils.simulateLongTask +import kotlinx.coroutines.delay class FakeStartDMAction : StartDMAction { @@ -32,8 +32,9 @@ class FakeStartDMAction : StartDMAction { executeResult = result } - override suspend fun execute(userId: UserId, actionState: MutableState>) = simulateLongTask { + override suspend fun execute(userId: UserId, actionState: MutableState>) { actionState.value = Async.Loading() + delay(1) actionState.value = executeResult } } diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/details/RoomMemberDetailsPresenterTests.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/details/RoomMemberDetailsPresenterTests.kt index 9cf01c91e2..79a8faf3f1 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/details/RoomMemberDetailsPresenterTests.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/details/RoomMemberDetailsPresenterTests.kt @@ -33,8 +33,6 @@ import io.element.android.libraries.matrix.api.core.RoomId 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.RoomMember -import io.element.android.libraries.matrix.api.room.StartDMResult -import io.element.android.libraries.matrix.test.A_FAILURE_REASON import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.matrix.test.A_THROWABLE import io.element.android.libraries.matrix.test.FakeMatrixClient @@ -208,7 +206,7 @@ class RoomMemberDetailsPresenterTests { val initialState = awaitItem() Truth.assertThat(initialState.startDmActionState).isInstanceOf(Async.Uninitialized::class.java) val startDMSuccessResult = Async.Success(A_ROOM_ID) - val startDMFailureResult = Async.Failure(StartDMResult.Failure(A_FAILURE_REASON)) + val startDMFailureResult = Async.Failure(A_THROWABLE) // Failure startDMAction.givenExecuteResult(startDMFailureResult) diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/StartDM.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/StartDM.kt index b30248170f..7d0fd9a582 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/StartDM.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/StartDM.kt @@ -30,12 +30,12 @@ suspend fun MatrixClient.startDM(userId: UserId): StartDMResult { } else { createDM(userId).fold( { StartDMResult.Success(it, isNew = true) }, - { StartDMResult.Failure(it.localizedMessage) } + { StartDMResult.Failure(it) } ) } } sealed interface StartDMResult { data class Success(val roomId: RoomId, val isNew: Boolean) : StartDMResult - data class Failure(override val message: String?) : StartDMResult, Exception(message) + data class Failure(val throwable: Throwable) : StartDMResult }