Close the invite screen once the invite has been declined (#7440)

Declining an invite from the full screen invite left the user on that
same screen, so they had to press back themselves to get to the room
list. Both decline paths are affected: the plain decline handled by
AcceptDeclineInviteView, and the decline and block sub screen, which
navigated up into the join room flow instead of out of it.

Navigate up from the join room flow on a successful decline, and give
DeclineInviteAndBlockEntryPoint a callback so the sub screen can report
a successful decline separately from a back press. The room list flow
supplies the same callback, popping the sub screen once the invite has
been declined.
This commit is contained in:
hayyaksi
2026-08-19 13:52:11 +03:00
committed by GitHub
parent 03c69be722
commit 6bd841d838
10 changed files with 53 additions and 6 deletions
@@ -269,6 +269,11 @@ class HomeFlowNode(
parentNode = this,
buildContext = buildContext,
inviteData = navTarget.inviteData,
callback = object : DeclineInviteAndBlockEntryPoint.Callback {
override fun onDeclineSuccess() {
backstack.pop()
}
},
)
}
is NavTarget.SelectNewOwnersWhenLeavingRoom -> {
@@ -11,6 +11,7 @@ package io.element.android.features.home.impl
import com.bumble.appyx.core.modality.BuildContext
import com.google.common.truth.Truth.assertThat
import io.element.android.features.home.api.HomeEntryPoint
import io.element.android.features.invite.test.declineandblock.FakeDeclineInviteAndBlockEntryPoint
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.room.JoinedRoom
import io.element.android.libraries.matrix.test.FakeMatrixClient
@@ -36,7 +37,7 @@ class DefaultHomeEntryPointTest : RobolectricTest() {
acceptDeclineInviteView = { _, _, _, _ -> lambdaError() },
directLogoutView = { _ -> lambdaError() },
reportRoomEntryPoint = { _, _, _ -> lambdaError() },
declineInviteAndBlockUserEntryPoint = { _, _, _ -> lambdaError() },
declineInviteAndBlockUserEntryPoint = FakeDeclineInviteAndBlockEntryPoint(),
changeRoomMemberRolesEntryPoint = { _, _, _, _ -> lambdaError() },
leaveRoomRenderer = { _, _, _ -> lambdaError() },
sessionCoroutineScope = backgroundScope,
@@ -10,13 +10,19 @@ package io.element.android.features.invite.api.declineandblock
import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node
import com.bumble.appyx.core.plugin.Plugin
import io.element.android.features.invite.api.InviteData
import io.element.android.libraries.architecture.FeatureEntryPoint
fun interface DeclineInviteAndBlockEntryPoint : FeatureEntryPoint {
interface DeclineInviteAndBlockEntryPoint : FeatureEntryPoint {
fun createNode(
parentNode: Node,
buildContext: BuildContext,
inviteData: InviteData,
callback: Callback,
): Node
interface Callback : Plugin {
fun onDeclineSuccess()
}
}
@@ -17,7 +17,9 @@ import dev.zacsweers.metro.Assisted
import dev.zacsweers.metro.AssistedInject
import io.element.android.annotations.ContributesNode
import io.element.android.features.invite.api.InviteData
import io.element.android.features.invite.api.declineandblock.DeclineInviteAndBlockEntryPoint
import io.element.android.libraries.architecture.NodeInputs
import io.element.android.libraries.architecture.callback
import io.element.android.libraries.architecture.inputs
import io.element.android.libraries.di.SessionScope
@@ -31,6 +33,7 @@ class DeclineAndBlockNode(
data class Inputs(val inviteData: InviteData) : NodeInputs
private val inviteData = inputs<Inputs>().inviteData
private val callback: DeclineInviteAndBlockEntryPoint.Callback = callback()
private val presenter = presenterFactory.create(inviteData)
@Composable
@@ -39,6 +42,7 @@ class DeclineAndBlockNode(
DeclineAndBlockView(
state = state,
onBackClick = ::navigateUp,
onDeclineSuccess = callback::onDeclineSuccess,
modifier = modifier
)
}
@@ -46,6 +46,7 @@ import io.element.android.libraries.ui.strings.CommonStrings
fun DeclineAndBlockView(
state: DeclineAndBlockState,
onBackClick: () -> Unit,
onDeclineSuccess: () -> Unit,
modifier: Modifier = Modifier,
) {
val focusManager = LocalFocusManager.current
@@ -53,7 +54,7 @@ fun DeclineAndBlockView(
val isDeclining = state.declineAction is AsyncAction.Loading
AsyncActionView(
async = state.declineAction,
onSuccess = { onBackClick() },
onSuccess = { onDeclineSuccess() },
errorMessage = { stringResource(CommonStrings.error_unknown) },
onRetry = { state.eventSink(DeclineAndBlockEvents.Decline) },
onErrorDismiss = { state.eventSink(DeclineAndBlockEvents.ClearDeclineAction) }
@@ -149,5 +150,6 @@ internal fun DeclineAndBlockViewPreview(
DeclineAndBlockView(
state = state,
onBackClick = {},
onDeclineSuccess = {},
)
}
@@ -22,8 +22,9 @@ class DefaultDeclineAndBlockEntryPoint : DeclineInviteAndBlockEntryPoint {
parentNode: Node,
buildContext: BuildContext,
inviteData: InviteData,
callback: DeclineInviteAndBlockEntryPoint.Callback,
): Node {
val inputs = DeclineAndBlockNode.Inputs(inviteData)
return parentNode.createNode<DeclineAndBlockNode>(buildContext, plugins = listOf(inputs))
return parentNode.createNode<DeclineAndBlockNode>(buildContext, plugins = listOf(inputs, callback))
}
}
@@ -17,6 +17,7 @@ import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performTextInput
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
import io.element.android.features.invite.impl.R
import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.tests.testutils.EnsureNeverCalled
import io.element.android.tests.testutils.EventsRecorder
@@ -41,6 +42,20 @@ class DeclineAndBlockViewTest : RobolectricTest() {
}
}
@Test
fun `a successful decline invokes the expected callback`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder<DeclineAndBlockEvents>(expectEvents = false)
ensureCalledOnce {
setDeclineAndBlockView(
aDeclineAndBlockState(
declineAction = AsyncAction.Success(Unit),
eventSink = eventsRecorder,
),
onDeclineSuccess = it,
)
}
}
@Test
fun `clicking on decline when enabled emits the expected event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder<DeclineAndBlockEvents>()
@@ -112,11 +127,13 @@ class DeclineAndBlockViewTest : RobolectricTest() {
private fun AndroidComposeUiTest<ComponentActivity>.setDeclineAndBlockView(
state: DeclineAndBlockState,
onBackClick: () -> Unit = EnsureNeverCalled(),
onDeclineSuccess: () -> Unit = EnsureNeverCalled(),
) {
setContent {
DeclineAndBlockView(
state = state,
onBackClick = onBackClick,
onDeclineSuccess = onDeclineSuccess,
)
}
}
@@ -11,7 +11,9 @@ package io.element.android.features.invite.impl.declineandblock
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import com.bumble.appyx.core.modality.BuildContext
import com.google.common.truth.Truth.assertThat
import io.element.android.features.invite.api.declineandblock.DeclineInviteAndBlockEntryPoint
import io.element.android.features.invite.test.anInviteData
import io.element.android.tests.testutils.lambda.lambdaError
import io.element.android.tests.testutils.node.TestParentNode
import org.junit.Rule
import org.junit.Test
@@ -31,12 +33,17 @@ class DefaultDeclineAndBlockEntryPointTest {
)
}
val inviteData = anInviteData()
val callback = object : DeclineInviteAndBlockEntryPoint.Callback {
override fun onDeclineSuccess() = lambdaError()
}
val result = entryPoint.createNode(
parentNode = parentNode,
buildContext = BuildContext.root(null),
inviteData = inviteData
inviteData = inviteData,
callback = callback,
)
assertThat(result).isInstanceOf(DeclineAndBlockNode::class.java)
assertThat(result.plugins).contains(DeclineAndBlockNode.Inputs(inviteData))
assertThat(result.plugins).contains(callback)
}
}
@@ -19,6 +19,7 @@ class FakeDeclineInviteAndBlockEntryPoint : DeclineInviteAndBlockEntryPoint {
parentNode: Node,
buildContext: BuildContext,
inviteData: InviteData,
callback: DeclineInviteAndBlockEntryPoint.Callback,
): Node {
lambdaError()
}
@@ -69,6 +69,9 @@ class JoinRoomFlowNode(
parentNode = this,
buildContext = buildContext,
inviteData = navTarget.inviteData,
callback = object : DeclineInviteAndBlockEntryPoint.Callback {
override fun onDeclineSuccess() = navigateUp()
},
)
NavTarget.Root -> rootNode(buildContext)
}
@@ -99,7 +102,7 @@ class JoinRoomFlowNode(
acceptDeclineInviteView.Render(
state = state.acceptDeclineInviteState,
onAcceptInviteSuccess = {},
onDeclineInviteSuccess = {},
onDeclineInviteSuccess = { navigateUp() },
modifier = Modifier
)
}