Merge pull request #2843 from element-hq/feature/bma/joinRoomVia

Provide serverNames when available and fix issue around analytics
This commit is contained in:
Benoit Marty
2024-05-14 17:17:59 +02:00
committed by GitHub
43 changed files with 543 additions and 103 deletions

View File

@@ -26,4 +26,5 @@ dependencies {
implementation(projects.libraries.architecture)
implementation(projects.libraries.matrix.api)
implementation(projects.features.roomdirectory.api)
implementation(projects.services.analytics.api)
}

View File

@@ -18,6 +18,7 @@ package io.element.android.features.joinroom.api
import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node
import im.vector.app.features.analytics.plan.JoinedRoom
import io.element.android.features.roomdirectory.api.RoomDescription
import io.element.android.libraries.architecture.FeatureEntryPoint
import io.element.android.libraries.architecture.NodeInputs
@@ -32,5 +33,7 @@ interface JoinRoomEntryPoint : FeatureEntryPoint {
val roomId: RoomId,
val roomIdOrAlias: RoomIdOrAlias,
val roomDescription: Optional<RoomDescription>,
val serverNames: List<String>,
val trigger: JoinedRoom.Trigger,
) : NodeInputs
}

View File

@@ -44,9 +44,10 @@ dependencies {
implementation(projects.libraries.matrix.api)
implementation(projects.libraries.matrixui)
implementation(projects.libraries.designsystem)
implementation(projects.libraries.uiStrings)
implementation(projects.features.invite.api)
implementation(projects.features.roomdirectory.api)
implementation(projects.libraries.uiStrings)
implementation(projects.services.analytics.api)
testImplementation(libs.test.junit)
testImplementation(libs.coroutines.test)

View File

@@ -41,6 +41,8 @@ class JoinRoomNode @AssistedInject constructor(
inputs.roomId,
inputs.roomIdOrAlias,
inputs.roomDescription,
inputs.serverNames,
inputs.trigger,
)
@Composable
@@ -49,6 +51,7 @@ class JoinRoomNode @AssistedInject constructor(
JoinRoomView(
state = state,
onBackPressed = ::navigateUp,
onJoinSuccess = ::navigateUp,
onKnockSuccess = ::navigateUp,
modifier = modifier
)

View File

@@ -29,6 +29,7 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import im.vector.app.features.analytics.plan.JoinedRoom
import io.element.android.features.invite.api.response.AcceptDeclineInviteEvents
import io.element.android.features.invite.api.response.AcceptDeclineInviteState
import io.element.android.features.invite.api.response.InviteData
@@ -45,6 +46,7 @@ import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
import io.element.android.libraries.matrix.api.room.MatrixRoomInfo
import io.element.android.libraries.matrix.api.room.RoomType
import io.element.android.libraries.matrix.api.room.join.JoinRoom
import io.element.android.libraries.matrix.api.room.preview.RoomPreview
import io.element.android.libraries.matrix.ui.model.toInviteSender
import kotlinx.coroutines.CoroutineScope
@@ -55,7 +57,10 @@ class JoinRoomPresenter @AssistedInject constructor(
@Assisted private val roomId: RoomId,
@Assisted private val roomIdOrAlias: RoomIdOrAlias,
@Assisted private val roomDescription: Optional<RoomDescription>,
@Assisted private val serverNames: List<String>,
@Assisted private val trigger: JoinedRoom.Trigger,
private val matrixClient: MatrixClient,
private val joinRoom: JoinRoom,
private val knockRoom: KnockRoom,
private val acceptDeclineInvitePresenter: Presenter<AcceptDeclineInviteState>,
private val buildMeta: BuildMeta,
@@ -65,6 +70,8 @@ class JoinRoomPresenter @AssistedInject constructor(
roomId: RoomId,
roomIdOrAlias: RoomIdOrAlias,
roomDescription: Optional<RoomDescription>,
serverNames: List<String>,
trigger: JoinedRoom.Trigger,
): JoinRoomPresenter
}
@@ -73,6 +80,7 @@ class JoinRoomPresenter @AssistedInject constructor(
val coroutineScope = rememberCoroutineScope()
var retryCount by remember { mutableIntStateOf(0) }
val roomInfo by matrixClient.getRoomInfoFlow(roomId).collectAsState(initial = Optional.empty())
val joinAction: MutableState<AsyncAction<Unit>> = remember { mutableStateOf(AsyncAction.Uninitialized) }
val knockAction: MutableState<AsyncAction<Unit>> = remember { mutableStateOf(AsyncAction.Uninitialized) }
val contentState by produceState<ContentState>(
initialValue = ContentState.Loading(roomIdOrAlias),
@@ -108,16 +116,14 @@ class JoinRoomPresenter @AssistedInject constructor(
fun handleEvents(event: JoinRoomEvents) {
when (event) {
JoinRoomEvents.AcceptInvite,
JoinRoomEvents.JoinRoom -> {
JoinRoomEvents.JoinRoom -> coroutineScope.joinRoom(joinAction)
JoinRoomEvents.KnockRoom -> coroutineScope.knockRoom(knockAction)
JoinRoomEvents.AcceptInvite -> {
val inviteData = contentState.toInviteData() ?: return
acceptDeclineInviteState.eventSink(
AcceptDeclineInviteEvents.AcceptInvite(inviteData)
)
}
JoinRoomEvents.KnockRoom -> {
coroutineScope.knockRoom(roomId, knockAction)
}
JoinRoomEvents.DeclineInvite -> {
val inviteData = contentState.toInviteData() ?: return
acceptDeclineInviteState.eventSink(
@@ -129,6 +135,7 @@ class JoinRoomPresenter @AssistedInject constructor(
}
JoinRoomEvents.ClearError -> {
knockAction.value = AsyncAction.Uninitialized
joinAction.value = AsyncAction.Uninitialized
}
}
}
@@ -136,13 +143,24 @@ class JoinRoomPresenter @AssistedInject constructor(
return JoinRoomState(
contentState = contentState,
acceptDeclineInviteState = acceptDeclineInviteState,
joinAction = joinAction.value,
knockAction = knockAction.value,
applicationName = buildMeta.applicationName,
eventSink = ::handleEvents
)
}
private fun CoroutineScope.knockRoom(roomId: RoomId, knockAction: MutableState<AsyncAction<Unit>>) = launch {
private fun CoroutineScope.joinRoom(joinAction: MutableState<AsyncAction<Unit>>) = launch {
joinAction.runUpdatingState {
joinRoom.invoke(
roomId = roomId,
serverNames = serverNames,
trigger = trigger
)
}
}
private fun CoroutineScope.knockRoom(knockAction: MutableState<AsyncAction<Unit>>) = launch {
knockAction.runUpdatingState {
knockRoom(roomId)
}

View File

@@ -31,6 +31,7 @@ import io.element.android.libraries.matrix.ui.model.InviteSender
data class JoinRoomState(
val contentState: ContentState,
val acceptDeclineInviteState: AcceptDeclineInviteState,
val joinAction: AsyncAction<Unit>,
val knockAction: AsyncAction<Unit>,
val applicationName: String,
val eventSink: (JoinRoomEvents) -> Unit

View File

@@ -125,11 +125,13 @@ fun aLoadedContentState(
fun aJoinRoomState(
contentState: ContentState = aLoadedContentState(),
acceptDeclineInviteState: AcceptDeclineInviteState = anAcceptDeclineInviteState(),
joinAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
knockAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
eventSink: (JoinRoomEvents) -> Unit = {}
) = JoinRoomState(
contentState = contentState,
acceptDeclineInviteState = acceptDeclineInviteState,
joinAction = joinAction,
knockAction = knockAction,
applicationName = "AppName",
eventSink = eventSink

View File

@@ -66,6 +66,7 @@ import io.element.android.libraries.ui.strings.CommonStrings
fun JoinRoomView(
state: JoinRoomState,
onBackPressed: () -> Unit,
onJoinSuccess: () -> Unit,
onKnockSuccess: () -> Unit,
modifier: Modifier = Modifier,
) {
@@ -108,7 +109,11 @@ fun JoinRoomView(
}
)
}
AsyncActionView(
async = state.joinAction,
onSuccess = { onJoinSuccess() },
onErrorDismiss = { state.eventSink(JoinRoomEvents.ClearError) },
)
AsyncActionView(
async = state.knockAction,
onSuccess = { onKnockSuccess() },
@@ -323,6 +328,7 @@ internal fun JoinRoomViewPreview(@PreviewParameter(JoinRoomStateProvider::class)
JoinRoomView(
state = state,
onBackPressed = { },
onJoinSuccess = { },
onKnockSuccess = { },
)
}

View File

@@ -19,6 +19,7 @@ package io.element.android.features.joinroom.impl.di
import com.squareup.anvil.annotations.ContributesTo
import dagger.Module
import dagger.Provides
import im.vector.app.features.analytics.plan.JoinedRoom
import io.element.android.features.invite.api.response.AcceptDeclineInviteState
import io.element.android.features.joinroom.impl.JoinRoomPresenter
import io.element.android.features.roomdirectory.api.RoomDescription
@@ -28,6 +29,7 @@ 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.RoomIdOrAlias
import io.element.android.libraries.matrix.api.room.join.JoinRoom
import java.util.Optional
@Module
@@ -36,6 +38,7 @@ object JoinRoomModule {
@Provides
fun providesJoinRoomPresenterFactory(
client: MatrixClient,
joinRoom: JoinRoom,
knockRoom: KnockRoom,
acceptDeclineInvitePresenter: Presenter<AcceptDeclineInviteState>,
buildMeta: BuildMeta,
@@ -45,12 +48,17 @@ object JoinRoomModule {
roomId: RoomId,
roomIdOrAlias: RoomIdOrAlias,
roomDescription: Optional<RoomDescription>,
serverNames: List<String>,
trigger: JoinedRoom.Trigger,
): JoinRoomPresenter {
return JoinRoomPresenter(
roomId = roomId,
roomIdOrAlias = roomIdOrAlias,
roomDescription = roomDescription,
serverNames = serverNames,
trigger = trigger,
matrixClient = client,
joinRoom = joinRoom,
knockRoom = knockRoom,
acceptDeclineInvitePresenter = acceptDeclineInvitePresenter,
buildMeta = buildMeta,

View File

@@ -17,6 +17,7 @@
package io.element.android.features.joinroom.impl
import com.google.common.truth.Truth.assertThat
import im.vector.app.features.analytics.plan.JoinedRoom
import io.element.android.features.invite.api.response.AcceptDeclineInviteEvents
import io.element.android.features.invite.api.response.AcceptDeclineInviteState
import io.element.android.features.invite.api.response.anAcceptDeclineInviteState
@@ -36,10 +37,12 @@ import io.element.android.libraries.matrix.api.room.preview.RoomPreview
import io.element.android.libraries.matrix.test.AN_EXCEPTION
import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.libraries.matrix.test.A_ROOM_NAME
import io.element.android.libraries.matrix.test.A_SERVER_LIST
import io.element.android.libraries.matrix.test.FakeMatrixClient
import io.element.android.libraries.matrix.test.core.aBuildMeta
import io.element.android.libraries.matrix.test.room.aRoomInfo
import io.element.android.libraries.matrix.test.room.aRoomMember
import io.element.android.libraries.matrix.test.room.join.FakeJoinRoom
import io.element.android.libraries.matrix.ui.model.toInviteSender
import io.element.android.tests.testutils.WarmUpRule
import io.element.android.tests.testutils.lambda.assert
@@ -174,6 +177,59 @@ class JoinRoomPresenterTest {
}
}
@Test
fun `present - when room is joined with success, all the parameters are provided`() = runTest {
val aTrigger = JoinedRoom.Trigger.MobilePermalink
val joinRoomLambda = lambdaRecorder { _: RoomId, _: List<String>, _: JoinedRoom.Trigger ->
Result.success(Unit)
}
val presenter = createJoinRoomPresenter(
trigger = aTrigger,
serverNames = A_SERVER_LIST,
joinRoomLambda = joinRoomLambda,
)
presenter.test {
skipItems(1)
awaitItem().also { state ->
state.eventSink(JoinRoomEvents.JoinRoom)
}
awaitItem().also { state ->
assertThat(state.joinAction).isEqualTo(AsyncAction.Loading)
}
awaitItem().also { state ->
assertThat(state.joinAction).isEqualTo(AsyncAction.Success(Unit))
}
joinRoomLambda.assertions()
.isCalledOnce()
.with(value(A_ROOM_ID), value(A_SERVER_LIST), value(aTrigger))
}
}
@Test
fun `present - when room is joined with error, it is possible to clear the error`() = runTest {
val presenter = createJoinRoomPresenter(
joinRoomLambda = { _, _, _ ->
Result.failure(AN_EXCEPTION)
},
)
presenter.test {
skipItems(1)
awaitItem().also { state ->
state.eventSink(JoinRoomEvents.JoinRoom)
}
awaitItem().also { state ->
assertThat(state.joinAction).isEqualTo(AsyncAction.Loading)
}
awaitItem().also { state ->
assertThat(state.joinAction).isEqualTo(AsyncAction.Failure(AN_EXCEPTION))
state.eventSink(JoinRoomEvents.ClearError)
}
awaitItem().also { state ->
assertThat(state.joinAction).isEqualTo(AsyncAction.Uninitialized)
}
}
}
@Test
fun `present - when room is left and public then join authorization is equal to canJoin`() = runTest {
val roomInfo = aRoomInfo(currentUserMembership = CurrentUserMembership.LEFT, isPublic = true)
@@ -415,7 +471,12 @@ class JoinRoomPresenterTest {
private fun createJoinRoomPresenter(
roomId: RoomId = A_ROOM_ID,
roomDescription: Optional<RoomDescription> = Optional.empty(),
serverNames: List<String> = emptyList(),
trigger: JoinedRoom.Trigger = JoinedRoom.Trigger.Invite,
matrixClient: MatrixClient = FakeMatrixClient(),
joinRoomLambda: (RoomId, List<String>, JoinedRoom.Trigger) -> Result<Unit> = { _, _, _ ->
Result.success(Unit)
},
knockRoom: KnockRoom = FakeKnockRoom(),
buildMeta: BuildMeta = aBuildMeta(applicationName = "AppName"),
acceptDeclineInvitePresenter: Presenter<AcceptDeclineInviteState> = Presenter { anAcceptDeclineInviteState() }
@@ -424,7 +485,10 @@ class JoinRoomPresenterTest {
roomId = roomId,
roomIdOrAlias = roomId.toRoomIdOrAlias(),
roomDescription = roomDescription,
serverNames = serverNames,
trigger = trigger,
matrixClient = matrixClient,
joinRoom = FakeJoinRoom(joinRoomLambda),
knockRoom = knockRoom,
buildMeta = buildMeta,
acceptDeclineInvitePresenter = acceptDeclineInvitePresenter

View File

@@ -91,6 +91,34 @@ class JoinRoomViewTest {
eventsRecorder.assertSingle(JoinRoomEvents.ClearError)
}
@Test
fun `clicking on closing Join error emits the expected Event`() {
val eventsRecorder = EventsRecorder<JoinRoomEvents>()
rule.setJoinRoomView(
aJoinRoomState(
contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.CanKnock),
joinAction = AsyncAction.Failure(Exception("Error")),
eventSink = eventsRecorder,
),
)
rule.clickOn(CommonStrings.action_ok)
eventsRecorder.assertSingle(JoinRoomEvents.ClearError)
}
@Test
fun `when joining room is successful, the expected callback is invoked`() {
val eventsRecorder = EventsRecorder<JoinRoomEvents>(expectEvents = false)
ensureCalledOnce {
rule.setJoinRoomView(
aJoinRoomState(
joinAction = AsyncAction.Success(Unit),
eventSink = eventsRecorder,
),
onJoinSuccess = it
)
}
}
@Test
fun `clicking on Accept invitation IsInvited room emits the expected Event`() {
val eventsRecorder = EventsRecorder<JoinRoomEvents>()
@@ -149,12 +177,14 @@ class JoinRoomViewTest {
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setJoinRoomView(
state: JoinRoomState,
onBackPressed: () -> Unit = EnsureNeverCalled(),
onJoinSuccess: () -> Unit = EnsureNeverCalled(),
onKnockSuccess: () -> Unit = EnsureNeverCalled(),
) {
setContent {
JoinRoomView(
state = state,
onBackPressed = onBackPressed,
onJoinSuccess = onJoinSuccess,
onKnockSuccess = onKnockSuccess,
)
}